From e4c8a529f5f42e905d81a3610bfbef42f81d63d2 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 6 Mar 2022 14:06:07 +0100 Subject: [PATCH 001/999] Update to 2018 edition (and MSRV 1.32) --- .github/workflows/test-release.yml | 7 ++- .github/workflows/test.yml | 7 ++- Cargo.toml | 1 + ci/github.sh | 12 ++-- src/date.rs | 18 +++--- src/datetime.rs | 90 +++++++++++++++--------------- src/format/mod.rs | 16 +++--- src/format/parse.rs | 12 ++-- src/format/parsed.rs | 22 ++++---- src/format/scan.rs | 4 +- src/format/strftime.rs | 4 +- src/lib.rs | 18 +++--- src/naive/date.rs | 18 +++--- src/naive/datetime.rs | 42 +++++++------- src/naive/internals.rs | 6 +- src/naive/isoweek.rs | 4 +- src/naive/time.rs | 16 +++--- src/offset/fixed.rs | 12 ++-- src/offset/local.rs | 14 ++--- src/offset/mod.rs | 10 ++-- src/offset/utc.rs | 6 +- src/round.rs | 16 +++--- 22 files changed, 178 insertions(+), 177 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 5d57685fa1..4072db5a7d 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -31,10 +31,11 @@ jobs: - os: ubuntu-latest rust_version: nightly - os: ubuntu-18.04 - rust_version: 1.13.0 + rust_version: 1.32.0 - os: macos-latest - rust_version: 1.13.0 - # time doesn't work on windows with 1.13 + rust_version: 1.32.0 + - os: windows-latest + rust_version: 1.32.0 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e439fe7cc2..710ad46526 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,10 +39,11 @@ jobs: - os: ubuntu-latest rust_version: nightly - os: ubuntu-18.04 - rust_version: 1.13.0 + rust_version: 1.32.0 - os: macos-latest - rust_version: 1.13.0 - # time doesn't work on windows with 1.13 + rust_version: 1.32.0 + - os: windows-latest + rust_version: 1.32.0 runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index 6b4d48afa8..e39c900672 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ categories = ["date-and-time"] readme = "README.md" license = "MIT/Apache-2.0" exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"] +edition = "2018" [badges] travis-ci = { repository = "chronotope/chrono" } diff --git a/ci/github.sh b/ci/github.sh index ee62d46fb1..207cb08a04 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -8,7 +8,7 @@ source "${BASH_SOURCE[0]%/*}/_shlib.sh" TEST_TZS=(ACST-9:30 EST4 UTC0 Asia/Katmandu) FEATURES=(std serde clock "alloc serde" unstable-locales) CHECK_FEATURES=(alloc "std unstable-locales" "serde clock" "clock unstable-locales") -RUST_113_FEATURES=(rustc-serialize serde) +RUST_132_FEATURES=(rustc-serialize serde) main() { if [[ "$*" =~ "-h" ]]; then @@ -29,7 +29,7 @@ meaningful in the github actions feature matrix UI. runv cargo --version - if [[ ${RUST_VERSION:-} != 1.13.0 ]]; then + if [[ ${RUST_VERSION:-} != 1.32.0 ]]; then if [[ ${WASM:-} == yes_wasm ]]; then test_wasm elif [[ ${WASM:-} == wasm_simple ]]; then @@ -43,8 +43,8 @@ meaningful in the github actions feature matrix UI. else test_regular UTC0 fi - elif [[ ${RUST_VERSION:-} == 1.13.0 ]]; then - test_113 + elif [[ ${RUST_VERSION:-} == 1.32.0 ]]; then + test_132 else echo "ERROR: didn't run any tests" exit 1 @@ -74,9 +74,9 @@ check_combinatoric() { done } -test_113() { +test_132() { runv cargo build --color=always - for feature in "${RUST_113_FEATURES[@]}"; do + for feature in "${RUST_132_FEATURES[@]}"; do runt cargo build --features "$feature" --color=always done } diff --git a/src/date.rs b/src/date.rs index a12f383a18..ed29e0df95 100644 --- a/src/date.rs +++ b/src/date.rs @@ -3,21 +3,21 @@ //! ISO 8601 calendar date with time zone. +use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, Sub}; use core::{fmt, hash}; -use oldtime::Duration as OldDuration; #[cfg(feature = "unstable-locales")] -use format::Locale; +use crate::format::Locale; #[cfg(any(feature = "alloc", feature = "std", test))] -use format::{DelayedFormat, Item, StrftimeItems}; -use naive::{self, IsoWeek, NaiveDate, NaiveTime}; -use offset::{TimeZone, Utc}; -use DateTime; -use {Datelike, Weekday}; +use crate::format::{DelayedFormat, Item, StrftimeItems}; +use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::{TimeZone, Utc}; +use crate::DateTime; +use crate::{Datelike, Weekday}; /// ISO 8601 calendar date with time zone. /// @@ -36,7 +36,7 @@ use {Datelike, Weekday}; /// the corresponding local date should exist for at least a moment. /// (It may still have a gap from the offset changes.) /// -/// - The `TimeZone` is free to assign *any* [`Offset`](::offset::Offset) to the +/// - The `TimeZone` is free to assign *any* [`Offset`](crate::offset::Offset) to the /// local date, as long as that offset did occur in given day. /// /// For example, if `2015-03-08T01:59-08:00` is followed by `2015-03-08T03:00-07:00`, @@ -297,7 +297,7 @@ where } /// Formats the date with the specified format string. - /// See the [`::format::strftime`] module + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. /// /// # Example diff --git a/src/datetime.rs b/src/datetime.rs index a5594cadab..1ed7c2955c 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -3,10 +3,10 @@ //! ISO 8601 date and time with time zone. +use crate::oldtime::Duration as OldDuration; use core::cmp::Ordering; use core::ops::{Add, Sub}; use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; #[cfg(any(feature = "std", test))] use std::time::{SystemTime, UNIX_EPOCH}; @@ -16,19 +16,19 @@ use alloc::string::{String, ToString}; use std::string::ToString; #[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; +use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] -use format::Locale; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item}; -use naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +use crate::format::Locale; +use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{Fixed, Item}; +use crate::naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] -use offset::Local; -use offset::{FixedOffset, Offset, TimeZone, Utc}; -use Date; -use {Datelike, Timelike, Weekday}; +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::Date; +use crate::{Datelike, Timelike, Weekday}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use core::borrow::Borrow; /// Specific formatting options for seconds. This may be extended in the /// future, so exhaustive matching in external code is not recommended. @@ -473,7 +473,7 @@ impl DateTime { /// Parses a string with the specified format string and returns a new /// [`DateTime`] with a parsed [`FixedOffset`]. /// - /// See the [`::format::strftime`] module on the supported escape + /// See the [`crate::format::strftime`] module on the supported escape /// sequences. /// /// See also [`TimeZone::datetime_from_str`] which gives a local @@ -543,9 +543,9 @@ where /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use format::Numeric::*; - use format::Pad::Zero; - use SecondsFormat::*; + use crate::format::Numeric::*; + use crate::format::Pad::Zero; + use crate::SecondsFormat::*; debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); @@ -597,7 +597,7 @@ where } /// Formats the combined date and time with the specified format string. - /// See the [`::format::strftime`] module + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. /// /// # Example @@ -1084,11 +1084,11 @@ fn test_decodable_json_timestamps( #[cfg(feature = "rustc-serialize")] pub mod rustc_serialize { use super::DateTime; + #[cfg(feature = "clock")] + use crate::offset::Local; + use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; use core::fmt; use core::ops::Deref; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; impl Encodable for DateTime { @@ -1211,12 +1211,12 @@ pub mod rustc_serialize { #[cfg(feature = "serde")] pub mod serde { use super::DateTime; - use core::fmt; #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use serdelib::{de, ser}; - use {ne_timestamp, SerdeError}; + use crate::offset::Local; + use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; + use crate::serdelib::{de, ser}; + use crate::{ne_timestamp, SerdeError}; + use core::fmt; #[doc(hidden)] #[derive(Debug)] @@ -1379,8 +1379,8 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use offset::TimeZone; - use {DateTime, Utc}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; use super::{serde_from, NanoSecondsTimestampVisitor}; @@ -1532,7 +1532,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {DateTime, Utc}; + use crate::{DateTime, Utc}; use super::NanoSecondsTimestampVisitor; @@ -1689,8 +1689,8 @@ pub mod serde { use serdelib::{de, ser}; - use offset::TimeZone; - use {DateTime, Utc}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; use super::{serde_from, MicroSecondsTimestampVisitor}; @@ -1842,7 +1842,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {DateTime, Utc}; + use crate::{DateTime, Utc}; use super::MicroSecondsTimestampVisitor; @@ -1998,8 +1998,8 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use offset::TimeZone; - use {DateTime, Utc}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; use super::{serde_from, MilliSecondsTimestampVisitor}; @@ -2148,7 +2148,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {DateTime, Utc}; + use crate::{DateTime, Utc}; use super::MilliSecondsTimestampVisitor; @@ -2317,8 +2317,8 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use offset::TimeZone; - use {DateTime, Utc}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; use super::{serde_from, SecondsTimestampVisitor}; @@ -2461,7 +2461,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {DateTime, Utc}; + use crate::{DateTime, Utc}; use super::SecondsTimestampVisitor; @@ -2614,14 +2614,14 @@ pub mod serde { #[cfg(test)] mod tests { use super::DateTime; - use naive::{NaiveDate, NaiveTime}; + use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, TimeZone, Utc}; - use oldtime::Duration; - use std::time::{SystemTime, UNIX_EPOCH}; + use crate::offset::Local; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::oldtime::Duration; #[cfg(feature = "clock")] - use Datelike; + use crate::Datelike; + use std::time::{SystemTime, UNIX_EPOCH}; #[test] #[allow(non_snake_case)] @@ -2774,7 +2774,7 @@ mod tests { #[test] fn test_rfc3339_opts() { - use SecondsFormat::*; + use crate::SecondsFormat::*; let pst = FixedOffset::east(8 * 60 * 60); let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); @@ -2797,7 +2797,7 @@ mod tests { #[test] #[should_panic] fn test_rfc3339_opts_nonexhaustive() { - use SecondsFormat; + use crate::SecondsFormat; let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); } diff --git a/src/format/mod.rs b/src/format/mod.rs index ba1721d495..caec32e240 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -47,22 +47,22 @@ use core::str::FromStr; use std::error::Error; #[cfg(any(feature = "alloc", feature = "std", test))] -use naive::{NaiveDate, NaiveTime}; +use crate::naive::{NaiveDate, NaiveTime}; #[cfg(any(feature = "alloc", feature = "std", test))] -use offset::{FixedOffset, Offset}; +use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std", test))] -use {Datelike, Timelike}; -use {Month, ParseMonthError, ParseWeekdayError, Weekday}; +use crate::{Datelike, Timelike}; +use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; #[cfg(feature = "unstable-locales")] pub(crate) mod locales; -pub use self::parse::parse; -pub use self::parsed::Parsed; -pub use self::strftime::StrftimeItems; +pub use parse::parse; +pub use parsed::Parsed; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use pure_rust_locales::Locale; +pub use strftime::StrftimeItems; #[cfg(not(feature = "unstable-locales"))] #[derive(Debug)] @@ -465,8 +465,8 @@ fn format_inner<'a>( ) }; + use crate::div::{div_floor, mod_floor}; use core::fmt::Write; - use div::{div_floor, mod_floor}; match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), diff --git a/src/format/parse.rs b/src/format/parse.rs index 175e4c66d6..f7e28a4975 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -14,7 +14,7 @@ use super::scan; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; use super::{ParseError, ParseErrorKind, ParseResult}; use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; -use {DateTime, FixedOffset, Weekday}; +use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -809,8 +809,8 @@ fn test_parse() { fn test_rfc2822() { use super::NOT_ENOUGH; use super::*; - use offset::FixedOffset; - use DateTime; + use crate::offset::FixedOffset; + use crate::DateTime; // Test data - (input, Ok(expected result after parse and format) or Err(error code)) let testdates = [ @@ -864,7 +864,7 @@ fn test_rfc2822() { #[cfg(test)] #[test] fn parse_rfc850() { - use {TimeZone, Utc}; + use crate::{TimeZone, Utc}; static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT"; @@ -897,8 +897,8 @@ fn parse_rfc850() { #[test] fn test_rfc3339() { use super::*; - use offset::FixedOffset; - use DateTime; + use crate::offset::FixedOffset; + use crate::DateTime; // Test data - (input, Ok(expected result after parse and format) or Err(error code)) let testdates = [ diff --git a/src/format/parsed.rs b/src/format/parsed.rs index cebbafcbd5..8223b30ac5 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,16 +4,16 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. +use crate::oldtime::Duration as OldDuration; use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; -use div::div_rem; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use DateTime; -use Weekday; -use {Datelike, Timelike}; +use crate::div::div_rem; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::DateTime; +use crate::Weekday; +use crate::{Datelike, Timelike}; /// Parsed parts of date and time. There are two classes of methods: /// @@ -693,10 +693,10 @@ impl Parsed { mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; - use naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE}; - use offset::{FixedOffset, TimeZone, Utc}; - use Datelike; - use Weekday::*; + use crate::naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE}; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::Datelike; + use crate::Weekday::*; #[test] fn test_parsed_set_fields() { diff --git a/src/format/scan.rs b/src/format/scan.rs index 581ed4ef73..70034a7684 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -8,7 +8,7 @@ #![allow(deprecated)] use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; -use Weekday; +use crate::Weekday; /// Returns true when two slices are equal case-insensitively (in ASCII). /// Assumes that the `pattern` is already converted to lower case. @@ -62,7 +62,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { }; } - Ok((&s[::core::cmp::min(max, bytes.len())..], n)) + Ok((&s[core::cmp::min(max, bytes.len())..], n)) } /// Tries to consume at least one digits as a fractional second. diff --git a/src/format/strftime.rs b/src/format/strftime.rs index c6e3e721fc..167055e5d9 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -540,7 +540,7 @@ fn test_strftime_items() { #[cfg(test)] #[test] fn test_strftime_docs() { - use {FixedOffset, TimeZone, Timelike}; + use crate::{FixedOffset, TimeZone, Timelike}; let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); @@ -618,7 +618,7 @@ fn test_strftime_docs() { #[cfg(feature = "unstable-locales")] #[test] fn test_strftime_docs_localized() { - use {FixedOffset, TimeZone}; + use crate::{FixedOffset, TimeZone}; let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); diff --git a/src/lib.rs b/src/lib.rs index e18a1da38e..82b6622f7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,25 +518,25 @@ pub use round::{DurationRound, RoundingError, SubsecRound}; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { #[doc(no_inline)] - pub use Date; + pub use crate::Date; #[cfg(feature = "clock")] #[doc(no_inline)] - pub use Local; + pub use crate::Local; #[cfg(feature = "unstable-locales")] #[doc(no_inline)] - pub use Locale; + pub use crate::Locale; #[doc(no_inline)] - pub use SubsecRound; + pub use crate::SubsecRound; #[doc(no_inline)] - pub use {DateTime, SecondsFormat}; + pub use crate::{DateTime, SecondsFormat}; #[doc(no_inline)] - pub use {Datelike, Month, Timelike, Weekday}; + pub use crate::{Datelike, Month, Timelike, Weekday}; #[doc(no_inline)] - pub use {FixedOffset, Utc}; + pub use crate::{FixedOffset, Utc}; #[doc(no_inline)] - pub use {NaiveDate, NaiveDateTime, NaiveTime}; + pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; #[doc(no_inline)] - pub use {Offset, TimeZone}; + pub use crate::{Offset, TimeZone}; } // useful throughout the codebase diff --git a/src/naive/date.rs b/src/naive/date.rs index 9cd00e7d71..3ba935c2d7 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,20 +3,20 @@ //! ISO 8601 calendar date without timezone. +use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; -use div::div_mod_floor; +use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Item, Numeric, Pad}; -use naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use {Datelike, Weekday}; +use crate::format::DelayedFormat; +use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{Item, Numeric, Pad}; +use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; +use crate::{Datelike, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -1906,9 +1906,9 @@ mod tests { use super::NaiveDate; use super::{MAX_DATE, MAX_DAYS_FROM_YEAR_0, MAX_YEAR}; use super::{MIN_DATE, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; - use oldtime::Duration; + use crate::oldtime::Duration; + use crate::{Datelike, Weekday}; use std::{i32, u32}; - use {Datelike, Weekday}; #[test] fn test_date_from_ymd() { diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 213de89a16..806fbcd3b0 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -3,22 +3,22 @@ //! ISO 8601 date and time without timezone. +use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; -use div::div_mod_floor; +use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use naive::date::{MAX_DATE, MIN_DATE}; -use naive::time::{MAX_TIME, MIN_TIME}; -use naive::{IsoWeek, NaiveDate, NaiveTime}; -use {Datelike, Timelike, Weekday}; +use crate::format::DelayedFormat; +use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::naive::date::{MAX_DATE, MIN_DATE}; +use crate::naive::time::{MAX_TIME, MIN_TIME}; +use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; +use crate::{Datelike, Timelike, Weekday}; /// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` /// will always overflow the addition with any date and time type. @@ -1401,7 +1401,7 @@ impl Sub for NaiveDateTime { } /// The `Debug` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](::format::strftime). +/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -1434,7 +1434,7 @@ impl fmt::Debug for NaiveDateTime { } /// The `Display` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](::format::strftime). +/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](crate::format::strftime). /// /// It should be noted that, for leap seconds not on the minute boundary, /// it may print a representation not distinguishable from non-leap seconds. @@ -1465,7 +1465,7 @@ impl fmt::Display for NaiveDateTime { } /// Parsing a `str` into a `NaiveDateTime` uses the same format, -/// [`%Y-%m-%dT%H:%M:%S%.f`](::format::strftime), as in `Debug`. +/// [`%Y-%m-%dT%H:%M:%S%.f`](crate::format::strftime), as in `Debug`. /// /// # Example /// @@ -1810,7 +1810,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {ne_timestamp, NaiveDateTime}; + use crate::{ne_timestamp, NaiveDateTime}; /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch /// @@ -1962,7 +1962,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {ne_timestamp, NaiveDateTime}; + use crate::{ne_timestamp, NaiveDateTime}; /// Serialize a UTC datetime into an integer number of milliseconds since the epoch /// @@ -2111,7 +2111,7 @@ pub mod serde { use core::fmt; use serdelib::{de, ser}; - use {ne_timestamp, NaiveDateTime}; + use crate::{ne_timestamp, NaiveDateTime}; /// Serialize a UTC datetime into an integer number of seconds since the epoch /// @@ -2240,7 +2240,7 @@ pub mod serde { #[test] fn test_serde_bincode() { use self::bincode::{deserialize, serialize, Infinite}; - use naive::NaiveDate; + use crate::naive::NaiveDate; let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); let encoded = serialize(&dt, Infinite).unwrap(); @@ -2252,8 +2252,8 @@ pub mod serde { fn test_serde_bincode_optional() { use self::bincode::{deserialize, serialize, Infinite}; use self::serde_derive::{Deserialize, Serialize}; - use prelude::*; - use serde::ts_nanoseconds_option; + use crate::prelude::*; + use crate::serde::ts_nanoseconds_option; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct Test { @@ -2273,10 +2273,10 @@ pub mod serde { #[cfg(test)] mod tests { use super::NaiveDateTime; - use naive::{NaiveDate, MAX_DATE, MIN_DATE}; - use oldtime::Duration; + use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE}; + use crate::oldtime::Duration; + use crate::Datelike; use std::i64; - use Datelike; #[test] fn test_datetime_from_timestamp() { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 4d7c1b3bf1..de4045f92f 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -16,10 +16,10 @@ #![allow(dead_code)] // some internal methods have been left for consistency #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] +use crate::div::{div_rem, mod_floor}; +use crate::Weekday; use core::{fmt, i32}; -use div::{div_rem, mod_floor}; use num_traits::FromPrimitive; -use Weekday; /// The internal date representation. This also includes the packed `Mdf` value. pub type DateImpl = i32; @@ -490,8 +490,8 @@ mod tests { use self::num_iter::range_inclusive; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; + use crate::Weekday; use std::u32; - use Weekday; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 7e34313bad..5796416309 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -142,8 +142,8 @@ impl fmt::Debug for IsoWeek { #[cfg(test)] mod tests { - use naive::{internals, MAX_DATE, MIN_DATE}; - use Datelike; + use crate::naive::{internals, MAX_DATE, MIN_DATE}; + use crate::Datelike; #[test] fn test_iso_week_extremes() { diff --git a/src/naive/time.rs b/src/naive/time.rs index 43f4dd25af..d71423f56c 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -3,18 +3,18 @@ //! ISO 8601 time without timezone. +use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; -use div::div_mod_floor; +use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use Timelike; +use crate::format::DelayedFormat; +use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::Timelike; pub const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; pub const MAX_TIME: NaiveTime = NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; @@ -1541,9 +1541,9 @@ mod serde { #[cfg(test)] mod tests { use super::NaiveTime; - use oldtime::Duration; + use crate::oldtime::Duration; + use crate::Timelike; use std::u32; - use Timelike; #[test] fn test_time_from_hms_milli() { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 441d0f63c1..b29dcff30f 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -3,15 +3,15 @@ //! The time zone which has a fixed offset from UTC. +use crate::oldtime::Duration as OldDuration; use core::fmt; use core::ops::{Add, Sub}; -use oldtime::Duration as OldDuration; use super::{LocalResult, Offset, TimeZone}; -use div::div_mod_floor; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use DateTime; -use Timelike; +use crate::div::div_mod_floor; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::DateTime; +use crate::Timelike; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// @@ -218,7 +218,7 @@ impl Sub for DateTime { #[cfg(test)] mod tests { use super::FixedOffset; - use offset::TimeZone; + use crate::offset::TimeZone; #[test] fn test_date_extreme_offset() { diff --git a/src/offset/local.rs b/src/offset/local.rs index 8ee5848b5e..a99b20901b 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -4,16 +4,16 @@ //! The local (system) time zone. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use sys::{self, Timespec}; +use crate::sys::{self, Timespec}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use naive::NaiveTime; -use naive::{NaiveDate, NaiveDateTime}; -use {Date, DateTime}; +use crate::naive::NaiveTime; +use crate::naive::{NaiveDate, NaiveDateTime}; +use crate::{Date, DateTime}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use {Datelike, Timelike}; +use crate::{Datelike, Timelike}; /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. @@ -196,8 +196,8 @@ impl TimeZone for Local { #[cfg(test)] mod tests { use super::Local; - use offset::TimeZone; - use Datelike; + use crate::offset::TimeZone; + use crate::Datelike; #[test] fn test_local_date_sanity_check() { diff --git a/src/offset/mod.rs b/src/offset/mod.rs index f317a72289..cc04bc568e 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -20,10 +20,10 @@ use core::fmt; -use format::{parse, ParseResult, Parsed, StrftimeItems}; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use Weekday; -use {Date, DateTime}; +use crate::format::{parse, ParseResult, Parsed, StrftimeItems}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::Weekday; +use crate::{Date, DateTime}; /// The conversion result from the local time to the timezone-aware datetime types. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] @@ -406,7 +406,7 @@ pub trait TimeZone: Sized + Clone { /// Parses a string with the specified format string and returns a /// `DateTime` with the current offset. /// - /// See the [`::format::strftime`] module on the + /// See the [`crate::format::strftime`] module on the /// supported escape sequences. /// /// If the to-be-parsed string includes an offset, it *must* match the diff --git a/src/offset/utc.rs b/src/offset/utc.rs index aff86073d2..8099ff6a71 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -6,14 +6,14 @@ use core::fmt; use super::{FixedOffset, LocalResult, Offset, TimeZone}; -use naive::{NaiveDate, NaiveDateTime}; +use crate::naive::{NaiveDate, NaiveDateTime}; +#[cfg(feature = "clock")] +use crate::{Date, DateTime}; #[cfg(all( feature = "clock", not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) ))] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "clock")] -use {Date, DateTime}; /// The UTC time zone. This is the most efficient time zone when you don't need the local time. /// It is also used as an offset (which is also a dummy type). diff --git a/src/round.rs b/src/round.rs index a8eb60d0b4..b357d916c0 100644 --- a/src/round.rs +++ b/src/round.rs @@ -1,17 +1,15 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. +use crate::datetime::DateTime; +use crate::naive::NaiveDateTime; +use crate::oldtime::Duration; +use crate::TimeZone; +use crate::Timelike; use core::cmp::Ordering; use core::fmt; use core::marker::Sized; use core::ops::{Add, Sub}; -use datetime::DateTime; -use naive::NaiveDateTime; -use oldtime::Duration; -#[cfg(any(feature = "std", test))] -use std; -use TimeZone; -use Timelike; /// Extension trait for subsecond rounding or truncation to a maximum number /// of digits. Rounding can be used to decrease the error variance when @@ -303,8 +301,8 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { use super::{Duration, DurationRound, SubsecRound}; - use offset::{FixedOffset, TimeZone, Utc}; - use Timelike; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::Timelike; #[test] fn test_round_subsecs() { From aaf9381d6abaf6c4c219ccb15b9b763b0aebf31b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 19 Mar 2022 19:22:14 -0700 Subject: [PATCH 002/999] Make criterion an optional dependency Unfortunately dev-dependencies are not allowed to be optional. --- Cargo.toml | 6 +++--- benches/chrono.rs | 1 + benches/serde.rs | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e39c900672..2f248e5568 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ clock = ["libc", "std", "winapi"] oldtime = ["time"] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] -__internal_bench = [] +__internal_bench = ["criterion"] __doctest = [] [dependencies] @@ -43,6 +43,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } +criterion = { version = "0.3", optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } @@ -56,7 +57,6 @@ serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } bincode = { version = "0.8.0" } num-iter = { version = "0.1.35", default-features = false } -criterion = { version = "0.3" } doc-comment = { version = "0.3" } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] @@ -75,5 +75,5 @@ harness = false [[bench]] name = "serde" +required-features = ["__internal_bench", "serde"] harness = false -required-features = ["serde"] diff --git a/benches/chrono.rs b/benches/chrono.rs index 1c640634ac..01b9caed4f 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -1,4 +1,5 @@ //! Benchmarks for chrono that just depend on std +#![cfg(feature = "__internal_bench")] extern crate chrono; extern crate criterion; diff --git a/benches/serde.rs b/benches/serde.rs index 860b06e1ae..b7136efbfb 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "__internal_bench")] + extern crate chrono; extern crate criterion; From 1004eebaee8982e4afb7ea0e3e97356d39fe6b01 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 19 Mar 2022 20:01:30 -0700 Subject: [PATCH 003/999] Remove authors from Cargo metadata (see RFC 3052) --- Cargo.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f248e5568..03ad3eb263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,6 @@ [package] name = "chrono" version = "0.4.19" -authors = [ - "Kang Seonghoon ", - "Brandon W Maister ", -] - description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 9a5f76c9bb70647d1ab4abd88872e395a7c06c79 Mon Sep 17 00:00:00 2001 From: AR Baart <643200+ARBaart@users.noreply.github.com> Date: Mon, 21 Mar 2022 21:44:49 +0100 Subject: [PATCH 004/999] Fix issue #658 duration_round by zero panics (#659) --- CHANGELOG.md | 1 + src/round.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cfecebadd..fd8facd028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add `DateTime::from_local()` to construct from given local date and time (#572) * Correct build for wasm32-unknown-emscripten target (#568) * Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) +* Fix `duration_round` panic on rounding by `Duration::zero()` (#658) ## 0.4.19 diff --git a/src/round.rs b/src/round.rs index b357d916c0..c10b78c760 100644 --- a/src/round.rs +++ b/src/round.rs @@ -185,6 +185,9 @@ where if span > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } + if span == 0 { + return Ok(original); + } let delta_down = stamp % span; if delta_down == 0 { Ok(original) @@ -394,6 +397,11 @@ mod tests { fn test_duration_round() { let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + assert_eq!( + dt.duration_round(Duration::zero()).unwrap().to_string(), + "2016-12-31 23:59:59.175500 UTC" + ); + assert_eq!( dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" @@ -456,6 +464,11 @@ mod tests { fn test_duration_round_naive() { let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000).naive_utc(); + assert_eq!( + dt.duration_round(Duration::zero()).unwrap().to_string(), + "2016-12-31 23:59:59.175500" + ); + assert_eq!( dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180" From 9620c8bf9999d266ec0444e5b0d2687da969fb71 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:01:48 -0700 Subject: [PATCH 005/999] Use precise feature guards for WASM tests --- tests/wasm.rs | 134 ++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/tests/wasm.rs b/tests/wasm.rs index ab1d9e717d..7e7433adbe 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,81 +1,77 @@ -#[cfg(all(test, feature = "wasmbind"))] -mod test { - extern crate chrono; - extern crate wasm_bindgen_test; +#![cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] - use self::chrono::prelude::*; - use self::wasm_bindgen_test::*; +use self::chrono::prelude::*; +use self::wasm_bindgen_test::*; - #[wasm_bindgen_test] - fn now() { - let utc: DateTime = Utc::now(); - let local: DateTime = Local::now(); +#[wasm_bindgen_test] +fn now() { + let utc: DateTime = Utc::now(); + let local: DateTime = Local::now(); - // Ensure time set by the test script is correct - let now = env!("NOW"); - let actual = Utc.datetime_from_str(&now, "%s").unwrap(); - let diff = utc - actual; - assert!( - diff < chrono::Duration::minutes(5), - "expected {} - {} == {} < 5m (env var: {})", - utc, - actual, - diff, - now, - ); + // Ensure time set by the test script is correct + let now = env!("NOW"); + let actual = Utc.datetime_from_str(&now, "%s").unwrap(); + let diff = utc - actual; + assert!( + diff < chrono::Duration::minutes(5), + "expected {} - {} == {} < 5m (env var: {})", + utc, + actual, + diff, + now, + ); - let tz = env!("TZ"); - eprintln!("testing with tz={}", tz); + let tz = env!("TZ"); + eprintln!("testing with tz={}", tz); - // Ensure offset retrieved when getting local time is correct - let expected_offset = match tz { - "ACST-9:30" => FixedOffset::east(19 * 30 * 60), - "Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully - "EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60), - "EST" | "-0500" => FixedOffset::east(-5 * 60 * 60), - "UTC0" | "+0000" => FixedOffset::east(0), - tz => panic!("unexpected TZ {}", tz), - }; - assert_eq!( - &expected_offset, - local.offset(), - "expected: {:?} local: {:?}", - expected_offset, - local.offset(), - ); - } + // Ensure offset retrieved when getting local time is correct + let expected_offset = match tz { + "ACST-9:30" => FixedOffset::east(19 * 30 * 60), + "Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully + "EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60), + "EST" | "-0500" => FixedOffset::east(-5 * 60 * 60), + "UTC0" | "+0000" => FixedOffset::east(0), + tz => panic!("unexpected TZ {}", tz), + }; + assert_eq!( + &expected_offset, + local.offset(), + "expected: {:?} local: {:?}", + expected_offset, + local.offset(), + ); +} - #[wasm_bindgen_test] - fn from_is_exact() { - let now = js_sys::Date::new_0(); +#[wasm_bindgen_test] +fn from_is_exact() { + let now = js_sys::Date::new_0(); - let dt = DateTime::::from(now.clone()); + let dt = DateTime::::from(now.clone()); - assert_eq!(now.get_time() as i64, dt.timestamp_millis()); - } + assert_eq!(now.get_time() as i64, dt.timestamp_millis()); +} - #[wasm_bindgen_test] - fn local_from_local_datetime() { - let now = Local::now(); - let ndt = now.naive_local(); - let res = match Local.from_local_datetime(&ndt).single() { - Some(v) => v, - None => panic! {"Required for test!"}, - }; - assert_eq!(now, res); - } +#[wasm_bindgen_test] +fn local_from_local_datetime() { + let now = Local::now(); + let ndt = now.naive_local(); + let res = match Local.from_local_datetime(&ndt).single() { + Some(v) => v, + None => panic! {"Required for test!"}, + }; + assert_eq!(now, res); +} - #[wasm_bindgen_test] - fn convert_all_parts_with_milliseconds() { - let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); - let js_date = js_sys::Date::from(time); +#[wasm_bindgen_test] +fn convert_all_parts_with_milliseconds() { + let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); + 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_date(), 1); - assert_eq!(js_date.get_utc_hours(), 3); - assert_eq!(js_date.get_utc_minutes(), 1); - assert_eq!(js_date.get_utc_seconds(), 55); - assert_eq!(js_date.get_utc_milliseconds(), 974); - } + assert_eq!(js_date.get_utc_full_year(), 2020); + assert_eq!(js_date.get_utc_month(), 12); + assert_eq!(js_date.get_utc_date(), 1); + assert_eq!(js_date.get_utc_hours(), 3); + assert_eq!(js_date.get_utc_minutes(), 1); + assert_eq!(js_date.get_utc_seconds(), 55); + assert_eq!(js_date.get_utc_milliseconds(), 974); } From 68bfd8baaa89cfb5c61956a6dcc0c29dea2a62a7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:06:54 -0700 Subject: [PATCH 006/999] Move Weekday into a separate module --- src/lib.rs | 303 +------------------------------------------------ src/weekday.rs | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+), 299 deletions(-) create mode 100644 src/weekday.rs diff --git a/src/lib.rs b/src/lib.rs index 82b6622f7c..6807305da1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -495,6 +495,8 @@ extern crate wasm_bindgen; #[cfg_attr(feature = "__doctest", cfg(doctest))] doctest!("../README.md"); +use core::fmt; + // this reexport is to aid the transition and should not be in the prelude! pub use oldtime::Duration; @@ -649,305 +651,8 @@ impl fmt::Display for SerdeError { } } -/// The day of week. -/// -/// The order of the days of week depends on the context. -/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) -/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Weekday { - /// Monday. - Mon = 0, - /// Tuesday. - Tue = 1, - /// Wednesday. - Wed = 2, - /// Thursday. - Thu = 3, - /// Friday. - Fri = 4, - /// Saturday. - Sat = 5, - /// Sunday. - Sun = 6, -} - -impl Weekday { - /// The next day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` - #[inline] - pub fn succ(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Tue, - Weekday::Tue => Weekday::Wed, - Weekday::Wed => Weekday::Thu, - Weekday::Thu => Weekday::Fri, - Weekday::Fri => Weekday::Sat, - Weekday::Sat => Weekday::Sun, - Weekday::Sun => Weekday::Mon, - } - } - - /// The previous day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` - #[inline] - pub fn pred(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Sun, - Weekday::Tue => Weekday::Mon, - Weekday::Wed => Weekday::Tue, - Weekday::Thu => Weekday::Wed, - Weekday::Fri => Weekday::Thu, - Weekday::Sat => Weekday::Fri, - Weekday::Sun => Weekday::Sat, - } - } - - /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 - #[inline] - pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } - } - - /// Returns a day-of-week number starting from Sunday = 1. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 - #[inline] - pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } - } - - /// Returns a day-of-week number starting from Monday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 - #[inline] - pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } - } - - /// Returns a day-of-week number starting from Sunday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 - #[inline] - pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } - } -} - -impl fmt::Display for Weekday { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Weekday::Mon => "Mon", - Weekday::Tue => "Tue", - Weekday::Wed => "Wed", - Weekday::Thu => "Thu", - Weekday::Fri => "Fri", - Weekday::Sat => "Sat", - Weekday::Sun => "Sun", - }) - } -} - -/// Any weekday can be represented as an integer from 0 to 6, which equals to -/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. -/// Do not heavily depend on this though; use explicit methods whenever possible. -impl num_traits::FromPrimitive for Weekday { - #[inline] - fn from_i64(n: i64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } - } - - #[inline] - fn from_u64(n: u64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } - } -} - -use core::fmt; - -/// An error resulting from reading `Weekday` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseWeekdayError { - _dummy: (), -} - -impl fmt::Debug for ParseWeekdayError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseWeekdayError {{ .. }}") - } -} - -// the actual `FromStr` implementation is in the `format` module to leverage the existing code - -#[cfg(feature = "serde")] -mod weekday_serde { - use super::Weekday; - use core::fmt; - use serdelib::{de, ser}; - - impl ser::Serialize for Weekday { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct WeekdayVisitor; - - impl<'de> de::Visitor<'de> for WeekdayVisitor { - type Value = Weekday; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Weekday") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short or long weekday names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Weekday { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(WeekdayVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Weekday::*; - - let cases: Vec<(Weekday, &str)> = vec![ - (Mon, "\"Mon\""), - (Tue, "\"Tue\""), - (Wed, "\"Wed\""), - (Thu, "\"Thu\""), - (Fri, "\"Fri\""), - (Sat, "\"Sat\""), - (Sun, "\"Sun\""), - ]; - - for (weekday, expected_str) in cases { - let string = to_string(&weekday).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Weekday::*; - - let cases: Vec<(&str, Weekday)> = vec![ - ("\"mon\"", Mon), - ("\"MONDAY\"", Mon), - ("\"MonDay\"", Mon), - ("\"mOn\"", Mon), - ("\"tue\"", Tue), - ("\"tuesday\"", Tue), - ("\"wed\"", Wed), - ("\"wednesday\"", Wed), - ("\"thu\"", Thu), - ("\"thursday\"", Thu), - ("\"fri\"", Fri), - ("\"friday\"", Fri), - ("\"sat\"", Sat), - ("\"saturday\"", Sat), - ("\"sun\"", Sun), - ("\"sunday\"", Sun), - ]; - - for (str, expected_weekday) in cases { - let weekday = from_str::(str).unwrap(); - assert_eq!(weekday, expected_weekday); - } - - let errors: Vec<&str> = - vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; - - for str in errors { - from_str::(str).unwrap_err(); - } - } -} +mod weekday; +pub use weekday::{Weekday, ParseWeekdayError}; /// The month of the year. /// diff --git a/src/weekday.rs b/src/weekday.rs new file mode 100644 index 0000000000..c719ce89b5 --- /dev/null +++ b/src/weekday.rs @@ -0,0 +1,299 @@ +use core::fmt; + +/// The day of week. +/// +/// The order of the days of week depends on the context. +/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) +/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +pub enum Weekday { + /// Monday. + Mon = 0, + /// Tuesday. + Tue = 1, + /// Wednesday. + Wed = 2, + /// Thursday. + Thu = 3, + /// Friday. + Fri = 4, + /// Saturday. + Sat = 5, + /// Sunday. + Sun = 6, +} + +impl Weekday { + /// The next day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` + #[inline] + pub fn succ(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Tue, + Weekday::Tue => Weekday::Wed, + Weekday::Wed => Weekday::Thu, + Weekday::Thu => Weekday::Fri, + Weekday::Fri => Weekday::Sat, + Weekday::Sat => Weekday::Sun, + Weekday::Sun => Weekday::Mon, + } + } + + /// The previous day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` + #[inline] + pub fn pred(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Sun, + Weekday::Tue => Weekday::Mon, + Weekday::Wed => Weekday::Tue, + Weekday::Thu => Weekday::Wed, + Weekday::Fri => Weekday::Thu, + Weekday::Sat => Weekday::Fri, + Weekday::Sun => Weekday::Sat, + } + } + + /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 + #[inline] + pub fn number_from_monday(&self) -> u32 { + match *self { + Weekday::Mon => 1, + Weekday::Tue => 2, + Weekday::Wed => 3, + Weekday::Thu => 4, + Weekday::Fri => 5, + Weekday::Sat => 6, + Weekday::Sun => 7, + } + } + + /// Returns a day-of-week number starting from Sunday = 1. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 + #[inline] + pub fn number_from_sunday(&self) -> u32 { + match *self { + Weekday::Mon => 2, + Weekday::Tue => 3, + Weekday::Wed => 4, + Weekday::Thu => 5, + Weekday::Fri => 6, + Weekday::Sat => 7, + Weekday::Sun => 1, + } + } + + /// Returns a day-of-week number starting from Monday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + #[inline] + pub fn num_days_from_monday(&self) -> u32 { + match *self { + Weekday::Mon => 0, + Weekday::Tue => 1, + Weekday::Wed => 2, + Weekday::Thu => 3, + Weekday::Fri => 4, + Weekday::Sat => 5, + Weekday::Sun => 6, + } + } + + /// Returns a day-of-week number starting from Sunday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 + #[inline] + pub fn num_days_from_sunday(&self) -> u32 { + match *self { + Weekday::Mon => 1, + Weekday::Tue => 2, + Weekday::Wed => 3, + Weekday::Thu => 4, + Weekday::Fri => 5, + Weekday::Sat => 6, + Weekday::Sun => 0, + } + } +} + +impl fmt::Display for Weekday { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Weekday::Mon => "Mon", + Weekday::Tue => "Tue", + Weekday::Wed => "Wed", + Weekday::Thu => "Thu", + Weekday::Fri => "Fri", + Weekday::Sat => "Sat", + Weekday::Sun => "Sun", + }) + } +} + +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl num_traits::FromPrimitive for Weekday { + #[inline] + fn from_i64(n: i64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } + + #[inline] + fn from_u64(n: u64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } +} + +/// An error resulting from reading `Weekday` value with `FromStr`. +#[derive(Clone, PartialEq)] +pub struct ParseWeekdayError { + pub(crate) _dummy: (), +} + +impl fmt::Debug for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseWeekdayError {{ .. }}") + } +} + +// the actual `FromStr` implementation is in the `format` module to leverage the existing code + +#[cfg(feature = "serde")] +mod weekday_serde { + use super::Weekday; + use core::fmt; + use serdelib::{de, ser}; + + impl ser::Serialize for Weekday { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } + } + + struct WeekdayVisitor; + + impl<'de> de::Visitor<'de> for WeekdayVisitor { + type Value = Weekday; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Weekday") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short or long weekday names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Weekday { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(WeekdayVisitor) + } + } + + #[cfg(test)] + extern crate serde_json; + + #[test] + fn test_serde_serialize() { + use self::serde_json::to_string; + use Weekday::*; + + let cases: Vec<(Weekday, &str)> = vec![ + (Mon, "\"Mon\""), + (Tue, "\"Tue\""), + (Wed, "\"Wed\""), + (Thu, "\"Thu\""), + (Fri, "\"Fri\""), + (Sat, "\"Sat\""), + (Sun, "\"Sun\""), + ]; + + for (weekday, expected_str) in cases { + let string = to_string(&weekday).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + fn test_serde_deserialize() { + use self::serde_json::from_str; + use Weekday::*; + + let cases: Vec<(&str, Weekday)> = vec![ + ("\"mon\"", Mon), + ("\"MONDAY\"", Mon), + ("\"MonDay\"", Mon), + ("\"mOn\"", Mon), + ("\"tue\"", Tue), + ("\"tuesday\"", Tue), + ("\"wed\"", Wed), + ("\"wednesday\"", Wed), + ("\"thu\"", Thu), + ("\"thursday\"", Thu), + ("\"fri\"", Fri), + ("\"friday\"", Fri), + ("\"sat\"", Sat), + ("\"saturday\"", Sat), + ("\"sun\"", Sun), + ("\"sunday\"", Sun), + ]; + + for (str, expected_weekday) in cases { + let weekday = from_str::(str).unwrap(); + assert_eq!(weekday, expected_weekday); + } + + let errors: Vec<&str> = + vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; + + for str in errors { + from_str::(str).unwrap_err(); + } + } +} From 2558a93d1a51977c03a39e960599395abae19f22 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:17:28 -0700 Subject: [PATCH 007/999] Enable warning on unreachable_pub --- src/datetime.rs | 4 +- src/div.rs | 2 +- src/format/scan.rs | 30 ++++++------ src/lib.rs | 3 +- src/naive/datetime.rs | 4 +- src/naive/internals.rs | 109 +++++++++++++++++++++-------------------- src/naive/isoweek.rs | 2 +- src/naive/time.rs | 5 +- src/sys.rs | 36 +++++++------- src/sys/unix.rs | 6 +-- src/sys/windows.rs | 6 +-- 11 files changed, 106 insertions(+), 101 deletions(-) diff --git a/src/datetime.rs b/src/datetime.rs index 1ed7c2955c..0210a7ce97 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1082,7 +1082,7 @@ fn test_decodable_json_timestamps( } #[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { +pub(super) mod rustc_serialize { use super::DateTime; #[cfg(feature = "clock")] use crate::offset::Local; @@ -1209,7 +1209,7 @@ pub mod rustc_serialize { /// documented at re-export site #[cfg(feature = "serde")] -pub mod serde { +pub(super) mod serde { use super::DateTime; #[cfg(feature = "clock")] use crate::offset::Local; diff --git a/src/div.rs b/src/div.rs index 64b8e4bce4..3dc2b41703 100644 --- a/src/div.rs +++ b/src/div.rs @@ -7,7 +7,7 @@ // Algorithm from [Daan Leijen. _Division and Modulus for Computer Scientists_, // December 2001](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf) -pub use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor}; +pub(super) use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor}; #[cfg(test)] mod tests { diff --git a/src/format/scan.rs b/src/format/scan.rs index 70034a7684..bc7e8314a3 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -34,7 +34,7 @@ fn equals(s: &str, pattern: &str) -> bool { /// More than `max` digits are consumed up to the first `max` digits. /// Any number that does not fit in `i64` is an error. #[inline] -pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { +pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { assert!(min <= max); // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on @@ -67,7 +67,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { /// Tries to consume at least one digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let origlen = s.len(); let (s, v) = number(s, 1, 9)?; @@ -86,7 +86,7 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { /// Tries to consume a fixed number of digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let (s, v) = number(s, digits, digits)?; @@ -99,7 +99,7 @@ pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { } /// Tries to parse the month index (0 through 11) with the first three ASCII letters. -pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -123,7 +123,7 @@ pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { } /// Tries to parse the weekday with the first three ASCII letters. -pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -143,7 +143,7 @@ pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// Tries to parse the month index (0 through 11) with short or long month names. /// It prefers long month names to short month names when both are possible. -pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars static LONG_MONTH_SUFFIXES: [&'static str; 12] = ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; @@ -161,7 +161,7 @@ pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// Tries to parse the weekday with short or long weekday names. /// It prefers long weekday names to short weekday names when both are possible. -pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] = ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; @@ -178,7 +178,7 @@ pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { } /// Tries to consume exactly one given character. -pub fn char(s: &str, c1: u8) -> ParseResult<&str> { +pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { match s.as_bytes().first() { Some(&c) if c == c1 => Ok(&s[1..]), Some(_) => Err(INVALID), @@ -187,7 +187,7 @@ pub fn char(s: &str, c1: u8) -> ParseResult<&str> { } /// Tries to consume one or more whitespace. -pub fn space(s: &str) -> ParseResult<&str> { +pub(super) fn space(s: &str) -> ParseResult<&str> { let s_ = s.trim_left(); if s_.len() < s.len() { Ok(s_) @@ -199,7 +199,7 @@ pub fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub fn colon_or_space(s: &str) -> ParseResult<&str> { +pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) } @@ -207,7 +207,7 @@ pub fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -272,7 +272,7 @@ where } /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -296,7 +296,7 @@ where /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as /// `+00:00`, and allows missing minutes entirely. -pub fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -308,7 +308,7 @@ where /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). -pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { // tries to parse legacy time zone names let upto = s .as_bytes() @@ -345,6 +345,6 @@ pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { /// Tries to consume everyting until next whitespace-like symbol. /// Does not provide any offset information from the consumed data. -pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { +pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) } diff --git a/src/lib.rs b/src/lib.rs index 6807305da1..a9715bd3cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -422,6 +422,7 @@ #![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 #![deny(missing_docs)] #![deny(missing_debug_implementations)] +#![warn(unreachable_pub)] #![deny(dead_code)] // lints are added all the time, we test on 1.13 #![allow(unknown_lints)] @@ -652,7 +653,7 @@ impl fmt::Display for SerdeError { } mod weekday; -pub use weekday::{Weekday, ParseWeekdayError}; +pub use weekday::{ParseWeekdayError, Weekday}; /// The month of the year. /// diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 806fbcd3b0..a247ba74dc 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -1641,7 +1641,7 @@ where } #[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { +pub(super) mod rustc_serialize { use super::NaiveDateTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use std::ops::Deref; @@ -1717,7 +1717,7 @@ pub mod rustc_serialize { /// Tools to help serializing/deserializing `NaiveDateTime`s #[cfg(feature = "serde")] -pub mod serde { +pub(super) mod serde { use super::NaiveDateTime; use core::fmt; use serdelib::{de, ser}; diff --git a/src/naive/internals.rs b/src/naive/internals.rs index de4045f92f..733f3eac8d 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -22,10 +22,10 @@ use core::{fmt, i32}; use num_traits::FromPrimitive; /// The internal date representation. This also includes the packed `Mdf` value. -pub type DateImpl = i32; +pub(super) type DateImpl = i32; -pub const MAX_YEAR: DateImpl = i32::MAX >> 13; -pub const MIN_YEAR: DateImpl = i32::MIN >> 13; +pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13; +pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; /// The year flags (aka the dominical letter). /// @@ -35,23 +35,24 @@ pub const MIN_YEAR: DateImpl = i32::MIN >> 13; /// where `a` is `1` for the common year (simplifies the `Of` validation) /// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year /// (simplifies the day of week calculation from the 1-based ordinal). +#[allow(unreachable_pub)] // public as an alias for benchmarks only #[derive(PartialEq, Eq, Copy, Clone)] -pub struct YearFlags(pub u8); - -pub const A: YearFlags = YearFlags(0o15); -pub const AG: YearFlags = YearFlags(0o05); -pub const B: YearFlags = YearFlags(0o14); -pub const BA: YearFlags = YearFlags(0o04); -pub const C: YearFlags = YearFlags(0o13); -pub const CB: YearFlags = YearFlags(0o03); -pub const D: YearFlags = YearFlags(0o12); -pub const DC: YearFlags = YearFlags(0o02); -pub const E: YearFlags = YearFlags(0o11); -pub const ED: YearFlags = YearFlags(0o01); -pub const F: YearFlags = YearFlags(0o17); -pub const FE: YearFlags = YearFlags(0o07); -pub const G: YearFlags = YearFlags(0o16); -pub const GF: YearFlags = YearFlags(0o06); +pub struct YearFlags(pub(super) u8); + +pub(super) const A: YearFlags = YearFlags(0o15); +pub(super) const AG: YearFlags = YearFlags(0o05); +pub(super) const B: YearFlags = YearFlags(0o14); +pub(super) const BA: YearFlags = YearFlags(0o04); +pub(super) const C: YearFlags = YearFlags(0o13); +pub(super) const CB: YearFlags = YearFlags(0o03); +pub(super) const D: YearFlags = YearFlags(0o12); +pub(super) const DC: YearFlags = YearFlags(0o02); +pub(super) const E: YearFlags = YearFlags(0o11); +pub(super) const ED: YearFlags = YearFlags(0o01); +pub(super) const F: YearFlags = YearFlags(0o17); +pub(super) const FE: YearFlags = YearFlags(0o07); +pub(super) const G: YearFlags = YearFlags(0o16); +pub(super) const GF: YearFlags = YearFlags(0o06); static YEAR_TO_FLAGS: [YearFlags; 400] = [ BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, @@ -94,7 +95,7 @@ static YEAR_DELTAS: [u8; 401] = [ 96, 97, 97, 97, 97, // 400+1 ]; -pub fn cycle_to_yo(cycle: u32) -> (u32, u32) { +pub(super) fn cycle_to_yo(cycle: u32) -> (u32, u32) { let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365); let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]); if ordinal0 < delta { @@ -106,11 +107,13 @@ pub fn cycle_to_yo(cycle: u32) -> (u32, u32) { (year_mod_400, ordinal0 + 1) } -pub fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { +pub(super) fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1 } impl YearFlags { + #[allow(unreachable_pub)] // public as an alias for benchmarks only + #[doc(hidden)] // for benchmarks only #[inline] pub fn from_year(year: i32) -> YearFlags { let year = mod_floor(year, 400); @@ -118,18 +121,18 @@ impl YearFlags { } #[inline] - pub fn from_year_mod_400(year: i32) -> YearFlags { + pub(super) fn from_year_mod_400(year: i32) -> YearFlags { YEAR_TO_FLAGS[year as usize] } #[inline] - pub fn ndays(&self) -> u32 { + pub(super) fn ndays(&self) -> u32 { let YearFlags(flags) = *self; 366 - u32::from(flags >> 3) } #[inline] - pub fn isoweek_delta(&self) -> u32 { + pub(super) fn isoweek_delta(&self) -> u32 { let YearFlags(flags) = *self; let mut delta = u32::from(flags) & 0b0111; if delta < 3 { @@ -139,7 +142,7 @@ impl YearFlags { } #[inline] - pub fn nisoweeks(&self) -> u32 { + pub(super) fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } @@ -170,10 +173,10 @@ impl fmt::Debug for YearFlags { } } -pub const MIN_OL: u32 = 1 << 1; -pub const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` -pub const MIN_MDL: u32 = (1 << 6) | (1 << 1); -pub const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; +pub(super) const MIN_OL: u32 = 1 << 1; +pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` +pub(super) const MIN_MDL: u32 = (1 << 6) | (1 << 1); +pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; const XX: i8 = -128; static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ @@ -265,7 +268,7 @@ static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ /// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), /// which is an index to the `OL_TO_MDL` lookup table. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Of(pub u32); +pub(super) struct Of(pub(crate) u32); impl Of { #[inline] @@ -278,13 +281,13 @@ impl Of { } #[inline] - pub fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of { + pub(super) fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of { let ordinal = Of::clamp_ordinal(ordinal); Of((ordinal << 4) | u32::from(flags)) } #[inline] - pub fn from_mdf(Mdf(mdf): Mdf) -> Of { + pub(super) fn from_mdf(Mdf(mdf): Mdf) -> Of { let mdl = mdf >> 3; match MDL_TO_OL.get(mdl as usize) { Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)), @@ -293,45 +296,45 @@ impl Of { } #[inline] - pub fn valid(&self) -> bool { + pub(super) fn valid(&self) -> bool { let Of(of) = *self; let ol = of >> 3; MIN_OL <= ol && ol <= MAX_OL } #[inline] - pub fn ordinal(&self) -> u32 { + pub(super) fn ordinal(&self) -> u32 { let Of(of) = *self; of >> 4 } #[inline] - pub fn with_ordinal(&self, ordinal: u32) -> Of { + pub(super) fn with_ordinal(&self, ordinal: u32) -> Of { let ordinal = Of::clamp_ordinal(ordinal); let Of(of) = *self; Of((of & 0b1111) | (ordinal << 4)) } #[inline] - pub fn flags(&self) -> YearFlags { + pub(super) fn flags(&self) -> YearFlags { let Of(of) = *self; YearFlags((of & 0b1111) as u8) } #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { + pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { let Of(of) = *self; Of((of & !0b1111) | u32::from(flags)) } #[inline] - pub fn weekday(&self) -> Weekday { + pub(super) fn weekday(&self) -> Weekday { let Of(of) = *self; Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap() } #[inline] - pub fn isoweekdate_raw(&self) -> (u32, Weekday) { + pub(super) fn isoweekdate_raw(&self) -> (u32, Weekday) { // week ordinal = ordinal + delta let Of(of) = *self; let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); @@ -340,18 +343,18 @@ impl Of { #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] #[inline] - pub fn to_mdf(&self) -> Mdf { + pub(super) fn to_mdf(&self) -> Mdf { Mdf::from_of(*self) } #[inline] - pub fn succ(&self) -> Of { + pub(super) fn succ(&self) -> Of { let Of(of) = *self; Of(of + (1 << 4)) } #[inline] - pub fn pred(&self) -> Of { + pub(super) fn pred(&self) -> Of { let Of(of) = *self; Of(of - (1 << 4)) } @@ -376,7 +379,7 @@ impl fmt::Debug for Of { /// (month, day of month and leap flag), /// which is an index to the `MDL_TO_OL` lookup table. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Mdf(pub u32); +pub(super) struct Mdf(pub(super) u32); impl Mdf { #[inline] @@ -398,14 +401,14 @@ impl Mdf { } #[inline] - pub fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf { + pub(super) fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf { let month = Mdf::clamp_month(month); let day = Mdf::clamp_day(day); Mdf((month << 9) | (day << 4) | u32::from(flags)) } #[inline] - pub fn from_of(Of(of): Of) -> Mdf { + pub(super) fn from_of(Of(of): Of) -> Mdf { let ol = of >> 3; match OL_TO_MDL.get(ol as usize) { Some(&v) => Mdf(of + (u32::from(v) << 3)), @@ -414,7 +417,7 @@ impl Mdf { } #[inline] - pub fn valid(&self) -> bool { + pub(super) fn valid(&self) -> bool { let Mdf(mdf) = *self; let mdl = mdf >> 3; match MDL_TO_OL.get(mdl as usize) { @@ -424,46 +427,46 @@ impl Mdf { } #[inline] - pub fn month(&self) -> u32 { + pub(super) fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } #[inline] - pub fn with_month(&self, month: u32) -> Mdf { + pub(super) fn with_month(&self, month: u32) -> Mdf { let month = Mdf::clamp_month(month); let Mdf(mdf) = *self; Mdf((mdf & 0b1_1111_1111) | (month << 9)) } #[inline] - pub fn day(&self) -> u32 { + pub(super) fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } #[inline] - pub fn with_day(&self, day: u32) -> Mdf { + pub(super) fn with_day(&self, day: u32) -> Mdf { let day = Mdf::clamp_day(day); let Mdf(mdf) = *self; Mdf((mdf & !0b1_1111_0000) | (day << 4)) } #[inline] - pub fn flags(&self) -> YearFlags { + pub(super) fn flags(&self) -> YearFlags { let Mdf(mdf) = *self; YearFlags((mdf & 0b1111) as u8) } #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { + pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; Mdf((mdf & !0b1111) | u32::from(flags)) } #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] #[inline] - pub fn to_of(&self) -> Of { + pub(super) fn to_of(&self) -> Of { Of::from_mdf(*self) } } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 5796416309..2d98257c44 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -27,7 +27,7 @@ pub struct IsoWeek { // because the year range for the week date and the calendar date do not match and // it is confusing to have a date that is out of range in one and not in another. // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. -pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { +pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { let (rawweek, _) = of.isoweekdate_raw(); let (year, week) = if rawweek < 1 { // previous year diff --git a/src/naive/time.rs b/src/naive/time.rs index d71423f56c..68eefccc2b 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -16,8 +16,9 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::Timelike; -pub const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; -pub const MAX_TIME: NaiveTime = NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; +pub(super) const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; +pub(super) const MAX_TIME: NaiveTime = + NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; /// ISO 8601 time without timezone. /// Allows for the nanosecond precision and optional leap second representation. diff --git a/src/sys.rs b/src/sys.rs index 0fa67838eb..9bb21e49ee 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -33,21 +33,21 @@ mod inner; /// /// For example a timespec of 1.2 seconds after the beginning of the epoch would /// be represented as {sec: 1, nsec: 200000000}. -pub struct Timespec { - pub sec: i64, - pub nsec: i32, +pub(crate) struct Timespec { + pub(crate) sec: i64, + pub(crate) nsec: i32, } impl Timespec { /// Constructs a timespec representing the current time in UTC. - pub fn now() -> Timespec { + pub(crate) fn now() -> Timespec { let st = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } } /// Converts this timespec into the system's local time. - pub fn local(self) -> Tm { + pub(crate) fn local(self) -> Tm { let mut tm = Tm { tm_sec: 0, tm_min: 0, @@ -72,51 +72,51 @@ impl Timespec { // FIXME: use c_int instead of i32? #[cfg(feature = "clock")] #[repr(C)] -pub struct Tm { +pub(crate) struct Tm { /// Seconds after the minute - [0, 60] - pub tm_sec: i32, + pub(crate) tm_sec: i32, /// Minutes after the hour - [0, 59] - pub tm_min: i32, + pub(crate) tm_min: i32, /// Hours after midnight - [0, 23] - pub tm_hour: i32, + pub(crate) tm_hour: i32, /// Day of the month - [1, 31] - pub tm_mday: i32, + pub(crate) tm_mday: i32, /// Months since January - [0, 11] - pub tm_mon: i32, + pub(crate) tm_mon: i32, /// Years since 1900 - pub tm_year: i32, + pub(crate) tm_year: i32, /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - pub tm_wday: i32, + pub(crate) tm_wday: i32, /// Days since January 1 - [0, 365] - pub tm_yday: i32, + pub(crate) tm_yday: i32, /// Daylight Saving Time flag. /// /// This value is positive if Daylight Saving Time is in effect, zero if /// Daylight Saving Time is not in effect, and negative if this information /// is not available. - pub tm_isdst: i32, + pub(crate) tm_isdst: i32, /// Identifies the time zone that was used to compute this broken-down time /// value, including any adjustment for Daylight Saving Time. This is the /// number of seconds east of UTC. For example, for U.S. Pacific Daylight /// Time, the value is `-7*60*60 = -25200`. - pub tm_utcoff: i32, + pub(crate) tm_utcoff: i32, /// Nanoseconds after the second - [0, 109 - 1] - pub tm_nsec: i32, + pub(crate) tm_nsec: i32, } impl Tm { /// Convert time to the seconds from January 1, 1970 - pub fn to_timespec(&self) -> Timespec { + pub(crate) fn to_timespec(&self) -> Timespec { let sec = match self.tm_utcoff { 0 => inner::utc_tm_to_time(self), _ => inner::local_tm_to_time(self), diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 2f845e7455..3d2c063aa2 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -77,7 +77,7 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { ret } -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { +pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { unsafe { let sec = sec as time_t; let mut out = mem::zeroed(); @@ -103,7 +103,7 @@ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { } } -pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 { +pub(super) fn utc_tm_to_time(rust_tm: &Tm) -> i64 { #[cfg(not(any( all(target_os = "android", target_pointer_width = "32"), target_os = "nacl", @@ -119,7 +119,7 @@ pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 { unsafe { timegm(&mut tm) as i64 } } -pub fn local_tm_to_time(rust_tm: &Tm) -> i64 { +pub(super) fn local_tm_to_time(rust_tm: &Tm) -> i64 { let mut tm = unsafe { mem::zeroed() }; rust_tm_to_tm(rust_tm, &mut tm); unsafe { libc::mktime(&mut tm) as i64 } diff --git a/src/sys/windows.rs b/src/sys/windows.rs index 3f90338e41..d608d209a1 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -88,7 +88,7 @@ macro_rules! call { } } -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { +pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { let ft = time_to_file_time(sec); unsafe { let mut utc = mem::zeroed(); @@ -110,7 +110,7 @@ pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { } } -pub fn utc_tm_to_time(tm: &Tm) -> i64 { +pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { unsafe { let mut ft = mem::zeroed(); let sys_time = tm_to_system_time(tm); @@ -119,7 +119,7 @@ pub fn utc_tm_to_time(tm: &Tm) -> i64 { } } -pub fn local_tm_to_time(tm: &Tm) -> i64 { +pub(super) fn local_tm_to_time(tm: &Tm) -> i64 { unsafe { let mut ft = mem::zeroed(); let mut utc = mem::zeroed(); From abee666b584e7bf5ab7cdbf6099b55deed18eb21 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:19:53 -0700 Subject: [PATCH 008/999] Move Month into a separate module --- src/lib.rs | 306 +-------------------------------------------------- src/month.rs | 306 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+), 304 deletions(-) create mode 100644 src/month.rs diff --git a/src/lib.rs b/src/lib.rs index a9715bd3cf..2152de995f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -655,310 +655,8 @@ impl fmt::Display for SerdeError { mod weekday; pub use weekday::{ParseWeekdayError, Weekday}; -/// The month of the year. -/// -/// This enum is just a convenience implementation. -/// The month in dates created by DateLike objects does not return this enum. -/// -/// It is possible to convert from a date to a month independently -/// ``` -/// # extern crate num_traits; -/// use num_traits::FromPrimitive; -/// use chrono::prelude::*; -/// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); -/// // `2019-10-28T09:10:11Z` -/// let month = Month::from_u32(date.month()); -/// assert_eq!(month, Some(Month::October)) -/// ``` -/// Or from a Month to an integer usable by dates -/// ``` -/// # use chrono::prelude::*; -/// let month = Month::January; -/// let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); -/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); -/// ``` -/// 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)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Month { - /// January - January = 0, - /// February - February = 1, - /// March - March = 2, - /// April - April = 3, - /// May - May = 4, - /// June - June = 5, - /// July - July = 6, - /// August - August = 7, - /// September - September = 8, - /// October - October = 9, - /// November - November = 10, - /// December - December = 11, -} - -impl Month { - /// The next month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `February` | `March` | `...` | `January` - #[inline] - pub fn succ(&self) -> Month { - match *self { - Month::January => Month::February, - Month::February => Month::March, - Month::March => Month::April, - Month::April => Month::May, - Month::May => Month::June, - Month::June => Month::July, - Month::July => Month::August, - Month::August => Month::September, - Month::September => Month::October, - Month::October => Month::November, - Month::November => Month::December, - Month::December => Month::January, - } - } - - /// The previous month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `December` | `January` | `...` | `November` - #[inline] - pub fn pred(&self) -> Month { - match *self { - Month::January => Month::December, - Month::February => Month::January, - Month::March => Month::February, - Month::April => Month::March, - Month::May => Month::April, - Month::June => Month::May, - Month::July => Month::June, - Month::August => Month::July, - Month::September => Month::August, - Month::October => Month::September, - Month::November => Month::October, - Month::December => Month::November, - } - } - - /// Returns a month-of-year number starting from January = 1. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// -------------------------| --------- | ---------- | --- | ----- - /// `m.number_from_month()`: | 1 | 2 | `...` | 12 - #[inline] - pub fn number_from_month(&self) -> u32 { - match *self { - Month::January => 1, - Month::February => 2, - Month::March => 3, - Month::April => 4, - Month::May => 5, - Month::June => 6, - Month::July => 7, - Month::August => 8, - Month::September => 9, - Month::October => 10, - Month::November => 11, - Month::December => 12, - } - } - - /// Get the name of the month - /// - /// ``` - /// use chrono::Month; - /// - /// assert_eq!(Month::January.name(), "January") - /// ``` - pub fn name(&self) -> &'static str { - match *self { - Month::January => "January", - Month::February => "February", - Month::March => "March", - Month::April => "April", - Month::May => "May", - Month::June => "June", - Month::July => "July", - Month::August => "August", - Month::September => "September", - Month::October => "October", - Month::November => "November", - Month::December => "December", - } - } -} - -impl num_traits::FromPrimitive for Month { - /// Returns an Option from a i64, assuming a 1-index, January = 1. - /// - /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` - /// ---------------------------| -------------------- | --------------------- | ... | ----- - /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) - - #[inline] - fn from_u64(n: u64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_i64(n: i64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_u32(n: u32) -> Option { - match n { - 1 => Some(Month::January), - 2 => Some(Month::February), - 3 => Some(Month::March), - 4 => Some(Month::April), - 5 => Some(Month::May), - 6 => Some(Month::June), - 7 => Some(Month::July), - 8 => Some(Month::August), - 9 => Some(Month::September), - 10 => Some(Month::October), - 11 => Some(Month::November), - 12 => Some(Month::December), - _ => None, - } - } -} - -/// An error resulting from reading `` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseMonthError { - _dummy: (), -} - -impl fmt::Debug for ParseMonthError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseMonthError {{ .. }}") - } -} - -#[cfg(feature = "serde")] -mod month_serde { - use super::Month; - use serdelib::{de, ser}; - - use core::fmt; - - impl ser::Serialize for Month { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(self.name()) - } - } - - struct MonthVisitor; - - impl<'de> de::Visitor<'de> for MonthVisitor { - type Value = Month; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Month") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Month { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(MonthVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Month::*; - - let cases: Vec<(Month, &str)> = vec![ - (January, "\"January\""), - (February, "\"February\""), - (March, "\"March\""), - (April, "\"April\""), - (May, "\"May\""), - (June, "\"June\""), - (July, "\"July\""), - (August, "\"August\""), - (September, "\"September\""), - (October, "\"October\""), - (November, "\"November\""), - (December, "\"December\""), - ]; - - for (month, expected_str) in cases { - let string = to_string(&month).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Month::*; - - let cases: Vec<(&str, Month)> = vec![ - ("\"january\"", January), - ("\"jan\"", January), - ("\"FeB\"", February), - ("\"MAR\"", March), - ("\"mar\"", March), - ("\"april\"", April), - ("\"may\"", May), - ("\"june\"", June), - ("\"JULY\"", July), - ("\"august\"", August), - ("\"september\"", September), - ("\"October\"", October), - ("\"November\"", November), - ("\"DECEmbEr\"", December), - ]; - - for (string, expected_month) in cases { - let month = from_str::(string).unwrap(); - assert_eq!(month, expected_month); - } - - let errors: Vec<&str> = - vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; - - for string in errors { - from_str::(string).unwrap_err(); - } - } -} +mod month; +pub use month::{Month, ParseMonthError}; /// The common set of methods for date component. pub trait Datelike: Sized { diff --git a/src/month.rs b/src/month.rs new file mode 100644 index 0000000000..c9b744533b --- /dev/null +++ b/src/month.rs @@ -0,0 +1,306 @@ +use core::fmt; + +/// The month of the year. +/// +/// This enum is just a convenience implementation. +/// The month in dates created by DateLike objects does not return this enum. +/// +/// It is possible to convert from a date to a month independently +/// ``` +/// # extern crate num_traits; +/// use num_traits::FromPrimitive; +/// use chrono::prelude::*; +/// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); +/// // `2019-10-28T09:10:11Z` +/// let month = Month::from_u32(date.month()); +/// assert_eq!(month, Some(Month::October)) +/// ``` +/// Or from a Month to an integer usable by dates +/// ``` +/// # use chrono::prelude::*; +/// let month = Month::January; +/// let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); +/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); +/// ``` +/// 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)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +pub enum Month { + /// January + January = 0, + /// February + February = 1, + /// March + March = 2, + /// April + April = 3, + /// May + May = 4, + /// June + June = 5, + /// July + July = 6, + /// August + August = 7, + /// September + September = 8, + /// October + October = 9, + /// November + November = 10, + /// December + December = 11, +} + +impl Month { + /// The next month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.succ()`: | `February` | `March` | `...` | `January` + #[inline] + pub fn succ(&self) -> Month { + match *self { + Month::January => Month::February, + Month::February => Month::March, + Month::March => Month::April, + Month::April => Month::May, + Month::May => Month::June, + Month::June => Month::July, + Month::July => Month::August, + Month::August => Month::September, + Month::September => Month::October, + Month::October => Month::November, + Month::November => Month::December, + Month::December => Month::January, + } + } + + /// The previous month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.succ()`: | `December` | `January` | `...` | `November` + #[inline] + pub fn pred(&self) -> Month { + match *self { + Month::January => Month::December, + Month::February => Month::January, + Month::March => Month::February, + Month::April => Month::March, + Month::May => Month::April, + Month::June => Month::May, + Month::July => Month::June, + Month::August => Month::July, + Month::September => Month::August, + Month::October => Month::September, + Month::November => Month::October, + Month::December => Month::November, + } + } + + /// Returns a month-of-year number starting from January = 1. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// -------------------------| --------- | ---------- | --- | ----- + /// `m.number_from_month()`: | 1 | 2 | `...` | 12 + #[inline] + pub fn number_from_month(&self) -> u32 { + match *self { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + } + } + + /// Get the name of the month + /// + /// ``` + /// use chrono::Month; + /// + /// assert_eq!(Month::January.name(), "January") + /// ``` + pub fn name(&self) -> &'static str { + match *self { + Month::January => "January", + Month::February => "February", + Month::March => "March", + Month::April => "April", + Month::May => "May", + Month::June => "June", + Month::July => "July", + Month::August => "August", + Month::September => "September", + Month::October => "October", + Month::November => "November", + Month::December => "December", + } + } +} + +impl num_traits::FromPrimitive for Month { + /// Returns an Option from a i64, assuming a 1-index, January = 1. + /// + /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + /// ---------------------------| -------------------- | --------------------- | ... | ----- + /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) + + #[inline] + fn from_u64(n: u64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_i64(n: i64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_u32(n: u32) -> Option { + match n { + 1 => Some(Month::January), + 2 => Some(Month::February), + 3 => Some(Month::March), + 4 => Some(Month::April), + 5 => Some(Month::May), + 6 => Some(Month::June), + 7 => Some(Month::July), + 8 => Some(Month::August), + 9 => Some(Month::September), + 10 => Some(Month::October), + 11 => Some(Month::November), + 12 => Some(Month::December), + _ => None, + } + } +} + +/// An error resulting from reading `` value with `FromStr`. +#[derive(Clone, PartialEq)] +pub struct ParseMonthError { + pub(crate) _dummy: (), +} + +impl fmt::Debug for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + +#[cfg(feature = "serde")] +mod month_serde { + use super::Month; + use serdelib::{de, ser}; + + use core::fmt; + + impl ser::Serialize for Month { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(self.name()) + } + } + + struct MonthVisitor; + + impl<'de> de::Visitor<'de> for MonthVisitor { + type Value = Month; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Month") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Month { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(MonthVisitor) + } + } + + #[cfg(test)] + extern crate serde_json; + + #[test] + fn test_serde_serialize() { + use self::serde_json::to_string; + use Month::*; + + let cases: Vec<(Month, &str)> = vec![ + (January, "\"January\""), + (February, "\"February\""), + (March, "\"March\""), + (April, "\"April\""), + (May, "\"May\""), + (June, "\"June\""), + (July, "\"July\""), + (August, "\"August\""), + (September, "\"September\""), + (October, "\"October\""), + (November, "\"November\""), + (December, "\"December\""), + ]; + + for (month, expected_str) in cases { + let string = to_string(&month).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + fn test_serde_deserialize() { + use self::serde_json::from_str; + use Month::*; + + let cases: Vec<(&str, Month)> = vec![ + ("\"january\"", January), + ("\"jan\"", January), + ("\"FeB\"", February), + ("\"MAR\"", March), + ("\"mar\"", March), + ("\"april\"", April), + ("\"may\"", May), + ("\"june\"", June), + ("\"JULY\"", July), + ("\"august\"", August), + ("\"september\"", September), + ("\"October\"", October), + ("\"November\"", November), + ("\"DECEmbEr\"", December), + ]; + + for (string, expected_month) in cases { + let month = from_str::(string).unwrap(); + assert_eq!(month, expected_month); + } + + let errors: Vec<&str> = + vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; + + for string in errors { + from_str::(string).unwrap_err(); + } + } +} From 861378fc467a82dd40ed6490a735f7c5ad1c5728 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:21:43 -0700 Subject: [PATCH 009/999] Move specific test into month module --- src/lib.rs | 8 -------- src/month.rs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2152de995f..d0d3330c59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -942,11 +942,3 @@ fn test_num_days_from_ce_against_alternative_impl() { assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {:?}", mid_year); } } - -#[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); -} diff --git a/src/month.rs b/src/month.rs index c9b744533b..8301e10494 100644 --- a/src/month.rs +++ b/src/month.rs @@ -304,3 +304,16 @@ mod month_serde { } } } + +#[cfg(test)] +mod tests { + use super::Month; + + #[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); + } +} From 65dd625e16daa894b1d1e8e90a8279335b25c6ec Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:28:04 -0700 Subject: [PATCH 010/999] Move Datelike and Timelike traits into a traits module --- src/lib.rs | 230 +---------------------------------------------- src/traits.rs | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 228 deletions(-) create mode 100644 src/traits.rs diff --git a/src/lib.rs b/src/lib.rs index d0d3330c59..9071a30a0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -658,182 +658,8 @@ pub use weekday::{ParseWeekdayError, Weekday}; mod month; pub use month::{Month, ParseMonthError}; -/// The common set of methods for date component. -pub trait Datelike: Sized { - /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). - fn year(&self) -> i32; - - /// Returns the absolute year number starting from 1 with a boolean flag, - /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). - #[inline] - fn year_ce(&self) -> (bool, u32) { - let year = self.year(); - if year < 1 { - (false, (1 - year) as u32) - } else { - (true, year as u32) - } - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - fn month(&self) -> u32; - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - fn month0(&self) -> u32; - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - fn day(&self) -> u32; - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - fn day0(&self) -> u32; - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - fn ordinal(&self) -> u32; - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - fn ordinal0(&self) -> u32; - - /// Returns the day of week. - fn weekday(&self) -> Weekday; - - /// Returns the ISO week. - fn iso_week(&self) -> IsoWeek; - - /// Makes a new value with the year number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_year(&self, year: i32) -> Option; - - /// Makes a new value with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month(&self, month: u32) -> Option; - - /// Makes a new value with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month0(&self, month0: u32) -> Option; - - /// Makes a new value with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day(&self, day: u32) -> Option; - - /// Makes a new value with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day0(&self, day0: u32) -> Option; - - /// Makes a new value with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal(&self, ordinal: u32) -> Option; - - /// Makes a new value with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal0(&self, ordinal0: u32) -> Option; - - /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. - /// - /// # Examples - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(1970, 1, 1).num_days_from_ce(), 719_163); - /// assert_eq!(NaiveDate::from_ymd(2, 1, 1).num_days_from_ce(), 366); - /// assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - /// assert_eq!(NaiveDate::from_ymd(0, 1, 1).num_days_from_ce(), -365); - /// ``` - fn num_days_from_ce(&self) -> i32 { - // See test_num_days_from_ce_against_alternative_impl below for a more straightforward - // implementation. - - // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. - let mut year = self.year() - 1; - let mut ndays = 0; - if year < 0 { - let excess = 1 + (-year) / 400; - year += excess * 400; - ndays -= excess * 146_097; - } - let div_100 = year / 100; - ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); - ndays + self.ordinal() as i32 - } -} - -/// The common set of methods for time component. -pub trait Timelike: Sized { - /// Returns the hour number from 0 to 23. - fn hour(&self) -> u32; - - /// Returns the hour number from 1 to 12 with a boolean flag, - /// which is false for AM and true for PM. - #[inline] - fn hour12(&self) -> (bool, u32) { - let hour = self.hour(); - let mut hour12 = hour % 12; - if hour12 == 0 { - hour12 = 12; - } - (hour >= 12, hour12) - } - - /// Returns the minute number from 0 to 59. - fn minute(&self) -> u32; - - /// Returns the second number from 0 to 59. - fn second(&self) -> u32; - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). - fn nanosecond(&self) -> u32; - - /// Makes a new value with the hour number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_hour(&self, hour: u32) -> Option; - - /// Makes a new value with the minute number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_minute(&self, min: u32) -> Option; - - /// Makes a new value with the second number changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`second`](#tymethod.second) method, - /// the input range is restricted to 0 through 59. - fn with_second(&self, sec: u32) -> Option; - - /// Makes a new value with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`nanosecond`](#tymethod.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - fn with_nanosecond(&self, nano: u32) -> Option; - - /// Returns the number of non-leap seconds past the last midnight. - #[inline] - fn num_seconds_from_midnight(&self) -> u32 { - self.hour() * 3600 + self.minute() * 60 + self.second() - } -} +mod traits; +pub use traits::{Datelike, Timelike}; #[cfg(test)] extern crate num_iter; @@ -890,55 +716,3 @@ mod test { assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); } } - -/// Tests `Datelike::num_days_from_ce` against an alternative implementation. -/// -/// The alternative implementation is not as short as the current one but it is simpler to -/// understand, with less unexplained magic constants. -#[test] -fn test_num_days_from_ce_against_alternative_impl() { - /// Returns the number of multiples of `div` in the range `start..end`. - /// - /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the - /// behaviour is defined by the following equation: - /// `in_between(start, end, div) == - in_between(end, start, div)`. - /// - /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. - /// - /// # Panics - /// - /// Panics if `div` is not positive. - fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); - let start = (start.div_euclid(div), start.rem_euclid(div)); - let end = (end.div_euclid(div), end.rem_euclid(div)); - // The lowest multiple of `div` greater than or equal to `start`, divided. - let start = start.0 + (start.1 != 0) as i32; - // The lowest multiple of `div` greater than or equal to `end`, divided. - let end = end.0 + (end.1 != 0) as i32; - end - start - } - - /// Alternative implementation to `Datelike::num_days_from_ce` - fn num_days_from_ce(date: &Date) -> i32 { - let year = date.year(); - let diff = move |div| in_between(1, year, div); - // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all - // the multiples of 4 except multiples of 100 but including multiples of 400. - date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) - } - - use num_iter::range_inclusive; - - for year in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - let jan1_year = NaiveDate::from_ymd(year, 1, 1); - assert_eq!( - jan1_year.num_days_from_ce(), - num_days_from_ce(&jan1_year), - "on {:?}", - jan1_year - ); - let mid_year = jan1_year + Duration::days(133); - assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {:?}", mid_year); - } -} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000000..9060ace02e --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,242 @@ +use crate::{IsoWeek, Weekday}; + +/// The common set of methods for date component. +pub trait Datelike: Sized { + /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). + fn year(&self) -> i32; + + /// Returns the absolute year number starting from 1 with a boolean flag, + /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). + #[inline] + fn year_ce(&self) -> (bool, u32) { + let year = self.year(); + if year < 1 { + (false, (1 - year) as u32) + } else { + (true, year as u32) + } + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + fn month(&self) -> u32; + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + fn month0(&self) -> u32; + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + fn day(&self) -> u32; + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + fn day0(&self) -> u32; + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + fn ordinal(&self) -> u32; + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + fn ordinal0(&self) -> u32; + + /// Returns the day of week. + fn weekday(&self) -> Weekday; + + /// Returns the ISO week. + fn iso_week(&self) -> IsoWeek; + + /// Makes a new value with the year number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_year(&self, year: i32) -> Option; + + /// Makes a new value with the month number (starting from 1) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_month(&self, month: u32) -> Option; + + /// Makes a new value with the month number (starting from 0) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_month0(&self, month0: u32) -> Option; + + /// Makes a new value with the day of month (starting from 1) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_day(&self, day: u32) -> Option; + + /// Makes a new value with the day of month (starting from 0) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_day0(&self, day0: u32) -> Option; + + /// Makes a new value with the day of year (starting from 1) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_ordinal(&self, ordinal: u32) -> Option; + + /// Makes a new value with the day of year (starting from 0) changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_ordinal0(&self, ordinal0: u32) -> Option; + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd(1970, 1, 1).num_days_from_ce(), 719_163); + /// assert_eq!(NaiveDate::from_ymd(2, 1, 1).num_days_from_ce(), 366); + /// assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); + /// assert_eq!(NaiveDate::from_ymd(0, 1, 1).num_days_from_ce(), -365); + /// ``` + fn num_days_from_ce(&self) -> i32 { + // See test_num_days_from_ce_against_alternative_impl below for a more straightforward + // implementation. + + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } +} + +/// The common set of methods for time component. +pub trait Timelike: Sized { + /// Returns the hour number from 0 to 23. + fn hour(&self) -> u32; + + /// Returns the hour number from 1 to 12 with a boolean flag, + /// which is false for AM and true for PM. + #[inline] + fn hour12(&self) -> (bool, u32) { + let hour = self.hour(); + let mut hour12 = hour % 12; + if hour12 == 0 { + hour12 = 12; + } + (hour >= 12, hour12) + } + + /// Returns the minute number from 0 to 59. + fn minute(&self) -> u32; + + /// Returns the second number from 0 to 59. + fn second(&self) -> u32; + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). + fn nanosecond(&self) -> u32; + + /// Makes a new value with the hour number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_hour(&self, hour: u32) -> Option; + + /// Makes a new value with the minute number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_minute(&self, min: u32) -> Option; + + /// Makes a new value with the second number changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`second`](#tymethod.second) method, + /// the input range is restricted to 0 through 59. + fn with_second(&self, sec: u32) -> Option; + + /// Makes a new value with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`nanosecond`](#tymethod.nanosecond) method, + /// the input range can exceed 1,000,000,000 for leap seconds. + fn with_nanosecond(&self, nano: u32) -> Option; + + /// Returns the number of non-leap seconds past the last midnight. + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { + self.hour() * 3600 + self.minute() * 60 + self.second() + } +} + +#[cfg(test)] +mod tests { + use super::Datelike; + use crate::naive::{MAX_DATE, MIN_DATE}; + use crate::{NaiveDate, Duration}; + + /// Tests `Datelike::num_days_from_ce` against an alternative implementation. + /// + /// The alternative implementation is not as short as the current one but it is simpler to + /// understand, with less unexplained magic constants. + #[test] + fn test_num_days_from_ce_against_alternative_impl() { + /// Returns the number of multiples of `div` in the range `start..end`. + /// + /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the + /// behaviour is defined by the following equation: + /// `in_between(start, end, div) == - in_between(end, start, div)`. + /// + /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. + /// + /// # Panics + /// + /// Panics if `div` is not positive. + fn in_between(start: i32, end: i32, div: i32) -> i32 { + assert!(div > 0, "in_between: nonpositive div = {}", div); + let start = (start.div_euclid(div), start.rem_euclid(div)); + let end = (end.div_euclid(div), end.rem_euclid(div)); + // The lowest multiple of `div` greater than or equal to `start`, divided. + let start = start.0 + (start.1 != 0) as i32; + // The lowest multiple of `div` greater than or equal to `end`, divided. + let end = end.0 + (end.1 != 0) as i32; + end - start + } + + /// Alternative implementation to `Datelike::num_days_from_ce` + fn num_days_from_ce(date: &Date) -> i32 { + let year = date.year(); + let diff = move |div| in_between(1, year, div); + // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all + // the multiples of 4 except multiples of 100 but including multiples of 400. + date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) + } + + use num_iter::range_inclusive; + + for year in range_inclusive(MIN_DATE.year(), MAX_DATE.year()) { + let jan1_year = NaiveDate::from_ymd(year, 1, 1); + assert_eq!( + jan1_year.num_days_from_ce(), + num_days_from_ce(&jan1_year), + "on {:?}", + jan1_year + ); + let mid_year = jan1_year + Duration::days(133); + assert_eq!( + mid_year.num_days_from_ce(), + num_days_from_ce(&mid_year), + "on {:?}", + mid_year + ); + } + } +} From b72d74742366dbe52e3b7389bd7c4aefb292400a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:35:32 -0700 Subject: [PATCH 011/999] Delete div module in favor of direct imports --- src/div.rs | 41 ----------------------------------------- src/format/mod.rs | 1 - src/format/parsed.rs | 4 ++-- src/lib.rs | 1 - src/naive/date.rs | 2 +- src/naive/datetime.rs | 2 +- src/naive/internals.rs | 2 +- src/naive/time.rs | 4 ++-- src/offset/fixed.rs | 3 +-- 9 files changed, 8 insertions(+), 52 deletions(-) delete mode 100644 src/div.rs diff --git a/src/div.rs b/src/div.rs deleted file mode 100644 index 3dc2b41703..0000000000 --- a/src/div.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This is a part of Chrono. -// Portions Copyright 2013-2014 The Rust Project Developers. -// See README.md and LICENSE.txt for details. - -//! Integer division utilities. (Shamelessly copied from [num](https://github.com/rust-lang/num/)) - -// Algorithm from [Daan Leijen. _Division and Modulus for Computer Scientists_, -// December 2001](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf) - -pub(super) use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor}; - -#[cfg(test)] -mod tests { - use super::{div_mod_floor, mod_floor}; - - #[test] - fn test_mod_floor() { - assert_eq!(mod_floor(8, 3), 2); - assert_eq!(mod_floor(8, -3), -1); - assert_eq!(mod_floor(-8, 3), 1); - assert_eq!(mod_floor(-8, -3), -2); - - assert_eq!(mod_floor(1, 2), 1); - assert_eq!(mod_floor(1, -2), -1); - assert_eq!(mod_floor(-1, 2), 1); - assert_eq!(mod_floor(-1, -2), -1); - } - - #[test] - fn test_div_mod_floor() { - assert_eq!(div_mod_floor(8, 3), (2, 2)); - assert_eq!(div_mod_floor(8, -3), (-3, -1)); - assert_eq!(div_mod_floor(-8, 3), (-3, 1)); - assert_eq!(div_mod_floor(-8, -3), (2, -2)); - - assert_eq!(div_mod_floor(1, 2), (0, 1)); - assert_eq!(div_mod_floor(1, -2), (-1, -1)); - assert_eq!(div_mod_floor(-1, 2), (-1, 1)); - assert_eq!(div_mod_floor(-1, -2), (0, -1)); - } -} diff --git a/src/format/mod.rs b/src/format/mod.rs index caec32e240..30fb181703 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -465,7 +465,6 @@ fn format_inner<'a>( ) }; - use crate::div::{div_floor, mod_floor}; use core::fmt::Write; match *item { diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 8223b30ac5..aed780a751 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,13 +4,13 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use crate::oldtime::Duration as OldDuration; +use num_integer::div_rem; use num_traits::ToPrimitive; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; -use crate::div::div_rem; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Weekday; use crate::{Datelike, Timelike}; diff --git a/src/lib.rs b/src/lib.rs index 9071a30a0d..78e566257b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -552,7 +552,6 @@ macro_rules! try_opt { }; } -mod div; pub mod offset; pub mod naive { //! Date and time types unconcerned with timezones. diff --git a/src/naive/date.rs b/src/naive/date.rs index 3ba935c2d7..b4c4991dd6 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -8,9 +8,9 @@ use crate::oldtime::Duration as OldDuration; use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; +use num_integer::div_mod_floor; use num_traits::ToPrimitive; -use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index a247ba74dc..44206ef87d 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -8,9 +8,9 @@ use crate::oldtime::Duration as OldDuration; use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; +use num_integer::div_mod_floor; use num_traits::ToPrimitive; -use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 733f3eac8d..3eb833c0bc 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -16,9 +16,9 @@ #![allow(dead_code)] // some internal methods have been left for consistency #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -use crate::div::{div_rem, mod_floor}; use crate::Weekday; use core::{fmt, i32}; +use num_integer::{div_rem, mod_floor}; use num_traits::FromPrimitive; /// The internal date representation. This also includes the packed `Mdf` value. diff --git a/src/naive/time.rs b/src/naive/time.rs index 68eefccc2b..6262720904 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -3,17 +3,17 @@ //! ISO 8601 time without timezone. -use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; +use num_integer::div_mod_floor; -use crate::div::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::oldtime::Duration as OldDuration; use crate::Timelike; pub(super) const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index b29dcff30f..87bac2d22c 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -3,12 +3,11 @@ //! The time zone which has a fixed offset from UTC. -use crate::oldtime::Duration as OldDuration; use core::fmt; use core::ops::{Add, Sub}; +use num_integer::div_mod_floor; use super::{LocalResult, Offset, TimeZone}; -use crate::div::div_mod_floor; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::DateTime; use crate::Timelike; From 36a6e0de0cca4672b2d4fa4e1eb28d7c04e93a2d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 20:36:32 -0700 Subject: [PATCH 012/999] Move naive module contents into a separate file --- src/lib.rs | 41 ++--------------------------------------- src/naive/mod.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 39 deletions(-) create mode 100644 src/naive/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 78e566257b..8eb11fa7f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -552,48 +552,11 @@ macro_rules! try_opt { }; } -pub mod offset; -pub mod naive { - //! Date and time types unconcerned with timezones. - //! - //! They are primarily building blocks for other types - //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), - //! but can be also used for the simpler date and time handling. - - mod date; - mod datetime; - mod internals; - mod isoweek; - mod time; - - pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; - #[cfg(feature = "rustc-serialize")] - #[allow(deprecated)] - pub use self::datetime::rustc_serialize::TsSeconds; - pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; - pub use self::isoweek::IsoWeek; - pub use self::time::NaiveTime; - - #[cfg(feature = "__internal_bench")] - #[doc(hidden)] - pub use self::internals::YearFlags as __BenchYearFlags; - - /// Serialization/Deserialization of naive types in alternate formats - /// - /// The various modules in here are intended to be used with serde's [`with` - /// annotation][1] to serialize as something other than the default [RFC - /// 3339][2] format. - /// - /// [1]: https://serde.rs/attributes.html#field-attributes - /// [2]: https://tools.ietf.org/html/rfc3339 - #[cfg(feature = "serde")] - pub mod serde { - pub use super::datetime::serde::*; - } -} mod date; mod datetime; pub mod format; +pub mod naive; +pub mod offset; mod round; #[cfg(feature = "__internal_bench")] diff --git a/src/naive/mod.rs b/src/naive/mod.rs new file mode 100644 index 0000000000..7a951d0c60 --- /dev/null +++ b/src/naive/mod.rs @@ -0,0 +1,36 @@ +//! Date and time types unconcerned with timezones. +//! +//! They are primarily building blocks for other types +//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), +//! but can be also used for the simpler date and time handling. + +mod date; +mod datetime; +mod internals; +mod isoweek; +mod time; + +pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; +#[cfg(feature = "rustc-serialize")] +#[allow(deprecated)] +pub use self::datetime::rustc_serialize::TsSeconds; +pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; +pub use self::isoweek::IsoWeek; +pub use self::time::NaiveTime; + +#[cfg(feature = "__internal_bench")] +#[doc(hidden)] +pub use self::internals::YearFlags as __BenchYearFlags; + +/// Serialization/Deserialization of naive types in alternate formats +/// +/// The various modules in here are intended to be used with serde's [`with` +/// annotation][1] to serialize as something other than the default [RFC +/// 3339][2] format. +/// +/// [1]: https://serde.rs/attributes.html#field-attributes +/// [2]: https://tools.ietf.org/html/rfc3339 +#[cfg(feature = "serde")] +pub mod serde { + pub use super::datetime::serde::*; +} From 733aa10a6355a2fc709fd2bd8cbc4407c66a701c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:01:15 -0700 Subject: [PATCH 013/999] Get rid of most extern crate statements --- src/datetime.rs | 55 +++++++++++++++++++----------------------- src/format/mod.rs | 4 +++ src/lib.rs | 41 +++++-------------------------- src/month.rs | 9 +++---- src/naive/date.rs | 13 +++------- src/naive/datetime.rs | 28 +++++++++------------ src/naive/internals.rs | 6 ++--- src/naive/time.rs | 13 +++------- src/offset/fixed.rs | 1 + src/traits.rs | 2 +- src/weekday.rs | 9 +++---- 11 files changed, 64 insertions(+), 117 deletions(-) diff --git a/src/datetime.rs b/src/datetime.rs index 0210a7ce97..b5ba74beb7 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -3,17 +3,20 @@ //! ISO 8601 date and time with time zone. -use crate::oldtime::Duration as OldDuration; -use core::cmp::Ordering; -use core::ops::{Add, Sub}; -use core::{fmt, hash, str}; -#[cfg(any(feature = "std", test))] -use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(feature = "alloc")] +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::ops::{Add, Sub}; +use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; +#[cfg(any(feature = "std", test))] +use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; @@ -25,10 +28,9 @@ use crate::naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::oldtime::Duration as OldDuration; use crate::Date; use crate::{Datelike, Timelike, Weekday}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; /// Specific formatting options for seconds. This may be extended in the /// future, so exhaustive matching in external code is not recommended. @@ -1214,9 +1216,9 @@ pub(super) mod serde { #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use crate::serdelib::{de, ser}; use crate::{ne_timestamp, SerdeError}; use core::fmt; + use serde::{de, ser}; #[doc(hidden)] #[derive(Debug)] @@ -1377,7 +1379,7 @@ pub(super) mod serde { /// ``` pub mod ts_nanoseconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; use crate::offset::TimeZone; use crate::{DateTime, Utc}; @@ -1530,7 +1532,7 @@ pub(super) mod serde { /// ``` pub mod ts_nanoseconds_option { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; use crate::{DateTime, Utc}; @@ -1686,14 +1688,12 @@ pub(super) mod serde { /// ``` pub mod ts_microseconds { use core::fmt; + use serde::{de, ser}; - use serdelib::{de, ser}; - + use super::{serde_from, MicroSecondsTimestampVisitor}; use crate::offset::TimeZone; use crate::{DateTime, Utc}; - use super::{serde_from, MicroSecondsTimestampVisitor}; - /// Serialize a UTC datetime into an integer number of microseconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. @@ -1840,11 +1840,10 @@ pub(super) mod serde { /// ``` pub mod ts_microseconds_option { use core::fmt; - use serdelib::{de, ser}; - - use crate::{DateTime, Utc}; + use serde::{de, ser}; use super::MicroSecondsTimestampVisitor; + use crate::{DateTime, Utc}; /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none /// @@ -1996,13 +1995,12 @@ pub(super) mod serde { /// ``` pub mod ts_milliseconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; + use super::{serde_from, MilliSecondsTimestampVisitor}; use crate::offset::TimeZone; use crate::{DateTime, Utc}; - use super::{serde_from, MilliSecondsTimestampVisitor}; - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. @@ -2146,11 +2144,10 @@ pub(super) mod serde { /// ``` pub mod ts_milliseconds_option { use core::fmt; - use serdelib::{de, ser}; - - use crate::{DateTime, Utc}; + use serde::{de, ser}; use super::MilliSecondsTimestampVisitor; + use crate::{DateTime, Utc}; /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none /// @@ -2315,13 +2312,12 @@ pub(super) mod serde { /// ``` pub mod ts_seconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; + use super::{serde_from, SecondsTimestampVisitor}; use crate::offset::TimeZone; use crate::{DateTime, Utc}; - use super::{serde_from, SecondsTimestampVisitor}; - /// Serialize a UTC datetime into an integer number of seconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. @@ -2459,11 +2455,10 @@ pub(super) mod serde { /// ``` pub mod ts_seconds_option { use core::fmt; - use serdelib::{de, ser}; - - use crate::{DateTime, Utc}; + use serde::{de, ser}; use super::SecondsTimestampVisitor; + use crate::{DateTime, Utc}; /// Serialize a UTC datetime into an integer number of seconds since the epoch or none /// diff --git a/src/format/mod.rs b/src/format/mod.rs index 30fb181703..b61ac05d16 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -35,6 +35,9 @@ #![allow(ellipsis_inclusive_range_patterns)] +#[cfg(feature = "alloc")] +extern crate alloc; + #[cfg(feature = "alloc")] use alloc::boxed::Box; #[cfg(feature = "alloc")] @@ -466,6 +469,7 @@ fn format_inner<'a>( }; use core::fmt::Write; + use num_integer::{div_floor, mod_floor}; match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), diff --git a/src/lib.rs b/src/lib.rs index 8eb11fa7f6..009ec8e679 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -451,56 +451,27 @@ manual_range_contains, ))] -#[cfg(feature = "alloc")] -extern crate alloc; -#[cfg(all(feature = "std", not(feature = "alloc")))] -extern crate std as alloc; -#[cfg(any(feature = "std", test))] -extern crate std as core; - #[cfg(feature = "oldtime")] extern crate time as oldtime; #[cfg(not(feature = "oldtime"))] mod oldtime; +// this reexport is to aid the transition and should not be in the prelude! +pub use oldtime::Duration; -#[cfg(feature = "clock")] -extern crate libc; -#[cfg(all(feature = "clock", windows))] -extern crate winapi; #[cfg(all( feature = "clock", not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) ))] mod sys; -extern crate num_integer; -extern crate num_traits; -#[cfg(feature = "rustc-serialize")] -extern crate rustc_serialize; -#[cfg(feature = "serde")] -extern crate serde as serdelib; #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] -#[macro_use] -extern crate doc_comment; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate js_sys; -#[cfg(feature = "unstable-locales")] -extern crate pure_rust_locales; -#[cfg(feature = "bench")] -extern crate test; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate wasm_bindgen; +use doc_comment::doctest; #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] doctest!("../README.md"); -use core::fmt; - -// this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::Duration; - pub use date::{Date, MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] pub use datetime::rustc_serialize::TsSeconds; @@ -578,6 +549,9 @@ pub mod serde { // Until rust 1.18 there is no "pub(crate)" so to share this we need it in the root +#[cfg(feature = "serde")] +use core::fmt; + #[cfg(feature = "serde")] enum SerdeError { NonExistent { timestamp: V }, @@ -623,9 +597,6 @@ pub use month::{Month, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; -#[cfg(test)] -extern crate num_iter; - mod test { #[allow(unused_imports)] use super::*; diff --git a/src/month.rs b/src/month.rs index 8301e10494..722f1f835e 100644 --- a/src/month.rs +++ b/src/month.rs @@ -201,7 +201,7 @@ impl fmt::Debug for ParseMonthError { #[cfg(feature = "serde")] mod month_serde { use super::Month; - use serdelib::{de, ser}; + use serde::{de, ser}; use core::fmt; @@ -240,12 +240,9 @@ mod month_serde { } } - #[cfg(test)] - extern crate serde_json; - #[test] fn test_serde_serialize() { - use self::serde_json::to_string; + use serde_json::to_string; use Month::*; let cases: Vec<(Month, &str)> = vec![ @@ -271,7 +268,7 @@ mod month_serde { #[test] fn test_serde_deserialize() { - use self::serde_json::from_str; + use serde_json::from_str; use Month::*; let cases: Vec<(&str, Month)> = vec![ diff --git a/src/naive/date.rs b/src/naive/date.rs index b4c4991dd6..eef885da96 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1815,7 +1815,7 @@ mod rustc_serialize { mod serde { use super::NaiveDate; use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; // TODO not very optimized for space (binary formats would want something better) @@ -1873,26 +1873,21 @@ mod serde { } } - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - #[test] fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); + super::test_encodable_json(serde_json::to_string); } #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); + super::test_decodable_json(|input| serde_json::from_str(&input)); } #[test] fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize, Infinite}; let d = NaiveDate::from_ymd(2014, 7, 24); let encoded = serialize(&d, Infinite).unwrap(); diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 44206ef87d..4875691b29 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -3,11 +3,11 @@ //! ISO 8601 date and time without timezone. -use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; + use num_integer::div_mod_floor; use num_traits::ToPrimitive; @@ -18,6 +18,7 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::date::{MAX_DATE, MIN_DATE}; use crate::naive::time::{MAX_TIME, MIN_TIME}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; +use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Timelike, Weekday}; /// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` @@ -1720,7 +1721,7 @@ pub(super) mod rustc_serialize { pub(super) mod serde { use super::NaiveDateTime; use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; /// Serialize a `NaiveDateTime` as an RFC 3339 string /// @@ -1808,7 +1809,7 @@ pub(super) mod serde { /// ``` pub mod ts_nanoseconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; use crate::{ne_timestamp, NaiveDateTime}; @@ -1960,7 +1961,7 @@ pub(super) mod serde { /// ``` pub mod ts_milliseconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; use crate::{ne_timestamp, NaiveDateTime}; @@ -2109,7 +2110,7 @@ pub(super) mod serde { /// ``` pub mod ts_seconds { use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; use crate::{ne_timestamp, NaiveDateTime}; @@ -2218,29 +2219,22 @@ pub(super) mod serde { } } - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_derive; - #[cfg(test)] - extern crate serde_json; - #[test] fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); + super::test_encodable_json(serde_json::to_string); } #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); + 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 self::bincode::{deserialize, serialize, Infinite}; use crate::naive::NaiveDate; + use bincode::{deserialize, serialize, Infinite}; let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); let encoded = serialize(&dt, Infinite).unwrap(); @@ -2250,10 +2244,10 @@ pub(super) mod serde { #[test] fn test_serde_bincode_optional() { - use self::bincode::{deserialize, serialize, Infinite}; - use self::serde_derive::{Deserialize, Serialize}; use crate::prelude::*; use crate::serde::ts_nanoseconds_option; + use bincode::{deserialize, serialize, Infinite}; + use serde_derive::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct Test { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3eb833c0bc..3fc9e820b5 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -487,14 +487,12 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { - #[cfg(test)] - extern crate num_iter; + use num_iter::range_inclusive; + use std::u32; - use self::num_iter::range_inclusive; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; use crate::Weekday; - use std::u32; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; diff --git a/src/naive/time.rs b/src/naive/time.rs index 6262720904..e926ef1f01 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -1471,7 +1471,7 @@ mod rustc_serialize { mod serde { use super::NaiveTime; use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; // TODO not very optimized for space (binary formats would want something better) // TODO round-trip for general leap seconds (not just those with second = 60) @@ -1511,26 +1511,21 @@ mod serde { } } - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - #[test] fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); + super::test_encodable_json(serde_json::to_string); } #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); + super::test_decodable_json(|input| serde_json::from_str(&input)); } #[test] fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize, Infinite}; let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); let encoded = serialize(&t, Infinite).unwrap(); diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 87bac2d22c..3b31402a4b 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -9,6 +9,7 @@ use num_integer::div_mod_floor; use super::{LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Timelike; diff --git a/src/traits.rs b/src/traits.rs index 9060ace02e..b3905e670d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -181,7 +181,7 @@ pub trait Timelike: Sized { mod tests { use super::Datelike; use crate::naive::{MAX_DATE, MIN_DATE}; - use crate::{NaiveDate, Duration}; + use crate::{Duration, NaiveDate}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. /// diff --git a/src/weekday.rs b/src/weekday.rs index c719ce89b5..3b9d839f27 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -199,7 +199,7 @@ impl fmt::Debug for ParseWeekdayError { mod weekday_serde { use super::Weekday; use core::fmt; - use serdelib::{de, ser}; + use serde::{de, ser}; impl ser::Serialize for Weekday { fn serialize(&self, serializer: S) -> Result @@ -236,12 +236,9 @@ mod weekday_serde { } } - #[cfg(test)] - extern crate serde_json; - #[test] fn test_serde_serialize() { - use self::serde_json::to_string; + use serde_json::to_string; use Weekday::*; let cases: Vec<(Weekday, &str)> = vec![ @@ -262,7 +259,7 @@ mod weekday_serde { #[test] fn test_serde_deserialize() { - use self::serde_json::from_str; + use serde_json::from_str; use Weekday::*; let cases: Vec<(&str, Weekday)> = vec![ From ffb877e2f4015d7ff34dddba320738b5d2a23198 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:25:18 -0700 Subject: [PATCH 014/999] Move tests into appropriate modules --- src/lib.rs | 53 ----------------------------------------------- src/month.rs | 22 ++++++++++++++++++++ src/naive/date.rs | 27 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 009ec8e679..08c6107670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -596,56 +596,3 @@ pub use month::{Month, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; - -mod test { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_readme_doomsday() { - use num_iter::range_inclusive; - - for y in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - // even months - let d4 = NaiveDate::from_ymd(y, 4, 4); - let d6 = NaiveDate::from_ymd(y, 6, 6); - let d8 = NaiveDate::from_ymd(y, 8, 8); - let d10 = NaiveDate::from_ymd(y, 10, 10); - let d12 = NaiveDate::from_ymd(y, 12, 12); - - // nine to five, seven-eleven - let d59 = NaiveDate::from_ymd(y, 5, 9); - let d95 = NaiveDate::from_ymd(y, 9, 5); - let d711 = NaiveDate::from_ymd(y, 7, 11); - let d117 = NaiveDate::from_ymd(y, 11, 7); - - // "March 0" - let d30 = NaiveDate::from_ymd(y, 3, 1).pred(); - - let weekday = d30.weekday(); - let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; - assert!(other_dates.iter().all(|d| d.weekday() == weekday)); - } - } - - #[test] - fn test_month_enum_primitive_parse() { - use num_traits::FromPrimitive; - - let jan_opt = Month::from_u32(1); - let feb_opt = Month::from_u64(2); - let dec_opt = Month::from_i64(12); - let no_month = Month::from_u32(13); - assert_eq!(jan_opt, Some(Month::January)); - assert_eq!(feb_opt, Some(Month::February)); - assert_eq!(dec_opt, Some(Month::December)); - assert_eq!(no_month, None); - - let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); - assert_eq!(Month::from_u32(date.month()), Some(Month::October)); - - let month = Month::January; - let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); - assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); - } -} diff --git a/src/month.rs b/src/month.rs index 722f1f835e..624cf3ea03 100644 --- a/src/month.rs +++ b/src/month.rs @@ -305,6 +305,28 @@ mod month_serde { #[cfg(test)] mod tests { use super::Month; + use crate::{Datelike, TimeZone, Utc}; + + #[test] + fn test_month_enum_primitive_parse() { + use num_traits::FromPrimitive; + + let jan_opt = Month::from_u32(1); + let feb_opt = Month::from_u64(2); + let dec_opt = Month::from_i64(12); + let no_month = Month::from_u32(13); + assert_eq!(jan_opt, Some(Month::January)); + assert_eq!(feb_opt, Some(Month::February)); + assert_eq!(dec_opt, Some(Month::December)); + assert_eq!(no_month, None); + + let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); + assert_eq!(Month::from_u32(date.month()), Some(Month::October)); + + let month = Month::January; + let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } #[test] fn test_month_enum_succ_pred() { diff --git a/src/naive/date.rs b/src/naive/date.rs index eef885da96..01ff9ea09b 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1905,6 +1905,33 @@ mod tests { use crate::{Datelike, Weekday}; use std::{i32, u32}; + #[test] + fn test_readme_doomsday() { + use num_iter::range_inclusive; + + for y in range_inclusive(MIN_DATE.year(), MAX_DATE.year()) { + // even months + let d4 = NaiveDate::from_ymd(y, 4, 4); + let d6 = NaiveDate::from_ymd(y, 6, 6); + let d8 = NaiveDate::from_ymd(y, 8, 8); + let d10 = NaiveDate::from_ymd(y, 10, 10); + let d12 = NaiveDate::from_ymd(y, 12, 12); + + // nine to five, seven-eleven + let d59 = NaiveDate::from_ymd(y, 5, 9); + let d95 = NaiveDate::from_ymd(y, 9, 5); + let d711 = NaiveDate::from_ymd(y, 7, 11); + let d117 = NaiveDate::from_ymd(y, 11, 7); + + // "March 0" + let d30 = NaiveDate::from_ymd(y, 3, 1).pred(); + + let weekday = d30.weekday(); + let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; + assert!(other_dates.iter().all(|d| d.weekday() == weekday)); + } + } + #[test] fn test_date_from_ymd() { let ymd_opt = NaiveDate::from_ymd_opt; From 4ebfb0d87f121c958874584ced3fda2dfa97d962 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:34:53 -0700 Subject: [PATCH 015/999] Clarify interface for sys module --- src/{sys.rs => sys/mod.rs} | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) rename src/{sys.rs => sys/mod.rs} (93%) diff --git a/src/sys.rs b/src/sys/mod.rs similarity index 93% rename from src/sys.rs rename to src/sys/mod.rs index 9bb21e49ee..bcb8ca8e9d 100644 --- a/src/sys.rs +++ b/src/sys/mod.rs @@ -17,17 +17,19 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(target_arch = "wasm32", target_env = "sgx"))] -#[path = "sys/stub.rs"] +#[path = "stub.rs"] mod inner; #[cfg(all(unix, not(target_arch = "wasm32"), not(target_env = "sgx")))] -#[path = "sys/unix.rs"] +#[path = "unix.rs"] mod inner; #[cfg(all(windows, not(target_arch = "wasm32"), not(target_env = "sgx")))] -#[path = "sys/windows.rs"] +#[path = "windows.rs"] mod inner; +use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; + /// A record specifying a time value in seconds and nanoseconds, where /// nanoseconds represent the offset from the given second. /// @@ -61,7 +63,7 @@ impl Timespec { tm_utcoff: 0, tm_nsec: 0, }; - inner::time_to_local_tm(self.sec, &mut tm); + time_to_local_tm(self.sec, &mut tm); tm.tm_nsec = self.nsec; tm } @@ -118,8 +120,8 @@ impl Tm { /// Convert time to the seconds from January 1, 1970 pub(crate) fn to_timespec(&self) -> Timespec { let sec = match self.tm_utcoff { - 0 => inner::utc_tm_to_time(self), - _ => inner::local_tm_to_time(self), + 0 => utc_tm_to_time(self), + _ => local_tm_to_time(self), }; Timespec { sec: sec, nsec: self.tm_nsec } } From c68a2b7bb8046d97f73d5cf1ffc465c33f31d5c9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:43:24 -0700 Subject: [PATCH 016/999] Move serde test helpers to an appropriate module --- src/datetime.rs | 25 ++++------------ src/lib.rs | 41 -------------------------- src/naive/datetime.rs | 67 +++++++++++++++++++++++++++++++++++++++---- src/naive/mod.rs | 2 +- 4 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/datetime.rs b/src/datetime.rs index b5ba74beb7..8f55117211 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1212,13 +1212,14 @@ pub(super) mod rustc_serialize { /// documented at re-export site #[cfg(feature = "serde")] pub(super) mod serde { + use core::fmt; + use serde::{de, ser}; + use super::DateTime; + use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; - use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use crate::{ne_timestamp, SerdeError}; - use core::fmt; - use serde::{de, ser}; + use crate::offset::{FixedOffset, TimeZone, Utc}; #[doc(hidden)] #[derive(Debug)] @@ -1236,22 +1237,6 @@ pub(super) mod serde { #[derive(Debug)] pub struct MilliSecondsTimestampVisitor; - // lik? function to convert a LocalResult into a serde-ish Result - fn serde_from(me: LocalResult, ts: &V) -> Result - where - E: de::Error, - V: fmt::Display, - T: fmt::Display, - { - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min: min, max: max })) - } - LocalResult::Single(val) => Ok(val), - } - } - /// Serialize into a rfc3339 time string /// /// See [the `serde` module](./serde/index.html) for alternate diff --git a/src/lib.rs b/src/lib.rs index 08c6107670..b05d072571 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -547,47 +547,6 @@ pub mod serde { pub use super::datetime::serde::*; } -// Until rust 1.18 there is no "pub(crate)" so to share this we need it in the root - -#[cfg(feature = "serde")] -use core::fmt; - -#[cfg(feature = "serde")] -enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, -} - -/// Construct a [`SerdeError::NonExistent`] -#[cfg(feature = "serde")] -fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } -} - -#[cfg(feature = "serde")] -impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) - } -} - -// impl core::error::Error for SerdeError {} -#[cfg(feature = "serde")] -impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &SerdeError::NonExistent { ref timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) - } - &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), - } - } -} - mod weekday; pub use weekday::{ParseWeekdayError, Weekday}; diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 4875691b29..89baf2599e 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -1718,11 +1718,13 @@ pub(super) mod rustc_serialize { /// Tools to help serializing/deserializing `NaiveDateTime`s #[cfg(feature = "serde")] -pub(super) mod serde { - use super::NaiveDateTime; +pub(crate) mod serde { use core::fmt; use serde::{de, ser}; + use super::NaiveDateTime; + use crate::offset::LocalResult; + /// Serialize a `NaiveDateTime` as an RFC 3339 string /// /// See [the `serde` module](./serde/index.html) for alternate @@ -1811,7 +1813,8 @@ pub(super) mod serde { use core::fmt; use serde::{de, ser}; - use crate::{ne_timestamp, NaiveDateTime}; + use super::ne_timestamp; + use crate::NaiveDateTime; /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch /// @@ -1963,7 +1966,8 @@ pub(super) mod serde { use core::fmt; use serde::{de, ser}; - use crate::{ne_timestamp, NaiveDateTime}; + use super::ne_timestamp; + use crate::NaiveDateTime; /// Serialize a UTC datetime into an integer number of milliseconds since the epoch /// @@ -2112,7 +2116,8 @@ pub(super) mod serde { use core::fmt; use serde::{de, ser}; - use crate::{ne_timestamp, NaiveDateTime}; + use super::ne_timestamp; + use crate::NaiveDateTime; /// Serialize a UTC datetime into an integer number of seconds since the epoch /// @@ -2262,6 +2267,58 @@ pub(super) mod serde { 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 + E: de::Error, + V: fmt::Display, + T: fmt::Display, + { + match me { + LocalResult::None => Err(E::custom(ne_timestamp(ts))), + LocalResult::Ambiguous(min, max) => { + Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min: min, max: max })) + } + LocalResult::Single(val) => Ok(val), + } + } + + #[cfg(feature = "serde")] + enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, + } + + /// Construct a [`SerdeError::NonExistent`] + #[cfg(feature = "serde")] + fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } + } + + #[cfg(feature = "serde")] + impl fmt::Debug for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChronoSerdeError({})", self) + } + } + + // impl core::error::Error for SerdeError {} + #[cfg(feature = "serde")] + impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &SerdeError::NonExistent { ref timestamp } => { + write!(f, "value is not a legal timestamp: {}", timestamp) + } + &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( + f, + "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max + ), + } + } + } } #[cfg(test)] diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 7a951d0c60..f23a731a76 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -5,7 +5,7 @@ //! but can be also used for the simpler date and time handling. mod date; -mod datetime; +pub(crate) mod datetime; mod internals; mod isoweek; mod time; From 0648088e4aebb84d073398eb7265d2d670cd8ee1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:45:23 -0700 Subject: [PATCH 017/999] Group module declarations and re-exports --- src/lib.rs | 56 +++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b05d072571..7c049b3dc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -472,23 +472,6 @@ use doc_comment::doctest; #[cfg_attr(feature = "__doctest", cfg(doctest))] doctest!("../README.md"); -pub use date::{Date, MAX_DATE, MIN_DATE}; -#[cfg(feature = "rustc-serialize")] -pub use datetime::rustc_serialize::TsSeconds; -pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; -/// L10n locales. -#[cfg(feature = "unstable-locales")] -pub use format::Locale; -pub use format::{ParseError, ParseResult}; -#[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -#[doc(no_inline)] -pub use offset::Local; -#[doc(no_inline)] -pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; -pub use round::{DurationRound, RoundingError, SubsecRound}; - /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { #[doc(no_inline)] @@ -524,11 +507,41 @@ macro_rules! try_opt { } mod date; +pub use date::{Date, MAX_DATE, MIN_DATE}; + mod datetime; +#[cfg(feature = "rustc-serialize")] +pub use datetime::rustc_serialize::TsSeconds; +pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; + pub mod format; +/// L10n locales. +#[cfg(feature = "unstable-locales")] +pub use format::Locale; +pub use format::{ParseError, ParseResult}; + pub mod naive; +#[doc(no_inline)] +pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; + pub mod offset; +#[cfg(feature = "clock")] +#[doc(no_inline)] +pub use offset::Local; +#[doc(no_inline)] +pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; + mod round; +pub use round::{DurationRound, RoundingError, SubsecRound}; + +mod weekday; +pub use weekday::{ParseWeekdayError, Weekday}; + +mod month; +pub use month::{Month, ParseMonthError}; + +mod traits; +pub use traits::{Datelike, Timelike}; #[cfg(feature = "__internal_bench")] #[doc(hidden)] @@ -546,12 +559,3 @@ pub use naive::__BenchYearFlags; pub mod serde { pub use super::datetime::serde::*; } - -mod weekday; -pub use weekday::{ParseWeekdayError, Weekday}; - -mod month; -pub use month::{Month, ParseMonthError}; - -mod traits; -pub use traits::{Datelike, Timelike}; From 2368e37f8ebf8fb0456a68688bf6a1cf8eace2e5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:54:49 -0700 Subject: [PATCH 018/999] Adopt modern clippy suggestions --- clippy.toml | 1 + src/date.rs | 6 +++--- src/datetime.rs | 20 ++++++++++---------- src/format/mod.rs | 22 +++++----------------- src/format/parse.rs | 12 ++++++------ src/format/scan.rs | 4 ++-- src/format/strftime.rs | 19 ++++++------------- src/lib.rs | 23 ----------------------- src/naive/date.rs | 4 ++-- src/naive/datetime.rs | 33 +++++++++++---------------------- src/naive/internals.rs | 4 ++-- src/naive/time.rs | 31 ++++++++++--------------------- src/offset/local.rs | 2 +- src/sys/mod.rs | 2 +- 14 files changed, 60 insertions(+), 123 deletions(-) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..22d09a5a01 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.32" diff --git a/src/date.rs b/src/date.rs index ed29e0df95..a3cb88a0bc 100644 --- a/src/date.rs +++ b/src/date.rs @@ -68,7 +68,7 @@ impl Date { // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date { - Date { date: date, offset: offset } + Date { date, offset } } /// Makes a new `DateTime` from the current date and given `NaiveTime`. @@ -234,7 +234,7 @@ impl Date { #[inline] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { let date = try_opt!(self.date.checked_add_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + Some(Date { date, offset: self.offset }) } /// Subtracts given `Duration` from the current date. @@ -243,7 +243,7 @@ impl Date { #[inline] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { let date = try_opt!(self.date.checked_sub_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + Some(Date { date, offset: self.offset }) } /// Subtracts another `Date` from the current date. diff --git a/src/datetime.rs b/src/datetime.rs index 8f55117211..6f626932b5 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -95,7 +95,7 @@ impl DateTime { // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - DateTime { datetime: datetime, offset: offset } + DateTime { datetime, offset } } /// Makes a new `DateTime` with given **local** datetime and offset that @@ -126,7 +126,7 @@ impl DateTime { pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { let datetime_utc = datetime - offset.fix(); - DateTime { datetime: datetime_utc, offset: offset } + DateTime { datetime: datetime_utc, offset } } /// Retrieves a date component @@ -454,7 +454,7 @@ impl DateTime { /// ); /// ``` pub fn parse_from_rfc2822(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); parse(&mut parsed, s, ITEMS.iter())?; parsed.to_datetime() @@ -466,7 +466,7 @@ impl DateTime { /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; let mut parsed = Parsed::new(); parse(&mut parsed, s, ITEMS.iter())?; parsed.to_datetime() @@ -508,14 +508,14 @@ where /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc2822(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; self.format_with_items(ITEMS.iter()).to_string() } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc3339(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; self.format_with_items(ITEMS.iter()).to_string() } @@ -551,7 +551,7 @@ where debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - const PREFIX: &'static [Item<'static>] = &[ + const PREFIX: &[Item<'static>] = &[ Item::Numeric(Year, Zero), Item::Literal("-"), Item::Numeric(Month, Zero), @@ -2571,9 +2571,9 @@ pub(super) mod serde { #[test] fn test_serde_deserialize() { super::test_decodable_json( - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), + |input| self::serde_json::from_str(input), + |input| self::serde_json::from_str(input), + |input| self::serde_json::from_str(input), ); } diff --git a/src/format/mod.rs b/src/format/mod.rs index b61ac05d16..11611b98e1 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -586,7 +586,7 @@ fn format_inner<'a>( Ok(()) }), LowerAmPm => time.map(|t| { - #[cfg_attr(feature = "cargo-clippy", allow(useless_asref))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::useless_asref))] { result.push_str(if t.hour12().0 { am_pm_lowercase[1].as_ref() @@ -757,7 +757,7 @@ pub struct DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: None } + DelayedFormat { date, time, off: None, items, locale: None } } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. @@ -771,13 +771,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Off: Offset + fmt::Display, { let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: None, - } + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: None } } /// Makes a new `DelayedFormat` value out of local date and time and locale. @@ -788,7 +782,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { items: I, locale: Locale, ) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) } + DelayedFormat { date, time, off: None, items, locale: Some(locale) } } /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. @@ -804,13 +798,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Off: Offset + fmt::Display, { let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: Some(locale), - } + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } } } diff --git a/src/format/parse.rs b/src/format/parse.rs index f7e28a4975..fdde32a297 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -470,7 +470,7 @@ impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { - const DATE_ITEMS: &'static [Item<'static>] = &[ + const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), @@ -479,7 +479,7 @@ impl str::FromStr for DateTime { Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; - const TIME_ITEMS: &'static [Item<'static>] = &[ + const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), @@ -499,11 +499,11 @@ impl str::FromStr for DateTime { if remainder.starts_with('T') || remainder.starts_with(' ') { parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; } else { - Err(INVALID)?; + return Err(INVALID); } } - Err((_s, e)) => Err(e)?, - Ok(_) => Err(NOT_ENOUGH)?, + Err((_s, e)) => return Err(e), + Ok(_) => return Err(NOT_ENOUGH), }; parsed.to_datetime() } @@ -866,7 +866,7 @@ fn test_rfc2822() { fn parse_rfc850() { use crate::{TimeZone, Utc}; - static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT"; + static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37); diff --git a/src/format/scan.rs b/src/format/scan.rs index bc7e8314a3..ffa213dd81 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -145,7 +145,7 @@ pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// It prefers long month names to short month names when both are possible. pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&'static str; 12] = + static LONG_MONTH_SUFFIXES: [&str; 12] = ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; let (mut s, month0) = short_month0(s)?; @@ -163,7 +163,7 @@ pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// It prefers long weekday names to short weekday names when both are possible. pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] = + static LONG_WEEKDAY_SUFFIXES: [&str; 7] = ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; let (mut s, weekday) = short_weekday(s)?; diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 167055e5d9..58169d125f 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -177,9 +177,9 @@ type Fmt<'a> = Vec>; #[cfg(not(feature = "unstable-locales"))] type Fmt<'a> = &'static [Item<'static>]; -static D_FMT: &'static [Item<'static>] = +static D_FMT: &[Item<'static>] = &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &'static [Item<'static>] = &[ +static D_T_FMT: &[Item<'static>] = &[ fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), @@ -194,8 +194,7 @@ static D_T_FMT: &'static [Item<'static>] = &[ sp!(" "), num0!(Year), ]; -static T_FMT: &'static [Item<'static>] = - &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; +static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] @@ -227,18 +226,12 @@ impl<'a> StrftimeItems<'a> { let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - StrftimeItems { - remainder: s, - recons: Vec::new(), - d_fmt: d_fmt, - d_t_fmt: d_t_fmt, - t_fmt: t_fmt, - } + StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt } } #[cfg(not(feature = "unstable-locales"))] fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &'static [Item<'static>; 0] = &[]; + static FMT_NONE: &[Item<'static>; 0] = &[]; StrftimeItems { remainder: s, @@ -261,7 +254,7 @@ impl<'a> StrftimeItems<'a> { } } -const HAVE_ALTERNATES: &'static str = "z"; +const HAVE_ALTERNATES: &str = "z"; impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; diff --git a/src/lib.rs b/src/lib.rs index 7c049b3dc3..62b997a951 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -427,29 +427,6 @@ // lints are added all the time, we test on 1.13 #![allow(unknown_lints)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -#![cfg_attr(feature = "cargo-clippy", allow( - renamed_and_removed_lints, - // The explicit 'static lifetimes are still needed for rustc 1.13-16 - // backward compatibility, and this appeases clippy. If minimum rustc - // becomes 1.17, should be able to remove this, those 'static lifetimes, - // and use `static` in a lot of places `const` is used now. - redundant_static_lifetimes, - // the field-init shorthand (which this lint recommends) was stabilized in rust 1.17. - redundant_field_names, - // #[non_exhaustive] was introduced in 1.40 - manual_non_exhaustive, - // `matches!` was stabilized in 1.42 - match_like_matches_macro, - // Changing trivially_copy_pass_by_ref would require an incompatible version - // bump. - trivially_copy_pass_by_ref, - try_err, - // Currently deprecated, we use the separate implementation to add docs - // warning that putting a time in a hash table is probably a bad idea - derive_hash_xor_eq, - // Range contains was stabilized in 1.35. - manual_range_contains, -))] #[cfg(feature = "oldtime")] extern crate time as oldtime; diff --git a/src/naive/date.rs b/src/naive/date.rs index 01ff9ea09b..d0b6b4c33a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1697,7 +1697,7 @@ impl str::FromStr for NaiveDate { type Err = ParseError; fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ + const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), @@ -1880,7 +1880,7 @@ mod serde { #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(&input)); + super::test_decodable_json(|input| serde_json::from_str(input)); } #[test] diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 89baf2599e..7e41f3dca5 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -6,7 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; +use core::{fmt, str}; use num_integer::div_mod_floor; use num_traits::ToPrimitive; @@ -59,7 +59,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime { date: MAX_DATE, time: MA /// assert_eq!(dt.weekday(), Weekday::Fri); /// assert_eq!(dt.num_seconds_from_midnight(), 33011); /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub struct NaiveDateTime { date: NaiveDate, time: NaiveTime, @@ -84,7 +84,7 @@ impl NaiveDateTime { /// ``` #[inline] pub fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { - NaiveDateTime { date: date, time: time } + NaiveDateTime { date, time } } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, @@ -152,7 +152,7 @@ impl NaiveDateTime { .and_then(NaiveDate::from_num_days_from_ce_opt); let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); match (date, time) { - (Some(date), Some(time)) => Some(NaiveDateTime { date: date, time: time }), + (Some(date), Some(time)) => Some(NaiveDateTime { date, time }), (_, _) => None, } } @@ -523,7 +523,7 @@ impl NaiveDateTime { } let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) + Some(NaiveDateTime { date, time }) } /// Subtracts given `Duration` from the current date and time. @@ -602,7 +602,7 @@ impl NaiveDateTime { } let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) + Some(NaiveDateTime { date, time }) } /// Subtracts another `NaiveDateTime` from the current date and time. @@ -1201,17 +1201,6 @@ impl Timelike for NaiveDateTime { } } -/// `NaiveDateTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveDateTime { - fn hash(&self, state: &mut H) { - self.date.hash(state); - self.time.hash(state); - } -} - /// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), @@ -1485,7 +1474,7 @@ impl str::FromStr for NaiveDateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ + const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), @@ -2231,7 +2220,7 @@ pub(crate) mod serde { #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(&input)); + super::test_decodable_json(|input| serde_json::from_str(input)); } // Bincode is relevant to test separately from JSON because @@ -2278,7 +2267,7 @@ pub(crate) mod serde { match me { LocalResult::None => Err(E::custom(ne_timestamp(ts))), LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min: min, max: max })) + Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) } LocalResult::Single(val) => Ok(val), } @@ -2308,10 +2297,10 @@ pub(crate) mod serde { impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &SerdeError::NonExistent { ref timestamp } => { + SerdeError::NonExistent { timestamp } => { write!(f, "value is not a legal timestamp: {}", timestamp) } - &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( + SerdeError::Ambiguous { timestamp, min, max } => write!( f, "value is an ambiguous timestamp: {}, could be either of {}, {}", timestamp, min, max diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3fc9e820b5..1fa2045a85 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -341,7 +341,7 @@ impl Of { (weekord / 7, Weekday::from_u32(weekord % 7).unwrap()) } - #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) fn to_mdf(&self) -> Mdf { Mdf::from_of(*self) @@ -464,7 +464,7 @@ impl Mdf { Mdf((mdf & !0b1111) | u32::from(flags)) } - #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) fn to_of(&self) -> Of { Of::from_mdf(*self) diff --git a/src/naive/time.rs b/src/naive/time.rs index e926ef1f01..5056edf147 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -6,7 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; +use core::{fmt, str}; use num_integer::div_mod_floor; #[cfg(any(feature = "alloc", feature = "std", test))] @@ -179,7 +179,7 @@ pub(super) const MAX_TIME: NaiveTime = /// /// Since Chrono alone cannot determine any existence of leap seconds, /// **there is absolutely no guarantee that the leap second read has actually happened**. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub struct NaiveTime { secs: u32, frac: u32, @@ -387,7 +387,7 @@ impl NaiveTime { return None; } let secs = hour * 3600 + min * 60 + sec; - Some(NaiveTime { secs: secs, frac: nano }) + Some(NaiveTime { secs, frac: nano }) } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. @@ -438,7 +438,7 @@ impl NaiveTime { if secs >= 86_400 || nano >= 2_000_000_000 { return None; } - Some(NaiveTime { secs: secs, frac: nano }) + Some(NaiveTime { secs, frac: nano }) } /// Parses a string with the specified format string and returns a new `NaiveTime`. @@ -549,7 +549,7 @@ impl NaiveTime { } else { frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; debug_assert!(frac < 2_000_000_000); - return (NaiveTime { secs: secs, frac: frac }, 0); + return (NaiveTime { secs, frac }, 0); } } debug_assert!(secs <= 86_400); @@ -905,7 +905,7 @@ impl Timelike for NaiveTime { return None; } let secs = hour * 3600 + self.secs % 3600; - Some(NaiveTime { secs: secs, ..*self }) + Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with the minute number changed. @@ -927,7 +927,7 @@ impl Timelike for NaiveTime { return None; } let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; - Some(NaiveTime { secs: secs, ..*self }) + Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with the second number changed. @@ -951,7 +951,7 @@ impl Timelike for NaiveTime { return None; } let secs = self.secs / 60 * 60 + sec; - Some(NaiveTime { secs: secs, ..*self }) + Some(NaiveTime { secs, ..*self }) } /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. @@ -1010,17 +1010,6 @@ impl Timelike for NaiveTime { } } -/// `NaiveTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveTime { - fn hash(&self, state: &mut H) { - self.secs.hash(state); - self.frac.hash(state); - } -} - /// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. /// @@ -1324,7 +1313,7 @@ impl str::FromStr for NaiveTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ + const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), @@ -1518,7 +1507,7 @@ mod serde { #[test] fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(&input)); + super::test_decodable_json(|input| serde_json::from_str(input)); } #[test] diff --git a/src/offset/local.rs b/src/offset/local.rs index 2c926322e7..3eef187bb8 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -65,7 +65,7 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec { tm_wday: 0, // to_local ignores this tm_yday: 0, // and this tm_isdst: -1, - tm_utcoff: tm_utcoff, + tm_utcoff, // do not set this, OS APIs are heavily inconsistent in terms of leap second handling tm_nsec: 0, }; diff --git a/src/sys/mod.rs b/src/sys/mod.rs index bcb8ca8e9d..2dc3453625 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -123,6 +123,6 @@ impl Tm { 0 => utc_tm_to_time(self), _ => local_tm_to_time(self), }; - Timespec { sec: sec, nsec: self.tm_nsec } + Timespec { sec, nsec: self.tm_nsec } } } From 947914917ee669b6c1dabc1a5e6a2c8fa9912a79 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 21:58:53 -0700 Subject: [PATCH 019/999] Split datetime module into different files --- src/datetime.rs | 3033 ------------------------------- src/datetime/mod.rs | 1094 +++++++++++ src/datetime/rustc_serialize.rs | 119 ++ src/datetime/serde.rs | 1368 ++++++++++++++ src/datetime/tests.rs | 437 +++++ 5 files changed, 3018 insertions(+), 3033 deletions(-) delete mode 100644 src/datetime.rs create mode 100644 src/datetime/mod.rs create mode 100644 src/datetime/rustc_serialize.rs create mode 100644 src/datetime/serde.rs create mode 100644 src/datetime/tests.rs diff --git a/src/datetime.rs b/src/datetime.rs deleted file mode 100644 index 6f626932b5..0000000000 --- a/src/datetime.rs +++ /dev/null @@ -1,3033 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 date and time with time zone. - -#[cfg(feature = "alloc")] -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::ops::{Add, Sub}; -use core::{fmt, hash, str}; -#[cfg(feature = "std")] -use std::string::ToString; -#[cfg(any(feature = "std", test))] -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::format::DelayedFormat; -#[cfg(feature = "unstable-locales")] -use crate::format::Locale; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use crate::format::{Fixed, Item}; -use crate::naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -use crate::offset::Local; -use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; -use crate::oldtime::Duration as OldDuration; -use crate::Date; -use crate::{Datelike, Timelike, Weekday}; - -/// Specific formatting options for seconds. This may be extended in the -/// future, so exhaustive matching in external code is not recommended. -/// -/// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SecondsFormat { - /// Format whole seconds only, with no decimal point nor subseconds. - Secs, - - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). - Millis, - - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). - Micros, - - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). - Nanos, - - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). - AutoSi, - - // Do not match against this. - #[doc(hidden)] - __NonExhaustive, -} - -/// ISO 8601 combined date and time with time zone. -/// -/// There are some constructors implemented here (the `from_*` methods), but -/// the general-purpose constructors are all via the methods on the -/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. -#[derive(Clone)] -pub struct DateTime { - datetime: NaiveDateTime, - offset: Tz::Offset, -} - -/// The minimum possible `DateTime`. -pub const MIN_DATETIME: DateTime = DateTime { datetime: naive::MIN_DATETIME, offset: Utc }; -/// The maximum possible `DateTime`. -pub const MAX_DATETIME: DateTime = DateTime { datetime: naive::MAX_DATETIME, offset: Utc }; - -impl DateTime { - /// Makes a new `DateTime` with given *UTC* datetime and offset. - /// The local datetime should be constructed via the `TimeZone` trait. - /// - /// # Example - /// - /// ``` - /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; - /// - /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - /// assert_eq!(Utc.timestamp(61, 0), dt); - /// ``` - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. - #[inline] - pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - DateTime { datetime, offset } - } - - /// Makes a new `DateTime` with given **local** datetime and offset that - /// presents local timezone. - /// - /// # Example - /// - /// ``` - /// use chrono::DateTime; - /// use chrono::naive::NaiveDate; - /// use chrono::offset::{Utc, FixedOffset}; - /// - /// let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); - /// let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - /// - /// let timezone_east = FixedOffset::east(8 * 60 * 60); - /// let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); - /// let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); - /// - /// let timezone_west = FixedOffset::west(7 * 60 * 60); - /// let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); - /// let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); - - /// assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); - /// assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); - /// ``` - #[inline] - pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - let datetime_utc = datetime - offset.fix(); - - DateTime { datetime: datetime_utc, offset } - } - - /// Retrieves a date component - /// - /// 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. - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let date: Date = Utc.ymd(2020, 1, 1); - /// let dt: DateTime = date.and_hms(0, 0, 0); - /// - /// assert_eq!(dt.date(), date); - /// - /// assert_eq!(dt.date().and_hms(1, 1, 1), date.and_hms(1, 1, 1)); - /// ``` - #[inline] - pub fn date(&self) -> Date { - Date::from_utc(self.naive_local().date(), self.offset.clone()) - } - - /// Retrieves the Date without an associated timezone - /// - /// [`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. - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let date: DateTime = Utc.ymd(2020, 1, 1).and_hms(0, 0, 0); - /// let other: DateTime = FixedOffset::east(23).ymd(2020, 1, 1).and_hms(0, 0, 0); - /// assert_eq!(date.date_naive(), other.date_naive()); - /// ``` - #[inline] - pub fn date_naive(&self) -> NaiveDate { - let local = self.naive_local(); - NaiveDate::from_ymd(local.year(), local.month(), local.day()) - } - - /// Retrieves a time component. - /// Unlike `date`, this is not associated to the time zone. - #[inline] - pub fn time(&self) -> NaiveTime { - self.datetime.time() + self.offset.fix() - } - - /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC - /// (aka "UNIX timestamp"). - #[inline] - pub fn timestamp(&self) -> i64 { - 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.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_millis(&self) -> i64 { - 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 - /// millenia, I'm curious.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_micros(), 1_000_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_micros(&self) -> i64 { - self.datetime.timestamp_micros() - } - - /// 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.) - /// - /// # Example - /// - /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); - /// ``` - #[inline] - pub fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() - } - - /// 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 - #[inline] - pub fn timestamp_subsec_millis(&self) -> u32 { - self.datetime.timestamp_subsec_millis() - } - - /// 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 - #[inline] - pub fn timestamp_subsec_micros(&self) -> u32 { - self.datetime.timestamp_subsec_micros() - } - - /// 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 - #[inline] - pub fn timestamp_subsec_nanos(&self) -> u32 { - self.datetime.timestamp_subsec_nanos() - } - - /// Retrieves an associated offset from UTC. - #[inline] - pub fn offset(&self) -> &Tz::Offset { - &self.offset - } - - /// Retrieves an associated time zone. - #[inline] - pub fn timezone(&self) -> Tz { - TimeZone::from_offset(&self.offset) - } - - /// Changes the associated time zone. - /// This does not change the actual `DateTime` (but will change the string representation). - #[inline] - pub fn with_timezone(&self, tz: &Tz2) -> DateTime { - tz.from_utc_datetime(&self.datetime) - } - - /// Adds given `Duration` to the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_add_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts given `Duration` from the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_sub_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts another `DateTime` from the current date and time. - /// This does not overflow or underflow at all. - #[inline] - pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { - self.datetime.signed_duration_since(rhs.datetime) - } - - /// Returns a view to the naive UTC datetime. - #[inline] - pub fn naive_utc(&self) -> NaiveDateTime { - self.datetime - } - - /// Returns a view to the naive local datetime. - #[inline] - pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() - } -} - -impl Default for DateTime { - fn default() -> Self { - Utc.from_utc_datetime(&NaiveDateTime::default()) - } -} - -#[cfg(feature = "clock")] -impl Default for DateTime { - fn default() -> Self { - Local.from_utc_datetime(&NaiveDateTime::default()) - } -} - -impl Default for DateTime { - fn default() -> Self { - FixedOffset::west(0).from_utc_datetime(&NaiveDateTime::default()) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by - /// this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone - /// difference. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local - /// time. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in - /// timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned - /// by this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Maps the local datetime to other datetime with given conversion function. -fn map_local(dt: &DateTime, mut f: F) -> Option> -where - F: FnMut(NaiveDateTime) -> Option, -{ - f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) -} - -impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// RFC 2822 is the internet message standard that specifices the - /// representation of times in HTTP and email headers. - /// - /// ``` - /// # use chrono::{DateTime, FixedOffset, TimeZone}; - /// assert_eq!( - /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), - /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) - /// ); - /// ``` - pub fn parse_from_rfc2822(s: &str) -> ParseResult> { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. - pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses a string with the specified format string and returns a new - /// [`DateTime`] with a parsed [`FixedOffset`]. - /// - /// See the [`crate::format::strftime`] module on the supported escape - /// sequences. - /// - /// See also [`TimeZone::datetime_from_str`] which gives a local - /// [`DateTime`] on specific time zone. - /// - /// Note that this method *requires a timezone* in the string. See - /// [`NaiveDateTime::parse_from_str`] - /// for a version that does not require a timezone in the to-be-parsed str. - /// - /// # Example - /// - /// ```rust - /// use chrono::{DateTime, FixedOffset, TimeZone}; - /// - /// let dt = DateTime::parse_from_str( - /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_datetime() - } -} - -impl DateTime -where - Tz::Offset: fmt::Display, -{ - /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc2822(&self) -> String { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339(&self) -> String { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per a `SecondsFormat`. - /// - /// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as - /// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use - /// [`Fixed::TimezoneOffsetColon`] - /// - /// # Examples - /// - /// ```rust - /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); - /// - /// let pst = FixedOffset::east(8 * 60 * 60); - /// let dt = pst.ymd(2018, 1, 26).and_hms_micro(10, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use crate::format::Numeric::*; - use crate::format::Pad::Zero; - use crate::SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &[Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - match ssitem { - None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } - } - - /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) - } - - /// Formats the combined date and time with the specified format string. - /// See the [`crate::format::strftime`] module - /// on the supported escape sequences. - /// - /// # Example - /// ```rust - /// use chrono::prelude::*; - /// - /// let date_time: DateTime = Utc.ymd(2017, 04, 02).and_hms(12, 50, 32); - /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); - /// assert_eq!(formatted, "02/04/2017 12:50"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Formats the combined date and time with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized_with_items<'a, I, B>( - &self, - items: I, - locale: Locale, - ) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset_and_locale( - Some(local.date()), - Some(local.time()), - &self.offset, - items, - locale, - ) - } - - /// Formats the combined date and time with the specified format string and - /// locale. - /// - /// See the [`::format::strftime`] module on the supported escape - /// sequences. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized<'a>( - &self, - fmt: &'a str, - locale: Locale, - ) -> DelayedFormat> { - self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) - } -} - -impl Datelike for DateTime { - #[inline] - fn year(&self) -> i32 { - self.naive_local().year() - } - #[inline] - fn month(&self) -> u32 { - self.naive_local().month() - } - #[inline] - fn month0(&self) -> u32 { - self.naive_local().month0() - } - #[inline] - fn day(&self) -> u32 { - self.naive_local().day() - } - #[inline] - fn day0(&self) -> u32 { - self.naive_local().day0() - } - #[inline] - fn ordinal(&self) -> u32 { - self.naive_local().ordinal() - } - #[inline] - fn ordinal0(&self) -> u32 { - self.naive_local().ordinal0() - } - #[inline] - fn weekday(&self) -> Weekday { - self.naive_local().weekday() - } - #[inline] - fn iso_week(&self) -> IsoWeek { - self.naive_local().iso_week() - } - - #[inline] - fn with_year(&self, year: i32) -> Option> { - map_local(self, |datetime| datetime.with_year(year)) - } - - #[inline] - fn with_month(&self, month: u32) -> Option> { - map_local(self, |datetime| datetime.with_month(month)) - } - - #[inline] - fn with_month0(&self, month0: u32) -> Option> { - map_local(self, |datetime| datetime.with_month0(month0)) - } - - #[inline] - fn with_day(&self, day: u32) -> Option> { - map_local(self, |datetime| datetime.with_day(day)) - } - - #[inline] - fn with_day0(&self, day0: u32) -> Option> { - map_local(self, |datetime| datetime.with_day0(day0)) - } - - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal(ordinal)) - } - - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) - } -} - -impl Timelike for DateTime { - #[inline] - fn hour(&self) -> u32 { - self.naive_local().hour() - } - #[inline] - fn minute(&self) -> u32 { - self.naive_local().minute() - } - #[inline] - fn second(&self) -> u32 { - self.naive_local().second() - } - #[inline] - fn nanosecond(&self) -> u32 { - self.naive_local().nanosecond() - } - - #[inline] - fn with_hour(&self, hour: u32) -> Option> { - map_local(self, |datetime| datetime.with_hour(hour)) - } - - #[inline] - fn with_minute(&self, min: u32) -> Option> { - map_local(self, |datetime| datetime.with_minute(min)) - } - - #[inline] - fn with_second(&self, sec: u32) -> Option> { - map_local(self, |datetime| datetime.with_second(sec)) - } - - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option> { - map_local(self, |datetime| datetime.with_nanosecond(nano)) - } -} - -// we need them as automatic impls cannot handle associated types -impl Copy for DateTime where ::Offset: Copy {} -unsafe impl Send for DateTime where ::Offset: Send {} - -impl PartialEq> for DateTime { - fn eq(&self, other: &DateTime) -> bool { - self.datetime == other.datetime - } -} - -impl Eq for DateTime {} - -impl PartialOrd> for DateTime { - /// Compare two DateTimes based on their true time, ignoring time zones - /// - /// # Example - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let earlier = Utc.ymd(2015, 5, 15).and_hms(2, 0, 0).with_timezone(&FixedOffset::west(1 * 3600)); - /// let later = Utc.ymd(2015, 5, 15).and_hms(3, 0, 0).with_timezone(&FixedOffset::west(5 * 3600)); - /// - /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); - /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); - /// - /// assert!(later > earlier); - /// ``` - fn partial_cmp(&self, other: &DateTime) -> Option { - self.datetime.partial_cmp(&other.datetime) - } -} - -impl Ord for DateTime { - fn cmp(&self, other: &DateTime) -> Ordering { - self.datetime.cmp(&other.datetime) - } -} - -impl hash::Hash for DateTime { - fn hash(&self, state: &mut H) { - self.datetime.hash(state) - } -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: OldDuration) -> DateTime { - self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") - } -} - -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> DateTime { - self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") - } -} - -impl Sub> for DateTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: DateTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -impl fmt::Debug for DateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) - } -} - -impl fmt::Display for DateTime -where - Tz::Offset: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.naive_local(), self.offset) - } -} - -/// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. -/// -/// All of these examples are equivalent: -/// ``` -/// # use chrono::{DateTime, Utc}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); -/// ``` -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Utc)) - } -} - -/// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. -/// -/// All of these examples are equivalent: -/// ``` -/// # use chrono::{DateTime, Local}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); -/// ``` -#[cfg(feature = "clock")] -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Local)) - } -} - -#[cfg(any(feature = "std", test))] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { - Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), - Err(e) => { - // unlikely but should be handled - let dur = e.duration(); - let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); - if nsec == 0 { - (-sec, 0) - } else { - (-sec - 1, 1_000_000_000 - nsec) - } - } - }; - Utc.timestamp(sec, nsec) - } -} - -#[cfg(feature = "clock")] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - DateTime::::from(t).with_timezone(&Local) - } -} - -#[cfg(any(feature = "std", test))] -impl From> for SystemTime { - fn from(dt: DateTime) -> SystemTime { - use std::time::Duration; - - let sec = dt.timestamp(); - let nsec = dt.timestamp_subsec_nanos(); - if sec < 0 { - // unlikely but should be handled - UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) - } else { - UNIX_EPOCH + Duration::new(sec as u64, nsec) - } - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From for DateTime { - fn from(date: js_sys::Date) -> DateTime { - DateTime::::from(&date) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From<&js_sys::Date> for DateTime { - fn from(date: &js_sys::Date) -> DateTime { - Utc.timestamp_millis(date.get_time() as i64) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From> for js_sys::Date { - /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, - /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 - /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. - fn from(date: DateTime) -> js_sys::Date { - let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); - js_sys::Date::new(&js_millis) - } -} - -#[test] -fn test_auto_conversion() { - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0); - let utc_dt2: DateTime = cdt_dt.into(); - assert_eq!(utc_dt, utc_dt2); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) -where - FUtc: Fn(&DateTime) -> Result, - FFixed: Fn(&DateTime) -> Result, - E: ::core::fmt::Debug, -{ - assert_eq!( - to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06Z""#.into()) - ); - - assert_eq!( - to_string_fixed(&FixedOffset::east(3660).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:01""#.into()) - ); - assert_eq!( - to_string_fixed(&FixedOffset::east(3650).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) - ); -} - -#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - // should check against the offset as well (the normal DateTime comparison will ignore them) - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(FixedOffset::east(60 * 60 + 23 * 60).ymd(2014, 7, 24).and_hms(13, 57, 6))) - ); - - // we don't know the exact local offset but we can check that - // the conversion didn't change the instant itself - assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - assert_eq!( - local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - - assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); -} - -#[cfg(all(test, feature = "clock", feature = "rustc-serialize"))] -fn test_decodable_json_timestamps( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - norm(&fixed_from_str("0").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&fixed_from_str("-1").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - ); - assert_eq!( - *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.ymd(1969, 12, 31).and_hms(23, 59, 59) - ); -} - -#[cfg(feature = "rustc-serialize")] -pub(super) mod rustc_serialize { - use super::DateTime; - #[cfg(feature = "clock")] - use crate::offset::Local; - use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use core::fmt; - use core::ops::Deref; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for DateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - // lik? function to convert a LocalResult into a serde-ish Result - fn from(me: LocalResult, d: &mut D) -> Result - where - D: Decoder, - T: fmt::Display, - { - match me { - LocalResult::None => Err(d.error("value is not a legal timestamp")), - LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - LocalResult::Single(val) => Ok(val), - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map_err(|_| d.error("invalid date and time")) - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map(|dt| dt.with_timezone(&Utc)) - .map_err(|_| d.error("invalid date and time")) - } - } - - /// A [`DateTime`] that can be deserialized from a timestamp - /// - /// A timestamp here is seconds since the epoch - #[derive(Debug)] - pub struct TsSeconds(DateTime); - - #[allow(deprecated)] - impl From> for DateTime { - /// Pull the inner DateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> DateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - #[cfg(feature = "clock")] - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - match d.read_str()?.parse::>() { - Ok(dt) => Ok(dt.with_timezone(&Local)), - Err(_) => Err(d.error("invalid date and time")), - } - } - } - - #[cfg(feature = "clock")] - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d) - .map(|dt| TsSeconds(dt.with_timezone(&Local))) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode, json::encode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode, json::decode, json::decode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamps(json::decode, json::decode, json::decode); - } -} - -/// documented at re-export site -#[cfg(feature = "serde")] -pub(super) mod serde { - use core::fmt; - use serde::{de, ser}; - - use super::DateTime; - use crate::naive::datetime::serde::serde_from; - #[cfg(feature = "clock")] - use crate::offset::Local; - use crate::offset::{FixedOffset, TimeZone, Utc}; - - #[doc(hidden)] - #[derive(Debug)] - pub struct SecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct NanoSecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct MicroSecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct MilliSecondsTimestampVisitor; - - /// Serialize into a rfc3339 time string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serializations. - impl ser::Serialize for DateTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - // Debug formatting is correct RFC3339, and it allows Zulu. - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct DateTimeVisitor; - - impl<'de> de::Visitor<'de> for DateTimeVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date and time string or a unix timestamp") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - /// Deserialize a value that optionally includes a timezone offset in its - /// string representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor) - } - } - - /// Deserialize into a UTC value - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) - } - } - - /// Deserialize a value that includes no timezone in its string - /// representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - #[cfg(feature = "clock")] - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) - } - } - - /// Ser/de to/from timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serde::{de, ser}; - - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; - - use super::{serde_from, NanoSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a [`DateTime`] from a nanosecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NanoSecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in nanoseconds") - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt( - (value / 1_000_000_000) as i64, - (value % 1_000_000_000) as u32, - ), - &value, - ) - } - } - } - - /// Ser/de to/from timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds_option { - use core::fmt; - use serde::{de, ser}; - - use crate::{DateTime, Utc}; - - use super::NanoSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a nanosecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionNanoSecondsTimestampVisitor) - } - - struct OptionNanoSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in nanoseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in microseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_microseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_microseconds { - use core::fmt; - use serde::{de, ser}; - - use super::{serde_from, MicroSecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of microseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_micro_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_micros()) - } - - /// Deserialize a `DateTime` from a microsecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_micro_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MicroSecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in microseconds") - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1_000_000, ((value % 1_000_000) * 1_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt( - (value / 1_000_000) as i64, - ((value % 1_000_000) * 1_000) as u32, - ), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in microseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_microseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_microseconds_option { - use core::fmt; - use serde::{de, ser}; - - use super::MicroSecondsTimestampVisitor; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_micro_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a microsecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_micro_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionMicroSecondsTimestampVisitor) - } - - struct OptionMicroSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in microseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serde::{de, ser}; - - use super::{serde_from, MilliSecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a millisecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) - } - - impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds") - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds_option { - use core::fmt; - use serde::{de, ser}; - - use super::MilliSecondsTimestampVisitor; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a millisecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::prelude::*; - /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// #[serde(untagged)] - /// enum E { - /// V(T), - /// } - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// struct S { - /// #[serde(default, deserialize_with = "from_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result<(), serde_json::Error> { - /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); - /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; - /// assert_eq!(s, E::V(S { time: None })); - /// let t: E = serde_json::from_str(r#"{}"#)?; - /// assert_eq!(t, E::V(S { time: None })); - /// # Ok(()) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionMilliSecondsTimestampVisitor) - .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) - } - - struct OptionMilliSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serde::{de, ser}; - - use super::{serde_from, SecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(SecondsTimestampVisitor) - } - - impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value, 0), &value) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value as i64, 0), &value) - } - } - } - - /// Ser/de to/from optional timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds_option { - use core::fmt; - use serde::{de, ser}; - - use super::SecondsTimestampVisitor; - use crate::{DateTime, Utc}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a seconds timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_option(OptionSecondsTimestampVisitor) - } - - struct OptionSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(SecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result - where - E: de::Error, - { - Ok(None) - } - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string, self::serde_json::to_string); - } - - #[cfg(feature = "clock")] - #[test] - fn test_serde_deserialize() { - super::test_decodable_json( - |input| self::serde_json::from_str(input), - |input| self::serde_json::from_str(input), - |input| self::serde_json::from_str(input), - ); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); - let encoded = serialize(&dt, Infinite).unwrap(); - let decoded: DateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - assert_eq!(dt.offset(), decoded.offset()); - } -} - -#[cfg(test)] -mod tests { - use super::DateTime; - use crate::naive::{NaiveDate, NaiveTime}; - #[cfg(feature = "clock")] - use crate::offset::Local; - use crate::offset::{FixedOffset, TimeZone, Utc}; - use crate::oldtime::Duration; - #[cfg(feature = "clock")] - use crate::Datelike; - use std::time::{SystemTime, UNIX_EPOCH}; - - #[test] - #[allow(non_snake_case)] - fn test_datetime_offset() { - let Est = FixedOffset::west(5 * 60 * 60); - let Edt = FixedOffset::west(4 * 60 * 60); - let Kst = FixedOffset::east(9 * 60 * 60); - - assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); - assert_eq!( - format!("{}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 -04:00" - ); - assert_eq!( - format!("{}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 +09:00" - ); - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09+09:00" - ); - - // edge cases - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00+09:00" - ); - assert_eq!( - format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59Z" - ); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59+09:00" - ); - - let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(dt, Edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); - assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); - assert_eq!( - dt.signed_duration_since(Edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), - Duration::seconds(-7 * 3600 - 3 * 60 - 3) - ); - - assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); - assert_eq!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Edt); - assert!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != Est); - } - - #[test] - fn test_datetime_date_and_time() { - let tz = FixedOffset::east(5 * 60 * 60); - let d = tz.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(d.time(), NaiveTime::from_hms(7, 8, 9)); - assert_eq!(d.date(), tz.ymd(2014, 5, 6)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2014, 5, 6)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::east(4 * 60 * 60); - let d = tz.ymd(2016, 5, 4).and_hms(3, 2, 1); - assert_eq!(d.time(), NaiveTime::from_hms(3, 2, 1)); - assert_eq!(d.date(), tz.ymd(2016, 5, 4)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2016, 5, 4)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::west(13 * 60 * 60); - let d = tz.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert_eq!(d.time(), NaiveTime::from_hms(12, 34, 56)); - assert_eq!(d.date(), tz.ymd(2017, 8, 9)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2017, 8, 9)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let utc_d = Utc.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert!(utc_d < d); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_with_timezone() { - let local_now = Local::now(); - let utc_now = local_now.with_timezone(&Utc); - let local_now2 = utc_now.with_timezone(&Local); - assert_eq!(local_now, local_now2); - } - - #[test] - #[allow(non_snake_case)] - fn test_datetime_rfc2822_and_rfc3339() { - let EDT = FixedOffset::east(5 * 60 * 60); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0000" - ); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); - - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) - ); - } - - #[test] - fn test_rfc3339_opts() { - use crate::SecondsFormat::*; - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); - assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); - assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); - assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); - assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); - - let ut = DateTime::::from_utc(dt.naive_utc(), Utc); - assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); - assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); - assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); - assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); - assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); - assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); - assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); - } - - #[test] - #[should_panic] - fn test_rfc3339_opts_nonexhaustive() { - use crate::SecondsFormat; - let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); - dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); - } - - #[test] - fn test_datetime_from_str() { - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - // no test for `DateTime`, we cannot verify that much. - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); - assert_eq!( - DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) - ); // ignore offset - assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert_eq!( - Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), - Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) - ); - } - - #[test] - fn test_to_string_round_trip() { - let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0); - let _dt: DateTime = dt.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(0)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_to_string_round_trip_with_local() { - let ndt = Local::now(); - let _dt: DateTime = ndt.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_format_with_local() { - // if we are not around the year boundary, local and UTC date should have the same year - let dt = Local::now().with_month(5).unwrap(); - assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_copy() { - // UTC is known to be `Copy`. - let a = Utc::now(); - let b = a; - assert_eq!(a, b); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_send() { - use std::thread; - - // UTC is known to be `Send`. - let a = Utc::now(); - thread::spawn(move || { - let _ = a; - }) - .join() - .unwrap(); - } - - #[test] - fn test_subsecond_part() { - let datetime = Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); - - assert_eq!(1, datetime.timestamp_subsec_millis()); - assert_eq!(1234, datetime.timestamp_subsec_micros()); - assert_eq!(1234567, datetime.timestamp_subsec_nanos()); - } - - #[test] - #[cfg(not(target_os = "windows"))] - fn test_from_system_time() { - use std::time::Duration; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - let nanos = 999_999_999; - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)), - UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - #[cfg(target_os = "windows")] - fn test_from_system_time() { - use std::time::Duration; - - let nanos = 999_999_000; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)), - UNIX_EPOCH - Duration::new(999_999_999, nanos) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - fn test_datetime_format_alignment() { - let datetime = Utc.ymd(2007, 1, 2); - - // Item::Literal - let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>3}", percent)); - assert_eq!("% ", format!("{:<3}", percent)); - assert_eq!(" % ", format!("{:^3}", percent)); - - // Item::Numeric - let year = datetime.format("%Y"); - assert_eq!(" 2007", format!("{:>6}", year)); - assert_eq!("2007 ", format!("{:<6}", year)); - assert_eq!(" 2007 ", format!("{:^6}", year)); - - // Item::Fixed - let tz = datetime.format("%Z"); - assert_eq!(" UTC", format!("{:>5}", tz)); - assert_eq!("UTC ", format!("{:<5}", tz)); - assert_eq!(" UTC ", format!("{:^5}", tz)); - - // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] - let ymd = datetime.format("%Y %B %d"); - let ymd_formatted = "2007 January 02"; - assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); - assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); - assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); - } - - #[test] - fn test_datetime_from_local() { - // 2000-01-12T02:00:00Z - let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - - // 2000-01-12T10:00:00+8:00:00 - let timezone_east = FixedOffset::east(8 * 60 * 60); - let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); - let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); - - // 2000-01-11T19:00:00-7:00:00 - let timezone_west = FixedOffset::west(7 * 60 * 60); - let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); - let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); - - assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); - assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); - } -} diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs new file mode 100644 index 0000000000..4dd2102377 --- /dev/null +++ b/src/datetime/mod.rs @@ -0,0 +1,1094 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 date and time with time zone. + +#[cfg(feature = "alloc")] +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::ops::{Add, Sub}; +use core::{fmt, hash, str}; +#[cfg(feature = "std")] +use std::string::ToString; +#[cfg(any(feature = "std", test))] +use std::time::{SystemTime, UNIX_EPOCH}; + +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::format::DelayedFormat; +#[cfg(feature = "unstable-locales")] +use crate::format::Locale; +use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{Fixed, Item}; +use crate::naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::oldtime::Duration as OldDuration; +use crate::Date; +use crate::{Datelike, Timelike, Weekday}; + +#[cfg(feature = "rustc-serialize")] +pub(super) mod rustc_serialize; + +/// documented at re-export site +#[cfg(feature = "serde")] +pub(super) mod serde; + +#[cfg(test)] +mod tests; + +/// Specific formatting options for seconds. This may be extended in the +/// future, so exhaustive matching in external code is not recommended. +/// +/// See the `TimeZone::to_rfc3339_opts` function for usage. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SecondsFormat { + /// Format whole seconds only, with no decimal point nor subseconds. + Secs, + + /// Use fixed 3 subsecond digits. This corresponds to + /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). + Millis, + + /// Use fixed 6 subsecond digits. This corresponds to + /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). + Micros, + + /// Use fixed 9 subsecond digits. This corresponds to + /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). + Nanos, + + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to + /// display all available non-zero sub-second digits. This corresponds to + /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). + AutoSi, + + // Do not match against this. + #[doc(hidden)] + __NonExhaustive, +} + +/// ISO 8601 combined date and time with time zone. +/// +/// There are some constructors implemented here (the `from_*` methods), but +/// the general-purpose constructors are all via the methods on the +/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. +#[derive(Clone)] +pub struct DateTime { + datetime: NaiveDateTime, + offset: Tz::Offset, +} + +/// The minimum possible `DateTime`. +pub const MIN_DATETIME: DateTime = DateTime { datetime: naive::MIN_DATETIME, offset: Utc }; +/// The maximum possible `DateTime`. +pub const MAX_DATETIME: DateTime = DateTime { datetime: naive::MAX_DATETIME, offset: Utc }; + +impl DateTime { + /// Makes a new `DateTime` with given *UTC* datetime and offset. + /// The local datetime should be constructed via the `TimeZone` trait. + /// + /// # Example + /// + /// ``` + /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; + /// + /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + /// assert_eq!(Utc.timestamp(61, 0), dt); + /// ``` + // + // note: this constructor is purposely not named to `new` to discourage the direct usage. + #[inline] + pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + DateTime { datetime, offset } + } + + /// Makes a new `DateTime` with given **local** datetime and offset that + /// presents local timezone. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// use chrono::naive::NaiveDate; + /// use chrono::offset::{Utc, FixedOffset}; + /// + /// let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); + /// let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + /// + /// let timezone_east = FixedOffset::east(8 * 60 * 60); + /// let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); + /// let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); + /// + /// let timezone_west = FixedOffset::west(7 * 60 * 60); + /// let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); + /// let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); + + /// assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); + /// assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); + /// ``` + #[inline] + pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + let datetime_utc = datetime - offset.fix(); + + DateTime { datetime: datetime_utc, offset } + } + + /// Retrieves a date component + /// + /// 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. + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let date: Date = Utc.ymd(2020, 1, 1); + /// let dt: DateTime = date.and_hms(0, 0, 0); + /// + /// assert_eq!(dt.date(), date); + /// + /// assert_eq!(dt.date().and_hms(1, 1, 1), date.and_hms(1, 1, 1)); + /// ``` + #[inline] + pub fn date(&self) -> Date { + Date::from_utc(self.naive_local().date(), self.offset.clone()) + } + + /// Retrieves the Date without an associated timezone + /// + /// [`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. + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let date: DateTime = Utc.ymd(2020, 1, 1).and_hms(0, 0, 0); + /// let other: DateTime = FixedOffset::east(23).ymd(2020, 1, 1).and_hms(0, 0, 0); + /// assert_eq!(date.date_naive(), other.date_naive()); + /// ``` + #[inline] + pub fn date_naive(&self) -> NaiveDate { + let local = self.naive_local(); + NaiveDate::from_ymd(local.year(), local.month(), local.day()) + } + + /// Retrieves a time component. + /// Unlike `date`, this is not associated to the time zone. + #[inline] + pub fn time(&self) -> NaiveTime { + self.datetime.time() + self.offset.fix() + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC + /// (aka "UNIX timestamp"). + #[inline] + pub fn timestamp(&self) -> i64 { + 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.) + /// + /// # Example + /// + /// ``` + /// use chrono::Utc; + /// use chrono::TimeZone; + /// + /// let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); + /// assert_eq!(dt.timestamp_millis(), 1_444); + /// + /// let dt = Utc.ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); + /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); + /// ``` + #[inline] + pub fn timestamp_millis(&self) -> i64 { + 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 + /// millenia, I'm curious.) + /// + /// # Example + /// + /// ``` + /// use chrono::Utc; + /// use chrono::TimeZone; + /// + /// let dt = Utc.ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); + /// assert_eq!(dt.timestamp_micros(), 1_000_444); + /// + /// let dt = Utc.ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); + /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); + /// ``` + #[inline] + pub fn timestamp_micros(&self) -> i64 { + self.datetime.timestamp_micros() + } + + /// 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.) + /// + /// # Example + /// + /// ``` + /// use chrono::Utc; + /// use chrono::TimeZone; + /// + /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// + /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); + /// ``` + #[inline] + pub fn timestamp_nanos(&self) -> i64 { + self.datetime.timestamp_nanos() + } + + /// 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 + #[inline] + pub fn timestamp_subsec_millis(&self) -> u32 { + self.datetime.timestamp_subsec_millis() + } + + /// 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 + #[inline] + pub fn timestamp_subsec_micros(&self) -> u32 { + self.datetime.timestamp_subsec_micros() + } + + /// 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 + #[inline] + pub fn timestamp_subsec_nanos(&self) -> u32 { + self.datetime.timestamp_subsec_nanos() + } + + /// Retrieves an associated offset from UTC. + #[inline] + pub fn offset(&self) -> &Tz::Offset { + &self.offset + } + + /// Retrieves an associated time zone. + #[inline] + pub fn timezone(&self) -> Tz { + TimeZone::from_offset(&self.offset) + } + + /// Changes the associated time zone. + /// This does not change the actual `DateTime` (but will change the string representation). + #[inline] + pub fn with_timezone(&self, tz: &Tz2) -> DateTime { + tz.from_utc_datetime(&self.datetime) + } + + /// Adds given `Duration` to the current date and time. + /// + /// Returns `None` when it will result in overflow. + #[inline] + pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { + let datetime = try_opt!(self.datetime.checked_add_signed(rhs)); + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Subtracts given `Duration` from the current date and time. + /// + /// Returns `None` when it will result in overflow. + #[inline] + pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { + let datetime = try_opt!(self.datetime.checked_sub_signed(rhs)); + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Subtracts another `DateTime` from the current date and time. + /// This does not overflow or underflow at all. + #[inline] + pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { + self.datetime.signed_duration_since(rhs.datetime) + } + + /// Returns a view to the naive UTC datetime. + #[inline] + pub fn naive_utc(&self) -> NaiveDateTime { + self.datetime + } + + /// Returns a view to the naive local datetime. + #[inline] + pub fn naive_local(&self) -> NaiveDateTime { + self.datetime + self.offset.fix() + } +} + +impl Default for DateTime { + fn default() -> Self { + Utc.from_utc_datetime(&NaiveDateTime::default()) + } +} + +#[cfg(feature = "clock")] +impl Default for DateTime { + fn default() -> Self { + Local.from_utc_datetime(&NaiveDateTime::default()) + } +} + +impl Default for DateTime { + fn default() -> Self { + FixedOffset::west(0).from_utc_datetime(&NaiveDateTime::default()) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by + /// this will be created with a fixed timezone offset of 0. + fn from(src: DateTime) -> Self { + src.with_timezone(&FixedOffset::east(0)) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone + /// difference. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local + /// time. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in + /// timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned + /// by this will be created with a fixed timezone offset of 0. + fn from(src: DateTime) -> Self { + src.with_timezone(&FixedOffset::east(0)) + } +} + +/// Maps the local datetime to other datetime with given conversion function. +fn map_local(dt: &DateTime, mut f: F) -> Option> +where + F: FnMut(NaiveDateTime) -> Option, +{ + f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) +} + +impl DateTime { + /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, + /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// + /// RFC 2822 is the internet message standard that specifices the + /// representation of times in HTTP and email headers. + /// + /// ``` + /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// assert_eq!( + /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), + /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) + /// ); + /// ``` + pub fn parse_from_rfc2822(s: &str) -> ParseResult> { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_datetime() + } + + /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, + /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom + /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. + pub fn parse_from_rfc3339(s: &str) -> ParseResult> { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_datetime() + } + + /// Parses a string with the specified format string and returns a new + /// [`DateTime`] with a parsed [`FixedOffset`]. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + /// + /// See also [`TimeZone::datetime_from_str`] which gives a local + /// [`DateTime`] on specific time zone. + /// + /// Note that this method *requires a timezone* in the string. See + /// [`NaiveDateTime::parse_from_str`] + /// for a version that does not require a timezone in the to-be-parsed str. + /// + /// # Example + /// + /// ```rust + /// use chrono::{DateTime, FixedOffset, TimeZone}; + /// + /// let dt = DateTime::parse_from_str( + /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); + /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime() + } +} + +impl DateTime +where + Tz::Offset: fmt::Display, +{ + /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + #[cfg(any(feature = "alloc", feature = "std", test))] + pub fn to_rfc2822(&self) -> String { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + self.format_with_items(ITEMS.iter()).to_string() + } + + /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(any(feature = "alloc", feature = "std", test))] + pub fn to_rfc3339(&self) -> String { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; + self.format_with_items(ITEMS.iter()).to_string() + } + + /// Return an RFC 3339 and ISO 8601 date and time string with subseconds + /// formatted as per a `SecondsFormat`. + /// + /// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as + /// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use + /// [`Fixed::TimezoneOffsetColon`] + /// + /// # Examples + /// + /// ```rust + /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; + /// let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_829); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), + /// "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), + /// "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T18:30:09Z"); + /// + /// let pst = FixedOffset::east(8 * 60 * 60); + /// let dt = pst.ymd(2018, 1, 26).and_hms_micro(10, 30, 9, 453_829); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T10:30:09+08:00"); + /// ``` + #[cfg(any(feature = "alloc", feature = "std", test))] + pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { + use crate::format::Numeric::*; + use crate::format::Pad::Zero; + use crate::SecondsFormat::*; + + debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); + + const PREFIX: &[Item<'static>] = &[ + Item::Numeric(Year, Zero), + Item::Literal("-"), + Item::Numeric(Month, Zero), + Item::Literal("-"), + Item::Numeric(Day, Zero), + Item::Literal("T"), + Item::Numeric(Hour, Zero), + Item::Literal(":"), + Item::Numeric(Minute, Zero), + Item::Literal(":"), + Item::Numeric(Second, Zero), + ]; + + let ssitem = match secform { + Secs => None, + Millis => Some(Item::Fixed(Fixed::Nanosecond3)), + Micros => Some(Item::Fixed(Fixed::Nanosecond6)), + Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), + AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), + __NonExhaustive => unreachable!(), + }; + + let tzitem = Item::Fixed(if use_z { + Fixed::TimezoneOffsetColonZ + } else { + Fixed::TimezoneOffsetColon + }); + + match ssitem { + None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), + Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), + } + } + + /// Formats the combined date and time with the specified formatting items. + #[cfg(any(feature = "alloc", feature = "std", test))] + #[inline] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.naive_local(); + DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) + } + + /// Formats the combined date and time with the specified format string. + /// See the [`crate::format::strftime`] module + /// on the supported escape sequences. + /// + /// # Example + /// ```rust + /// use chrono::prelude::*; + /// + /// let date_time: DateTime = Utc.ymd(2017, 04, 02).and_hms(12, 50, 32); + /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); + /// assert_eq!(formatted, "02/04/2017 12:50"); + /// ``` + #[cfg(any(feature = "alloc", feature = "std", test))] + #[inline] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Formats the combined date and time with the specified formatting items and locale. + #[cfg(feature = "unstable-locales")] + #[inline] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.naive_local(); + DelayedFormat::new_with_offset_and_locale( + Some(local.date()), + Some(local.time()), + &self.offset, + items, + locale, + ) + } + + /// Formats the combined date and time with the specified format string and + /// locale. + /// + /// See the [`::format::strftime`] module on the supported escape + /// sequences. + #[cfg(feature = "unstable-locales")] + #[inline] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } +} + +impl Datelike for DateTime { + #[inline] + fn year(&self) -> i32 { + self.naive_local().year() + } + #[inline] + fn month(&self) -> u32 { + self.naive_local().month() + } + #[inline] + fn month0(&self) -> u32 { + self.naive_local().month0() + } + #[inline] + fn day(&self) -> u32 { + self.naive_local().day() + } + #[inline] + fn day0(&self) -> u32 { + self.naive_local().day0() + } + #[inline] + fn ordinal(&self) -> u32 { + self.naive_local().ordinal() + } + #[inline] + fn ordinal0(&self) -> u32 { + self.naive_local().ordinal0() + } + #[inline] + fn weekday(&self) -> Weekday { + self.naive_local().weekday() + } + #[inline] + fn iso_week(&self) -> IsoWeek { + self.naive_local().iso_week() + } + + #[inline] + fn with_year(&self, year: i32) -> Option> { + map_local(self, |datetime| datetime.with_year(year)) + } + + #[inline] + fn with_month(&self, month: u32) -> Option> { + map_local(self, |datetime| datetime.with_month(month)) + } + + #[inline] + fn with_month0(&self, month0: u32) -> Option> { + map_local(self, |datetime| datetime.with_month0(month0)) + } + + #[inline] + fn with_day(&self, day: u32) -> Option> { + map_local(self, |datetime| datetime.with_day(day)) + } + + #[inline] + fn with_day0(&self, day0: u32) -> Option> { + map_local(self, |datetime| datetime.with_day0(day0)) + } + + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal(ordinal)) + } + + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) + } +} + +impl Timelike for DateTime { + #[inline] + fn hour(&self) -> u32 { + self.naive_local().hour() + } + #[inline] + fn minute(&self) -> u32 { + self.naive_local().minute() + } + #[inline] + fn second(&self) -> u32 { + self.naive_local().second() + } + #[inline] + fn nanosecond(&self) -> u32 { + self.naive_local().nanosecond() + } + + #[inline] + fn with_hour(&self, hour: u32) -> Option> { + map_local(self, |datetime| datetime.with_hour(hour)) + } + + #[inline] + fn with_minute(&self, min: u32) -> Option> { + map_local(self, |datetime| datetime.with_minute(min)) + } + + #[inline] + fn with_second(&self, sec: u32) -> Option> { + map_local(self, |datetime| datetime.with_second(sec)) + } + + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option> { + map_local(self, |datetime| datetime.with_nanosecond(nano)) + } +} + +// we need them as automatic impls cannot handle associated types +impl Copy for DateTime where ::Offset: Copy {} +unsafe impl Send for DateTime where ::Offset: Send {} + +impl PartialEq> for DateTime { + fn eq(&self, other: &DateTime) -> bool { + self.datetime == other.datetime + } +} + +impl Eq for DateTime {} + +impl PartialOrd> for DateTime { + /// Compare two DateTimes based on their true time, ignoring time zones + /// + /// # Example + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let earlier = Utc.ymd(2015, 5, 15).and_hms(2, 0, 0).with_timezone(&FixedOffset::west(1 * 3600)); + /// let later = Utc.ymd(2015, 5, 15).and_hms(3, 0, 0).with_timezone(&FixedOffset::west(5 * 3600)); + /// + /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); + /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); + /// + /// assert!(later > earlier); + /// ``` + fn partial_cmp(&self, other: &DateTime) -> Option { + self.datetime.partial_cmp(&other.datetime) + } +} + +impl Ord for DateTime { + fn cmp(&self, other: &DateTime) -> Ordering { + self.datetime.cmp(&other.datetime) + } +} + +impl hash::Hash for DateTime { + fn hash(&self, state: &mut H) { + self.datetime.hash(state) + } +} + +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: OldDuration) -> DateTime { + self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") + } +} + +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: OldDuration) -> DateTime { + self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") + } +} + +impl Sub> for DateTime { + type Output = OldDuration; + + #[inline] + fn sub(self, rhs: DateTime) -> OldDuration { + self.signed_duration_since(rhs) + } +} + +impl fmt::Debug for DateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}{:?}", self.naive_local(), self.offset) + } +} + +impl fmt::Display for DateTime +where + Tz::Offset: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.naive_local(), self.offset) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are acepted as the separator between the date and time +/// parts. Additional spaces are allowed between each component. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Utc}; +/// "2012-12-12T12:12:12Z".parse::>(); +/// "2012-12-12 12:12:12Z".parse::>(); +/// "2012- 12-12T12: 12:12Z".parse::>(); +/// ``` +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are acepted as the separator between the date and time +/// parts. Additional spaces are allowed between each component. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Local}; +/// "2012-12-12T12:12:12Z".parse::>(); +/// "2012-12-12 12:12:12Z".parse::>(); +/// "2012- 12-12T12: 12:12Z".parse::>(); +/// ``` +#[cfg(feature = "clock")] +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Local)) + } +} + +#[cfg(any(feature = "std", test))] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { + Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), + Err(e) => { + // unlikely but should be handled + let dur = e.duration(); + let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); + if nsec == 0 { + (-sec, 0) + } else { + (-sec - 1, 1_000_000_000 - nsec) + } + } + }; + Utc.timestamp(sec, nsec) + } +} + +#[cfg(feature = "clock")] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + DateTime::::from(t).with_timezone(&Local) + } +} + +#[cfg(any(feature = "std", test))] +impl From> for SystemTime { + fn from(dt: DateTime) -> SystemTime { + use std::time::Duration; + + let sec = dt.timestamp(); + let nsec = dt.timestamp_subsec_nanos(); + if sec < 0 { + // unlikely but should be handled + UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) + } else { + UNIX_EPOCH + Duration::new(sec as u64, nsec) + } + } +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +impl From for DateTime { + fn from(date: js_sys::Date) -> DateTime { + DateTime::::from(&date) + } +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +impl From<&js_sys::Date> for DateTime { + fn from(date: &js_sys::Date) -> DateTime { + Utc.timestamp_millis(date.get_time() as i64) + } +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +impl From> for js_sys::Date { + /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, + /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 + /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. + fn from(date: DateTime) -> js_sys::Date { + let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); + js_sys::Date::new(&js_millis) + } +} + +#[test] +fn test_auto_conversion() { + let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); + let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0); + let utc_dt2: DateTime = cdt_dt.into(); + assert_eq!(utc_dt, utc_dt2); +} + +#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) +where + FUtc: Fn(&DateTime) -> Result, + FFixed: Fn(&DateTime) -> Result, + E: ::core::fmt::Debug, +{ + assert_eq!( + to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + Some(r#""2014-07-24T12:34:06Z""#.into()) + ); + + assert_eq!( + to_string_fixed(&FixedOffset::east(3660).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + Some(r#""2014-07-24T12:34:06+01:01""#.into()) + ); + assert_eq!( + to_string_fixed(&FixedOffset::east(3650).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) + ); +} + +#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))] +fn test_decodable_json( + utc_from_str: FUtc, + fixed_from_str: FFixed, + local_from_str: FLocal, +) where + FUtc: Fn(&str) -> Result, E>, + FFixed: Fn(&str) -> Result, E>, + FLocal: Fn(&str) -> Result, E>, + E: ::core::fmt::Debug, +{ + // should check against the offset as well (the normal DateTime comparison will ignore them) + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + assert_eq!( + norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), + norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) + ); + assert_eq!( + norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), + norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) + ); + + assert_eq!( + norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), + norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))) + ); + assert_eq!( + norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), + norm(&Some(FixedOffset::east(60 * 60 + 23 * 60).ymd(2014, 7, 24).and_hms(13, 57, 6))) + ); + + // we don't know the exact local offset but we can check that + // the conversion didn't change the instant itself + assert_eq!( + local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), + Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) + ); + assert_eq!( + local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), + Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) + ); + + assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); +} + +#[cfg(all(test, feature = "clock", feature = "rustc-serialize"))] +fn test_decodable_json_timestamps( + utc_from_str: FUtc, + fixed_from_str: FFixed, + local_from_str: FLocal, +) where + FUtc: Fn(&str) -> Result, E>, + FFixed: Fn(&str) -> Result, E>, + FLocal: Fn(&str) -> Result, E>, + E: ::core::fmt::Debug, +{ + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + assert_eq!( + norm(&utc_from_str("0").ok().map(DateTime::from)), + norm(&Some(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0))) + ); + assert_eq!( + norm(&utc_from_str("-1").ok().map(DateTime::from)), + norm(&Some(Utc.ymd(1969, 12, 31).and_hms(23, 59, 59))) + ); + + assert_eq!( + norm(&fixed_from_str("0").ok().map(DateTime::from)), + norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))) + ); + assert_eq!( + norm(&fixed_from_str("-1").ok().map(DateTime::from)), + norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))) + ); + + assert_eq!( + *fixed_from_str("0").expect("0 timestamp should parse"), + Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + ); + assert_eq!( + *local_from_str("-1").expect("-1 timestamp should parse"), + Utc.ymd(1969, 12, 31).and_hms(23, 59, 59) + ); +} diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs new file mode 100644 index 0000000000..5ff43cfdcc --- /dev/null +++ b/src/datetime/rustc_serialize.rs @@ -0,0 +1,119 @@ +use super::DateTime; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; +use core::fmt; +use core::ops::Deref; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; + +impl Encodable for DateTime { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + format!("{:?}", self).encode(s) + } +} + +// lik? function to convert a LocalResult into a serde-ish Result +fn from(me: LocalResult, d: &mut D) -> Result +where + D: Decoder, + T: fmt::Display, +{ + match me { + LocalResult::None => Err(d.error("value is not a legal timestamp")), + LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), + LocalResult::Single(val) => Ok(val), + } +} + +impl Decodable for DateTime { + fn decode(d: &mut D) -> Result, D::Error> { + d.read_str()?.parse::>().map_err(|_| d.error("invalid date and time")) + } +} + +#[allow(deprecated)] +impl Decodable for TsSeconds { + #[allow(deprecated)] + fn decode(d: &mut D) -> Result, D::Error> { + from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) + } +} + +impl Decodable for DateTime { + fn decode(d: &mut D) -> Result, D::Error> { + d.read_str()? + .parse::>() + .map(|dt| dt.with_timezone(&Utc)) + .map_err(|_| d.error("invalid date and time")) + } +} + +/// A [`DateTime`] that can be deserialized from a timestamp +/// +/// A timestamp here is seconds since the epoch +#[derive(Debug)] +pub struct TsSeconds(DateTime); + +#[allow(deprecated)] +impl From> for DateTime { + /// Pull the inner DateTime out + #[allow(deprecated)] + fn from(obj: TsSeconds) -> DateTime { + obj.0 + } +} + +#[allow(deprecated)] +impl Deref for TsSeconds { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[allow(deprecated)] +impl Decodable for TsSeconds { + fn decode(d: &mut D) -> Result, D::Error> { + from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) + } +} + +#[cfg(feature = "clock")] +impl Decodable for DateTime { + fn decode(d: &mut D) -> Result, D::Error> { + match d.read_str()?.parse::>() { + Ok(dt) => Ok(dt.with_timezone(&Local)), + Err(_) => Err(d.error("invalid date and time")), + } + } +} + +#[cfg(feature = "clock")] +#[allow(deprecated)] +impl Decodable for TsSeconds { + #[allow(deprecated)] + fn decode(d: &mut D) -> Result, D::Error> { + from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(|dt| TsSeconds(dt.with_timezone(&Local))) + } +} + +#[cfg(test)] +use rustc_serialize::json; + +#[test] +fn test_encodable() { + super::test_encodable_json(json::encode, json::encode); +} + +#[cfg(feature = "clock")] +#[test] +fn test_decodable() { + super::test_decodable_json(json::decode, json::decode, json::decode); +} + +#[cfg(feature = "clock")] +#[test] +fn test_decodable_timestamps() { + super::test_decodable_json_timestamps(json::decode, json::decode, json::decode); +} diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs new file mode 100644 index 0000000000..1f9714536a --- /dev/null +++ b/src/datetime/serde.rs @@ -0,0 +1,1368 @@ +use core::fmt; +use serde::{de, ser}; + +use super::DateTime; +use crate::naive::datetime::serde::serde_from; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, TimeZone, Utc}; + +#[doc(hidden)] +#[derive(Debug)] +pub struct SecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct NanoSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MicroSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MilliSecondsTimestampVisitor; + +/// Serialize into a rfc3339 time string +/// +/// See [the `serde` module](./serde/index.html) for alternate +/// serializations. +impl ser::Serialize for DateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + // Debug formatting is correct RFC3339, and it allows Zulu. + serializer.collect_str(&FormatWrapped { inner: &self }) + } +} + +struct DateTimeVisitor; + +impl<'de> de::Visitor<'de> for DateTimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date and time string or a unix timestamp") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +/// Deserialize a value that optionally includes a timezone offset in its +/// string representation +/// +/// The value to be deserialized must be an rfc3339 string. +/// +/// See [the `serde` module](./serde/index.html) for alternate +/// deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor) + } +} + +/// Deserialize into a UTC value +/// +/// The value to be deserialized must be an rfc3339 string. +/// +/// See [the `serde` module](./serde/index.html) for alternate +/// deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Deserialize a value that includes no timezone in its string +/// representation +/// +/// The value to be deserialized must be an rfc3339 string. +/// +/// See [the `serde` module](./serde/index.html) for alternate +/// serialization formats. +#[cfg(feature = "clock")] +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) + } +} + +/// Ser/de to/from timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: DateTime +/// } +/// +/// # fn example() -> Result { +/// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; + + use super::{serde_from, NanoSecondsTimestampVisitor}; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_nanos()) + } + + /// Deserialize a [`DateTime`] from a nanosecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor) + } + + impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32), + &value, + ) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32), + &value, + ) + } + } +} + +/// Ser/de to/from timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option> +/// } +/// +/// # fn example() -> Result { +/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::{DateTime, Utc}; + + use super::NanoSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: DateTime +/// } +/// +/// # fn example() -> Result { +/// let time = Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use super::{serde_from, MicroSecondsTimestampVisitor}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_micros()) + } + + /// Deserialize a `DateTime` from a microsecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor) + } + + impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt(value / 1_000_000, ((value % 1_000_000) * 1_000) as u32), + &value, + ) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32), + &value, + ) + } + } +} + +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option> +/// } +/// +/// # fn example() -> Result { +/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MicroSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a microsecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: DateTime +/// } +/// +/// # fn example() -> Result { +/// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use super::{serde_from, MilliSecondsTimestampVisitor}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_millis()) + } + + /// Deserialize a `DateTime` from a millisecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) + } + + impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32), &value) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), + &value, + ) + } + } +} + +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option> +/// } +/// +/// # fn example() -> Result { +/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MilliSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::prelude::*; + /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// #[serde(untagged)] + /// enum E { + /// V(T), + /// } + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// struct S { + /// #[serde(default, deserialize_with = "from_milli_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result<(), serde_json::Error> { + /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); + /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; + /// assert_eq!(s, E::V(S { time: None })); + /// let t: E = serde_json::from_str(r#"{}"#)?; + /// assert_eq!(t, E::V(S { time: None })); + /// # Ok(()) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: DateTime +/// } +/// +/// # fn example() -> Result { +/// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use super::{serde_from, SecondsTimestampVisitor}; + use crate::offset::TimeZone; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp()) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor) + } + + impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value, 0), &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value as i64, 0), &value) + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::{TimeZone, DateTime, Utc}; +/// use chrono::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option> +/// } +/// +/// # fn example() -> Result { +/// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::SecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a seconds timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option> + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +#[cfg(test)] +extern crate bincode; +#[cfg(test)] +extern crate serde_json; + +#[test] +fn test_serde_serialize() { + super::test_encodable_json(self::serde_json::to_string, self::serde_json::to_string); +} + +#[cfg(feature = "clock")] +#[test] +fn test_serde_deserialize() { + super::test_decodable_json( + |input| self::serde_json::from_str(input), + |input| self::serde_json::from_str(input), + |input| self::serde_json::from_str(input), + ); +} + +#[test] +fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use self::bincode::{deserialize, serialize, Infinite}; + + let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); + let encoded = serialize(&dt, Infinite).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 new file mode 100644 index 0000000000..c2ca35591a --- /dev/null +++ b/src/datetime/tests.rs @@ -0,0 +1,437 @@ +use super::DateTime; +use crate::naive::{NaiveDate, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, TimeZone, Utc}; +use crate::oldtime::Duration; +#[cfg(feature = "clock")] +use crate::Datelike; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[test] +#[allow(non_snake_case)] +fn test_datetime_offset() { + let Est = FixedOffset::west(5 * 60 * 60); + let Edt = FixedOffset::west(4 * 60 * 60); + let Kst = FixedOffset::east(9 * 60 * 60); + + assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); + assert_eq!( + format!("{}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), + "2014-05-06 07:08:09 -04:00" + ); + assert_eq!( + format!("{}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), + "2014-05-06 07:08:09 +09:00" + ); + assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); + assert_eq!( + format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), + "2014-05-06T07:08:09-04:00" + ); + assert_eq!( + format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), + "2014-05-06T07:08:09+09:00" + ); + + // edge cases + assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); + assert_eq!( + format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), + "2014-05-06T00:00:00-04:00" + ); + assert_eq!( + format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), + "2014-05-06T00:00:00+09:00" + ); + assert_eq!( + format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), + "2014-05-06T23:59:59Z" + ); + assert_eq!( + format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), + "2014-05-06T23:59:59-04:00" + ); + assert_eq!( + format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), + "2014-05-06T23:59:59+09:00" + ); + + let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); + assert_eq!(dt, Edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); + assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); + assert_eq!( + dt.signed_duration_since(Edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), + Duration::seconds(-7 * 3600 - 3 * 60 - 3) + ); + + assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); + assert_eq!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Edt); + assert!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != Est); +} + +#[test] +fn test_datetime_date_and_time() { + let tz = FixedOffset::east(5 * 60 * 60); + let d = tz.ymd(2014, 5, 6).and_hms(7, 8, 9); + assert_eq!(d.time(), NaiveTime::from_hms(7, 8, 9)); + assert_eq!(d.date(), tz.ymd(2014, 5, 6)); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2014, 5, 6)); + assert_eq!(d.date().and_time(d.time()), Some(d)); + + let tz = FixedOffset::east(4 * 60 * 60); + let d = tz.ymd(2016, 5, 4).and_hms(3, 2, 1); + assert_eq!(d.time(), NaiveTime::from_hms(3, 2, 1)); + assert_eq!(d.date(), tz.ymd(2016, 5, 4)); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2016, 5, 4)); + assert_eq!(d.date().and_time(d.time()), Some(d)); + + let tz = FixedOffset::west(13 * 60 * 60); + let d = tz.ymd(2017, 8, 9).and_hms(12, 34, 56); + assert_eq!(d.time(), NaiveTime::from_hms(12, 34, 56)); + assert_eq!(d.date(), tz.ymd(2017, 8, 9)); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2017, 8, 9)); + assert_eq!(d.date().and_time(d.time()), Some(d)); + + let utc_d = Utc.ymd(2017, 8, 9).and_hms(12, 34, 56); + assert!(utc_d < d); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_with_timezone() { + let local_now = Local::now(); + let utc_now = local_now.with_timezone(&Utc); + let local_now2 = utc_now.with_timezone(&Local); + assert_eq!(local_now, local_now2); +} + +#[test] +#[allow(non_snake_case)] +fn test_datetime_rfc2822_and_rfc3339() { + let EDT = FixedOffset::east(5 * 60 * 60); + assert_eq!( + Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0000" + ); + assert_eq!( + Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0500" + ); + assert_eq!( + EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) + ); +} + +#[test] +fn test_rfc3339_opts() { + use crate::SecondsFormat::*; + let pst = FixedOffset::east(8 * 60 * 60); + let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); + assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); + assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); + assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); + assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); + + let ut = DateTime::::from_utc(dt.naive_utc(), Utc); + assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); + assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); + assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); + assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); + assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); + assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); + assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); +} + +#[test] +#[should_panic] +fn test_rfc3339_opts_nonexhaustive() { + use crate::SecondsFormat; + let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); + dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); +} + +#[test] +fn test_datetime_from_str() { + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_datetime_parse_from_str() { + let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); + assert_eq!( + DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) + ); // ignore offset + assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset + assert!(DateTime::parse_from_str( + "Fri, 09 Aug 2013 23:54:35 GMT", + "%a, %d %b %Y %H:%M:%S GMT" + ) + .is_err()); + assert_eq!( + Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), + Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) + ); +} + +#[test] +fn test_to_string_round_trip() { + let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0); + let _dt: DateTime = dt.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600)); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east(0)); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_to_string_round_trip_with_local() { + let ndt = Local::now(); + let _dt: DateTime = ndt.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_format_with_local() { + // if we are not around the year boundary, local and UTC date should have the same year + let dt = Local::now().with_month(5).unwrap(); + assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_is_copy() { + // UTC is known to be `Copy`. + let a = Utc::now(); + let b = a; + assert_eq!(a, b); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_is_send() { + use std::thread; + + // UTC is known to be `Send`. + let a = Utc::now(); + thread::spawn(move || { + let _ = a; + }) + .join() + .unwrap(); +} + +#[test] +fn test_subsecond_part() { + let datetime = Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); + + assert_eq!(1, datetime.timestamp_subsec_millis()); + assert_eq!(1234, datetime.timestamp_subsec_micros()); + assert_eq!(1234567, datetime.timestamp_subsec_nanos()); +} + +#[test] +#[cfg(not(target_os = "windows"))] +fn test_from_system_time() { + use std::time::Duration; + + let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let nanos = 999_999_999; + + // SystemTime -> DateTime + assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); + assert_eq!( + DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), + Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) + ); + assert_eq!( + DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), + Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1) + ); + + // DateTime -> SystemTime + assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); + assert_eq!( + SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), + UNIX_EPOCH + Duration::new(999_999_999, nanos) + ); + assert_eq!( + SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)), + UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) + ); + + // DateTime -> SystemTime (via `with_timezone`) + #[cfg(feature = "clock")] + { + assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); + } + assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); + assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); +} + +#[test] +#[cfg(target_os = "windows")] +fn test_from_system_time() { + use std::time::Duration; + + let nanos = 999_999_000; + + let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + + // SystemTime -> DateTime + assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); + assert_eq!( + DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), + Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) + ); + assert_eq!( + DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), + Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000) + ); + + // DateTime -> SystemTime + assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); + assert_eq!( + SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), + UNIX_EPOCH + Duration::new(999_999_999, nanos) + ); + assert_eq!( + SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)), + UNIX_EPOCH - Duration::new(999_999_999, nanos) + ); + + // DateTime -> SystemTime (via `with_timezone`) + #[cfg(feature = "clock")] + { + assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); + } + assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); + assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); +} + +#[test] +fn test_datetime_format_alignment() { + let datetime = Utc.ymd(2007, 1, 2); + + // Item::Literal + let percent = datetime.format("%%"); + assert_eq!(" %", format!("{:>3}", percent)); + assert_eq!("% ", format!("{:<3}", percent)); + assert_eq!(" % ", format!("{:^3}", percent)); + + // Item::Numeric + let year = datetime.format("%Y"); + assert_eq!(" 2007", format!("{:>6}", year)); + assert_eq!("2007 ", format!("{:<6}", year)); + assert_eq!(" 2007 ", format!("{:^6}", year)); + + // Item::Fixed + let tz = datetime.format("%Z"); + assert_eq!(" UTC", format!("{:>5}", tz)); + assert_eq!("UTC ", format!("{:<5}", tz)); + assert_eq!(" UTC ", format!("{:^5}", tz)); + + // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] + let ymd = datetime.format("%Y %B %d"); + let ymd_formatted = "2007 January 02"; + assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); + assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); + assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); +} + +#[test] +fn test_datetime_from_local() { + // 2000-01-12T02:00:00Z + let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + + // 2000-01-12T10:00:00+8:00:00 + let timezone_east = FixedOffset::east(8 * 60 * 60); + let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); + let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); + + // 2000-01-11T19:00:00-7:00:00 + let timezone_west = FixedOffset::west(7 * 60 * 60); + let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); + let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); + + assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); + assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); +} From 0cd8fa86530e0889d8a2cf97c1b1c5398e952e21 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 22:01:18 -0700 Subject: [PATCH 020/999] Split naive::datetime module into different files --- src/naive/{datetime.rs => datetime/mod.rs} | 953 +-------------------- src/naive/datetime/rustc_serialize.rs | 71 ++ src/naive/datetime/serde.rs | 597 +++++++++++++ src/naive/datetime/tests.rs | 242 ++++++ 4 files changed, 920 insertions(+), 943 deletions(-) rename src/naive/{datetime.rs => datetime/mod.rs} (64%) create mode 100644 src/naive/datetime/rustc_serialize.rs create mode 100644 src/naive/datetime/serde.rs create mode 100644 src/naive/datetime/tests.rs diff --git a/src/naive/datetime.rs b/src/naive/datetime/mod.rs similarity index 64% rename from src/naive/datetime.rs rename to src/naive/datetime/mod.rs index 7e41f3dca5..6d37af442e 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime/mod.rs @@ -21,6 +21,16 @@ use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Timelike, Weekday}; +#[cfg(feature = "rustc-serialize")] +pub(super) mod rustc_serialize; + +/// Tools to help serializing/deserializing `NaiveDateTime`s +#[cfg(feature = "serde")] +pub(crate) mod serde; + +#[cfg(test)] +mod tests; + /// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` /// will always overflow the addition with any date and time type. /// @@ -1629,946 +1639,3 @@ where "should parse integers as timestamps" ); } - -#[cfg(feature = "rustc-serialize")] -pub(super) mod rustc_serialize { - use super::NaiveDateTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - use std::ops::Deref; - - impl Encodable for NaiveDateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDateTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) - } - } - - /// A `DateTime` that can be deserialized from a seconds-based timestamp - #[derive(Debug)] - #[deprecated( - since = "1.4.2", - note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" - )] - pub struct TsSeconds(NaiveDateTime); - - #[allow(deprecated)] - impl From for NaiveDateTime { - /// Pull the internal NaiveDateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> NaiveDateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = NaiveDateTime; - - #[allow(deprecated)] - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result { - Ok(TsSeconds( - NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) - .ok_or_else(|| d.error("invalid timestamp"))?, - )) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } - - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamp(json::decode); - } -} - -/// Tools to help serializing/deserializing `NaiveDateTime`s -#[cfg(feature = "serde")] -pub(crate) mod serde { - use core::fmt; - use serde::{de, ser}; - - use super::NaiveDateTime; - use crate::offset::LocalResult; - - /// Serialize a `NaiveDateTime` as an RFC 3339 string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - impl ser::Serialize for NaiveDateTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct NaiveDateTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date and time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveDateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveDateTimeVisitor) - } - } - - /// Used to serialize/deserialize from nanosecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serde::{de, ser}; - - use super::ne_timestamp; - use crate::NaiveDateTime; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a `DateTime` from a nanoseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromNanoSecondsVisitor) - } - - struct NaiveDateTimeFromNanoSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromNanoSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value / 1_000_000_000, - (value % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value as i64 / 1_000_000_000, - (value as i64 % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from millisecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serde::{de, ser}; - - use super::ne_timestamp; - use crate::NaiveDateTime; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a milliseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromMilliSecondsVisitor) - } - - struct NaiveDateTimeFromMilliSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromMilliSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - (value / 1000) as i64, - ((value % 1000) * 1_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from second-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serde::{de, ser}; - - use super::ne_timestamp; - use crate::NaiveDateTime; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NaiveDateTimeFromSecondsVisitor) - } - - struct NaiveDateTimeFromSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value as i64, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - #[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::naive::NaiveDate; - use bincode::{deserialize, serialize, Infinite}; - - let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); - let encoded = serialize(&dt, Infinite).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, Infinite}; - 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.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; - let bytes: Vec = serialize(&expected, Infinite).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 - E: de::Error, - V: fmt::Display, - T: fmt::Display, - { - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) - } - LocalResult::Single(val) => Ok(val), - } - } - - #[cfg(feature = "serde")] - enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, - } - - /// Construct a [`SerdeError::NonExistent`] - #[cfg(feature = "serde")] - fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } - } - - #[cfg(feature = "serde")] - impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) - } - } - - // impl core::error::Error for SerdeError {} - #[cfg(feature = "serde")] - impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SerdeError::NonExistent { timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) - } - SerdeError::Ambiguous { timestamp, min, max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), - } - } - } -} - -#[cfg(test)] -mod tests { - use super::NaiveDateTime; - use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE}; - use crate::oldtime::Duration; - use crate::Datelike; - use std::i64; - - #[test] - fn test_datetime_from_timestamp() { - let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); - assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); - assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); - assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); - assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); - assert_eq!(from_timestamp(i64::MIN), None); - assert_eq!(from_timestamp(i64::MAX), None); - } - - #[test] - fn test_datetime_add() { - fn check( - (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: Duration, - result: Option<(i32, u32, u32, u32, u32, u32)>, - ) { - let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let sum = - result.map(|(y, m, d, h, n, s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - } - - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(3600 + 60 + 1), - Some((2014, 5, 6, 8, 9, 10)), - ); - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(-(3600 + 60 + 1)), - Some((2014, 5, 6, 6, 7, 8)), - ); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - - // overflow check - // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. - // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = MAX_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((MAX_DATE.year(), 12, 31, 0, 0, 0))); - check( - (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + Duration::seconds(86399), - Some((MAX_DATE.year(), 12, 31, 23, 59, 59)), - ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); - - let min_days_from_year_0 = MIN_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((MIN_DATE.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); - } - - #[test] - fn test_datetime_sub() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let since = NaiveDateTime::signed_duration_since; - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::zero() - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::seconds(1) - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(-1) - ); - assert_eq!( - since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(86399) - ); - assert_eq!( - since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - Duration::seconds(999_999_999) - ); - } - - #[test] - fn test_datetime_addassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += Duration::days(10); - assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); - } - - #[test] - fn test_datetime_subassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= Duration::days(10); - assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); - } - - #[test] - fn test_datetime_timestamp() { - let to_timestamp = - |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s).timestamp(); - assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); - assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); - assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); - } - - #[test] - fn test_datetime_from_str() { - // valid cases - let valid = [ - "2015-2-18T23:16:9.15", - "-77-02-18T23:16:09", - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let ymdhmsn = - |y, m, d, h, n, s, nano| NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano); - assert_eq!( - NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56)) - ); // ignore offset - assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), - Ok(ymdhms(2015, 2, 2, 0, 0, 0)) - ); - assert_eq!( - NaiveDateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ), - Ok(ymdhms(2013, 8, 9, 23, 54, 35)) - ); - assert!(NaiveDateTime::parse_from_str( - "Sat, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); - assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient - assert_eq!( - NaiveDateTime::parse_from_str("1441497364", "%s"), - Ok(ymdhms(2015, 9, 5, 23, 56, 4)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), - Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), - Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), - Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), - Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) - ); - } - - #[test] - fn test_datetime_format() { - let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321); - assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); - assert_eq!(dt.format("%s").to_string(), "1283929614"); - assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // a horror of leap second: coming near to you. - let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000); - assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); - assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. - } - - #[test] - fn test_datetime_add_sub_invariant() { - // issue #37 - let base = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); - let t = -946684799990000; - let time = base + Duration::microseconds(t); - assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); - } - - #[test] - fn test_nanosecond_range() { - const A_BILLION: i64 = 1_000_000_000; - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - - let minimum = "1677-09-21T00:12:44.000000000"; - let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - } -} diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs new file mode 100644 index 0000000000..c65017d2a3 --- /dev/null +++ b/src/naive/datetime/rustc_serialize.rs @@ -0,0 +1,71 @@ +use super::NaiveDateTime; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +use std::ops::Deref; + +impl Encodable for NaiveDateTime { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + format!("{:?}", self).encode(s) + } +} + +impl Decodable for NaiveDateTime { + fn decode(d: &mut D) -> Result { + d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) + } +} + +/// A `DateTime` that can be deserialized from a seconds-based timestamp +#[derive(Debug)] +#[deprecated( + since = "1.4.2", + note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" +)] +pub struct TsSeconds(NaiveDateTime); + +#[allow(deprecated)] +impl From for NaiveDateTime { + /// Pull the internal NaiveDateTime out + #[allow(deprecated)] + fn from(obj: TsSeconds) -> NaiveDateTime { + obj.0 + } +} + +#[allow(deprecated)] +impl Deref for TsSeconds { + type Target = NaiveDateTime; + + #[allow(deprecated)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[allow(deprecated)] +impl Decodable for TsSeconds { + #[allow(deprecated)] + fn decode(d: &mut D) -> Result { + Ok(TsSeconds( + NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) + .ok_or_else(|| d.error("invalid timestamp"))?, + )) + } +} + +#[cfg(test)] +use rustc_serialize::json; + +#[test] +fn test_encodable() { + super::test_encodable_json(json::encode); +} + +#[test] +fn test_decodable() { + super::test_decodable_json(json::decode); +} + +#[test] +fn test_decodable_timestamps() { + super::test_decodable_json_timestamp(json::decode); +} diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs new file mode 100644 index 0000000000..3caaab8af7 --- /dev/null +++ b/src/naive/datetime/serde.rs @@ -0,0 +1,597 @@ +use core::fmt; +use serde::{de, ser}; + +use super::NaiveDateTime; +use crate::offset::LocalResult; + +/// Serialize a `NaiveDateTime` as an RFC 3339 string +/// +/// See [the `serde` module](./serde/index.html) for alternate +/// serialization formats. +impl ser::Serialize for NaiveDateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + serializer.collect_str(&FormatWrapped { inner: &self }) + } +} + +struct NaiveDateTimeVisitor; + +impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date and time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveDateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveDateTimeVisitor) + } +} + +/// Used to serialize/deserialize from nanosecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # extern crate serde_json; +/// # extern crate serde; +/// # extern crate chrono; +/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// use chrono::naive::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: NaiveDateTime +/// } +/// +/// # fn example() -> Result { +/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use super::ne_timestamp; + use crate::NaiveDateTime; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # #[macro_use] extern crate serde; + /// # extern crate chrono; + /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use serde::Serialize; + /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_nanos()) + } + + /// Deserialize a `DateTime` from a nanoseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate serde; + /// # extern crate chrono; + /// # use chrono::{NaiveDateTime, Utc}; + /// # use serde::Deserialize; + /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NaiveDateTimeFromNanoSecondsVisitor) + } + + struct NaiveDateTimeFromNanoSecondsVisitor; + + impl<'de> de::Visitor<'de> for NaiveDateTimeFromNanoSecondsVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt( + value as i64 / 1_000_000_000, + (value as i64 % 1_000_000_000) as u32, + ) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + } +} + +/// Used to serialize/deserialize from millisecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # extern crate serde_json; +/// # extern crate serde; +/// # extern crate chrono; +/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// use chrono::naive::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: NaiveDateTime +/// } +/// +/// # fn example() -> Result { +/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use super::ne_timestamp; + use crate::NaiveDateTime; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # #[macro_use] extern crate serde; + /// # extern crate chrono; + /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use serde::Serialize; + /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_millis()) + } + + /// Deserialize a `DateTime` from a milliseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate serde; + /// # extern crate chrono; + /// # use chrono::{NaiveDateTime, Utc}; + /// # use serde::Deserialize; + /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NaiveDateTimeFromMilliSecondsVisitor) + } + + struct NaiveDateTimeFromMilliSecondsVisitor; + + impl<'de> de::Visitor<'de> for NaiveDateTimeFromMilliSecondsVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt( + (value / 1000) as i64, + ((value % 1000) * 1_000_000) as u32, + ) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + } +} + +/// Used to serialize/deserialize from second-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # extern crate serde_json; +/// # extern crate serde; +/// # extern crate chrono; +/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// use chrono::naive::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: NaiveDateTime +/// } +/// +/// # fn example() -> Result { +/// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use super::ne_timestamp; + use crate::NaiveDateTime; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # #[macro_use] extern crate serde; + /// # extern crate chrono; + /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use serde::Serialize; + /// use chrono::naive::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp()) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate serde; + /// # extern crate chrono; + /// # use chrono::{NaiveDateTime, Utc}; + /// # use serde::Deserialize; + /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NaiveDateTimeFromSecondsVisitor) + } + + struct NaiveDateTimeFromSecondsVisitor; + + impl<'de> de::Visitor<'de> for NaiveDateTimeFromSecondsVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt(value, 0) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt(value as i64, 0) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + } +} + +#[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::naive::NaiveDate; + use bincode::{deserialize, serialize, Infinite}; + + let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); + let encoded = serialize(&dt, Infinite).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, Infinite}; + 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.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; + let bytes: Vec = serialize(&expected, Infinite).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 + E: de::Error, + V: fmt::Display, + T: fmt::Display, +{ + match me { + LocalResult::None => Err(E::custom(ne_timestamp(ts))), + LocalResult::Ambiguous(min, max) => { + Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) + } + LocalResult::Single(val) => Ok(val), + } +} + +#[cfg(feature = "serde")] +enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, +} + +/// Construct a [`SerdeError::NonExistent`] +#[cfg(feature = "serde")] +fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } +} + +#[cfg(feature = "serde")] +impl fmt::Debug for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChronoSerdeError({})", self) + } +} + +// impl core::error::Error for SerdeError {} +#[cfg(feature = "serde")] +impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SerdeError::NonExistent { timestamp } => { + write!(f, "value is not a legal timestamp: {}", timestamp) + } + SerdeError::Ambiguous { timestamp, min, max } => write!( + f, + "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max + ), + } + } +} diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs new file mode 100644 index 0000000000..ea5f91790d --- /dev/null +++ b/src/naive/datetime/tests.rs @@ -0,0 +1,242 @@ +use super::NaiveDateTime; +use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE}; +use crate::oldtime::Duration; +use crate::Datelike; +use std::i64; + +#[test] +fn test_datetime_from_timestamp() { + let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); + let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(from_timestamp(i64::MIN), None); + assert_eq!(from_timestamp(i64::MAX), None); +} + +#[test] +fn test_datetime_add() { + fn check( + (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), + rhs: Duration, + result: Option<(i32, u32, u32, u32, u32, u32)>, + ) { + let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let sum = result.map(|(y, m, d, h, n, s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); + assert_eq!(lhs.checked_add_signed(rhs), sum); + assert_eq!(lhs.checked_sub_signed(-rhs), sum); + } + + check((2014, 5, 6, 7, 8, 9), Duration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), Duration::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); + check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + + // overflow check + // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. + // (they are private constants, but the equivalence is tested in that module.) + let max_days_from_year_0 = MAX_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((MAX_DATE.year(), 12, 31, 0, 0, 0))); + check( + (0, 1, 1, 0, 0, 0), + max_days_from_year_0 + Duration::seconds(86399), + Some((MAX_DATE.year(), 12, 31, 23, 59, 59)), + ); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); + + let min_days_from_year_0 = MIN_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((MIN_DATE.year(), 1, 1, 0, 0, 0))); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); + check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); +} + +#[test] +fn test_datetime_sub() { + let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let since = NaiveDateTime::signed_duration_since; + assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), Duration::zero()); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), + Duration::seconds(1) + ); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + Duration::seconds(-1) + ); + assert_eq!( + since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + Duration::seconds(86399) + ); + assert_eq!( + since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), + Duration::seconds(999_999_999) + ); +} + +#[test] +fn test_datetime_addassignment() { + let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date += Duration::minutes(10_000_000); + assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); + date += Duration::days(10); + assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); +} + +#[test] +fn test_datetime_subassignment() { + let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date -= Duration::minutes(10_000_000); + assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); + date -= Duration::days(10); + assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); +} + +#[test] +fn test_datetime_timestamp() { + let to_timestamp = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s).timestamp(); + assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); + assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); + assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); +} + +#[test] +fn test_datetime_from_str() { + // valid cases + let valid = [ + "2015-2-18T23:16:9.15", + "-77-02-18T23:16:09", + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", + ]; + for &s in &valid { + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + assert!("".parse::().is_err()); + assert!("x".parse::().is_err()); + assert!("15".parse::().is_err()); + assert!("15:8:9".parse::().is_err()); + assert!("15-8-9".parse::().is_err()); + assert!("2015-15-15T15:15:15".parse::().is_err()); + assert!("2012-12-12T12:12:12x".parse::().is_err()); + assert!("2012-123-12T12:12:12".parse::().is_err()); + assert!("+ 82701-123-12T12:12:12".parse::().is_err()); + assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound +} + +#[test] +fn test_datetime_parse_from_str() { + let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let ymdhmsn = |y, m, d, h, n, s, nano| NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano); + assert_eq!( + NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(2014, 5, 7, 12, 34, 56)) + ); // ignore offset + assert_eq!( + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), + Ok(ymdhms(2015, 2, 2, 0, 0, 0)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), + Ok(ymdhms(2013, 8, 9, 23, 54, 35)) + ); + assert!(NaiveDateTime::parse_from_str( + "Sat, 09 Aug 2013 23:54:35 GMT", + "%a, %d %b %Y %H:%M:%S GMT" + ) + .is_err()); + assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); + assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient + assert_eq!( + NaiveDateTime::parse_from_str("1441497364", "%s"), + Ok(ymdhms(2015, 9, 5, 23, 56, 4)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), + Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), + Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), + Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), + Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) + ); +} + +#[test] +fn test_datetime_format() { + let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321); + assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); + assert_eq!(dt.format("%s").to_string(), "1283929614"); + assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // a horror of leap second: coming near to you. + let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000); + assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); + assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. +} + +#[test] +fn test_datetime_add_sub_invariant() { + // issue #37 + let base = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); + let t = -946684799990000; + let time = base + Duration::microseconds(t); + assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); +} + +#[test] +fn test_nanosecond_range() { + const A_BILLION: i64 = 1_000_000_000; + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos(); + assert_eq!( + parsed, + NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + + let minimum = "1677-09-21T00:12:44.000000000"; + let parsed: NaiveDateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos(); + assert_eq!( + parsed, + NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); +} From aab30ef19d2e73f509bb07c8ff1456c7ecd65f6c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 22:03:47 -0700 Subject: [PATCH 021/999] Split naive::time module into separate files --- src/naive/{time.rs => time/mod.rs} | 398 +---------------------------- src/naive/time/rustc_serialize.rs | 27 ++ src/naive/time/serde.rs | 63 +++++ src/naive/time/tests.rs | 278 ++++++++++++++++++++ 4 files changed, 377 insertions(+), 389 deletions(-) rename src/naive/{time.rs => time/mod.rs} (78%) create mode 100644 src/naive/time/rustc_serialize.rs create mode 100644 src/naive/time/serde.rs create mode 100644 src/naive/time/tests.rs diff --git a/src/naive/time.rs b/src/naive/time/mod.rs similarity index 78% rename from src/naive/time.rs rename to src/naive/time/mod.rs index 5056edf147..8046214d23 100644 --- a/src/naive/time.rs +++ b/src/naive/time/mod.rs @@ -16,6 +16,15 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; +#[cfg(feature = "rustc-serialize")] +mod rustc_serialize; + +#[cfg(feature = "serde")] +mod serde; + +#[cfg(test)] +mod tests; + pub(super) const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; pub(super) const MAX_TIME: NaiveTime = NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; @@ -1424,392 +1433,3 @@ where assert!(from_str(r#"{"secs":0,"frac":0}"#).is_err()); assert!(from_str(r#"null"#).is_err()); } - -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid time")) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } -} - -#[cfg(feature = "serde")] -mod serde { - use super::NaiveTime; - use core::fmt; - use serde::{de, ser}; - - // TODO not very optimized for space (binary formats would want something better) - // TODO round-trip for general leap seconds (not just those with second = 60) - - impl ser::Serialize for NaiveTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct NaiveTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveTimeVisitor { - type Value = NaiveTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveTimeVisitor) - } - } - - #[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)); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use bincode::{deserialize, serialize, Infinite}; - - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - let encoded = serialize(&t, Infinite).unwrap(); - let decoded: NaiveTime = deserialize(&encoded).unwrap(); - assert_eq!(t, decoded); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveTime; - use crate::oldtime::Duration; - use crate::Timelike; - use std::u32; - - #[test] - fn test_time_from_hms_milli() { - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_000_000)) - ); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_from_hms_micro() { - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 333), - Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_999_000)) - ); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_hms() { - assert_eq!(NaiveTime::from_hms(3, 5, 7).hour(), 3); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(0), Some(NaiveTime::from_hms(0, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(23), Some(NaiveTime::from_hms(23, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(24), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).minute(), 5); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(0), Some(NaiveTime::from_hms(3, 0, 7))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_minute(59), - Some(NaiveTime::from_hms(3, 59, 7)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).second(), 7); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(0), Some(NaiveTime::from_hms(3, 5, 0))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_second(59), - Some(NaiveTime::from_hms(3, 5, 59)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(u32::MAX), None); - } - - #[test] - fn test_time_add() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $sum:expr) => {{ - assert_eq!($lhs + $rhs, $sum); - //assert_eq!($rhs + $lhs, $sum); - }}; - } - - let hmsm = NaiveTime::from_hms_milli; - - check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300)); - - // regression tests for #37 - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); - } - - #[test] - fn test_time_overflowing_add() { - let hmsm = NaiveTime::from_hms_milli; - - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), - (hmsm(14, 4, 5, 678), 0) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)), - (hmsm(2, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)), - (hmsm(20, 4, 5, 678), -86_400) - ); - - // overflowing_add_signed with leap seconds may be counter-intuitive - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)), - (hmsm(3, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)), - (hmsm(3, 4, 6, 678), -86_400) - ); - } - - #[test] - fn test_time_addassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time += Duration::hours(10); - assert_eq!(time, hms(22, 12, 12)); - time += Duration::hours(10); - assert_eq!(time, hms(8, 12, 12)); - } - - #[test] - fn test_time_subassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time -= Duration::hours(10); - assert_eq!(time, hms(2, 12, 12)); - time -= Duration::hours(10); - assert_eq!(time, hms(16, 12, 12)); - } - - #[test] - fn test_time_sub() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $diff:expr) => {{ - // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` - assert_eq!($lhs.signed_duration_since($rhs), $diff); - assert_eq!($rhs.signed_duration_since($lhs), -$diff); - }}; - } - - let hmsm = NaiveTime::from_hms_milli; - - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1)); - check!( - hmsm(3, 5, 7, 200), - hmsm(2, 4, 6, 300), - Duration::seconds(3600 + 60) + Duration::milliseconds(900) - ); - - // treats the leap second as if it coincides with the prior non-leap second, - // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400)); - - // additional equality: `time1 + duration = time2` is equivalent to - // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - } - - #[test] - fn test_time_fmt() { - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 999)), "23:59:59.999"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_000)), "23:59:60"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_001)), "23:59:60.001"); - assert_eq!(format!("{}", NaiveTime::from_hms_micro(0, 0, 0, 43210)), "00:00:00.043210"); - assert_eq!(format!("{}", NaiveTime::from_hms_nano(0, 0, 0, 6543210)), "00:00:00.006543210"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "0:0:0", - "0:0:0.0000000", - "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", - "23:59:60.373929310237", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); - } - - #[test] - fn test_time_parse_from_str() { - let hms = NaiveTime::from_hms; - assert_eq!( - NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(hms(12, 34, 56)) - ); // ignore date and offset - assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); - assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); - } - - #[test] - fn test_time_format() { - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); - assert_eq!(t.format("%M").to_string(), "05"); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); - assert_eq!(t.format("%R").to_string(), "03:05"); - assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); - assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); - assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - let t = NaiveTime::from_hms_micro(3, 5, 7, 432100); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); - - let t = NaiveTime::from_hms_milli(3, 5, 7, 210); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); - - let t = NaiveTime::from_hms(3, 5, 7); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); - - // corner cases - assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM"); - assert_eq!( - NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), - "23:59:60" - ); - } -} diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs new file mode 100644 index 0000000000..42c091a661 --- /dev/null +++ b/src/naive/time/rustc_serialize.rs @@ -0,0 +1,27 @@ +use super::NaiveTime; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; + +impl Encodable for NaiveTime { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + format!("{:?}", self).encode(s) + } +} + +impl Decodable for NaiveTime { + fn decode(d: &mut D) -> Result { + d.read_str()?.parse().map_err(|_| d.error("invalid time")) + } +} + +#[cfg(test)] +use rustc_serialize::json; + +#[test] +fn test_encodable() { + super::test_encodable_json(json::encode); +} + +#[test] +fn test_decodable() { + super::test_decodable_json(json::decode); +} diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs new file mode 100644 index 0000000000..0a60ae55bc --- /dev/null +++ b/src/naive/time/serde.rs @@ -0,0 +1,63 @@ +use super::NaiveTime; +use core::fmt; +use serde::{de, ser}; + +// TODO not very optimized for space (binary formats would want something better) +// TODO round-trip for general leap seconds (not just those with second = 60) + +impl ser::Serialize for NaiveTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } +} + +struct NaiveTimeVisitor; + +impl<'de> de::Visitor<'de> for NaiveTimeVisitor { + type Value = NaiveTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveTimeVisitor) + } +} + +#[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)); +} + +#[test] +fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize, Infinite}; + + let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); + let encoded = serialize(&t, Infinite).unwrap(); + let decoded: NaiveTime = deserialize(&encoded).unwrap(); + assert_eq!(t, decoded); +} diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs new file mode 100644 index 0000000000..b853c9f22d --- /dev/null +++ b/src/naive/time/tests.rs @@ -0,0 +1,278 @@ +use super::NaiveTime; +use crate::oldtime::Duration; +use crate::Timelike; +use std::u32; + +#[test] +fn test_time_from_hms_milli() { + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 777), + Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000)) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), + Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_000_000)) + ); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); +} + +#[test] +fn test_time_from_hms_micro() { + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 333), + Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000)) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), + Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000)) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), + Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_999_000)) + ); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); +} + +#[test] +fn test_time_hms() { + assert_eq!(NaiveTime::from_hms(3, 5, 7).hour(), 3); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(0), Some(NaiveTime::from_hms(0, 5, 7))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(23), Some(NaiveTime::from_hms(23, 5, 7))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(24), None); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms(3, 5, 7).minute(), 5); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(0), Some(NaiveTime::from_hms(3, 0, 7))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(59), Some(NaiveTime::from_hms(3, 59, 7))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(60), None); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms(3, 5, 7).second(), 7); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(0), Some(NaiveTime::from_hms(3, 5, 0))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(59), Some(NaiveTime::from_hms(3, 5, 59))); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(60), None); + assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(u32::MAX), None); +} + +#[test] +fn test_time_add() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $sum:expr) => {{ + assert_eq!($lhs + $rhs, $sum); + //assert_eq!($rhs + $lhs, $sum); + }}; + } + + let hmsm = NaiveTime::from_hms_milli; + + check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); + check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); + check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap + check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900)); + check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900)); + check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300)); + check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300)); + + // regression tests for #37 + check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); +} + +#[test] +fn test_time_overflowing_add() { + let hmsm = NaiveTime::from_hms_milli; + + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), + (hmsm(14, 4, 5, 678), 0) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)), + (hmsm(2, 4, 5, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)), + (hmsm(20, 4, 5, 678), -86_400) + ); + + // overflowing_add_signed with leap seconds may be counter-intuitive + assert_eq!( + hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)), + (hmsm(3, 4, 5, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)), + (hmsm(3, 4, 6, 678), -86_400) + ); +} + +#[test] +fn test_time_addassignment() { + let hms = NaiveTime::from_hms; + let mut time = hms(12, 12, 12); + time += Duration::hours(10); + assert_eq!(time, hms(22, 12, 12)); + time += Duration::hours(10); + assert_eq!(time, hms(8, 12, 12)); +} + +#[test] +fn test_time_subassignment() { + let hms = NaiveTime::from_hms; + let mut time = hms(12, 12, 12); + time -= Duration::hours(10); + assert_eq!(time, hms(2, 12, 12)); + time -= Duration::hours(10); + assert_eq!(time, hms(16, 12, 12)); +} + +#[test] +fn test_time_sub() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $diff:expr) => {{ + // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` + assert_eq!($lhs.signed_duration_since($rhs), $diff); + assert_eq!($rhs.signed_duration_since($lhs), -$diff); + }}; + } + + let hmsm = NaiveTime::from_hms_milli; + + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1)); + check!( + hmsm(3, 5, 7, 200), + hmsm(2, 4, 6, 300), + Duration::seconds(3600 + 60) + Duration::milliseconds(900) + ); + + // treats the leap second as if it coincides with the prior non-leap second, + // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. + check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400)); + check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400)); + check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400)); + + // additional equality: `time1 + duration = time2` is equivalent to + // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. + assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); + assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); +} + +#[test] +fn test_time_fmt() { + assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 999)), "23:59:59.999"); + assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_000)), "23:59:60"); + assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_001)), "23:59:60.001"); + assert_eq!(format!("{}", NaiveTime::from_hms_micro(0, 0, 0, 43210)), "00:00:00.043210"); + assert_eq!(format!("{}", NaiveTime::from_hms_nano(0, 0, 0, 6543210)), "00:00:00.006543210"); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); +} + +#[test] +fn test_date_from_str() { + // valid cases + let valid = [ + "0:0:0", + "0:0:0.0000000", + "0:0:0.0000003", + " 4 : 3 : 2.1 ", + " 09:08:07 ", + " 9:8:07 ", + "23:59:60.373929310237", + ]; + for &s in &valid { + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + assert!("".parse::().is_err()); + assert!("x".parse::().is_err()); + assert!("15".parse::().is_err()); + assert!("15:8".parse::().is_err()); + assert!("15:8:x".parse::().is_err()); + assert!("15:8:9x".parse::().is_err()); + assert!("23:59:61".parse::().is_err()); + assert!("12:34:56.x".parse::().is_err()); + assert!("12:34:56. 0".parse::().is_err()); +} + +#[test] +fn test_time_parse_from_str() { + let hms = NaiveTime::from_hms; + assert_eq!( + NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(hms(12, 34, 56)) + ); // ignore date and offset + assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); +} + +#[test] +fn test_time_format() { + let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); + assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); + assert_eq!(t.format("%M").to_string(), "05"); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); + assert_eq!(t.format("%R").to_string(), "03:05"); + assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); + assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); + assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + let t = NaiveTime::from_hms_micro(3, 5, 7, 432100); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); + + let t = NaiveTime::from_hms_milli(3, 5, 7, 210); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); + + let t = NaiveTime::from_hms(3, 5, 7); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); + + // corner cases + assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM"); + assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), "23:59:60"); +} From 66898813de232b544950ba06e1373b40b3e046ba Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 21 Mar 2022 22:16:13 -0700 Subject: [PATCH 022/999] Reduce lint allowing --- src/datetime/tests.rs | 84 ++++++++++++++---------------------------- src/format/mod.rs | 22 ++++++++--- src/format/parse.rs | 4 +- src/format/parsed.rs | 19 +++++----- src/format/scan.rs | 10 ++--- src/lib.rs | 2 - src/naive/internals.rs | 16 +------- 7 files changed, 62 insertions(+), 95 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index c2ca35591a..8c373d3ba3 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,3 +1,5 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] @@ -6,68 +8,45 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::oldtime::Duration; #[cfg(feature = "clock")] use crate::Datelike; -use std::time::{SystemTime, UNIX_EPOCH}; #[test] -#[allow(non_snake_case)] fn test_datetime_offset() { - let Est = FixedOffset::west(5 * 60 * 60); - let Edt = FixedOffset::west(4 * 60 * 60); - let Kst = FixedOffset::east(9 * 60 * 60); + let est = FixedOffset::west(5 * 60 * 60); + let edt = FixedOffset::west(4 * 60 * 60); + let kst = FixedOffset::east(9 * 60 * 60); assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); - assert_eq!( - format!("{}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 -04:00" - ); - assert_eq!( - format!("{}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 +09:00" - ); + assert_eq!(format!("{}", edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 -04:00"); + assert_eq!(format!("{}", kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 +09:00"); assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09+09:00" - ); + assert_eq!(format!("{:?}", edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09-04:00"); + assert_eq!(format!("{:?}", kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09+09:00"); // edge cases assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); + assert_eq!(format!("{:?}", edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00-04:00"); + assert_eq!(format!("{:?}", kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00+09:00"); + assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), "2014-05-06T23:59:59Z"); assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00+09:00" - ); - assert_eq!( - format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59Z" - ); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), + format!("{:?}", edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), "2014-05-06T23:59:59-04:00" ); assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), + format!("{:?}", kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), "2014-05-06T23:59:59+09:00" ); let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(dt, Edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); + assert_eq!(dt, edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); assert_eq!( - dt.signed_duration_since(Edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), + dt.signed_duration_since(edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), Duration::seconds(-7 * 3600 - 3 * 60 - 3) ); assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); - assert_eq!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Edt); - assert!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != Est); + assert_eq!(*edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), edt); + assert!(*edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != est); } #[test] @@ -107,31 +86,27 @@ fn test_datetime_with_timezone() { } #[test] -#[allow(non_snake_case)] fn test_datetime_rfc2822_and_rfc3339() { - let EDT = FixedOffset::east(5 * 60 * 60); + let edt = FixedOffset::east(5 * 60 * 60); assert_eq!( Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); + assert_eq!(Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), + edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), + edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), + edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), + edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), "2015-02-18T23:59:60.234567+05:00" ); @@ -149,11 +124,11 @@ fn test_datetime_rfc2822_and_rfc3339() { ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) + Ok(edt.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) ); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) + Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) ); } @@ -237,11 +212,8 @@ fn test_datetime_parse_from_str() { Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); + assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err()); assert_eq!( Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) diff --git a/src/format/mod.rs b/src/format/mod.rs index 11611b98e1..5d720c0b57 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -33,8 +33,6 @@ //! # } //! ``` -#![allow(ellipsis_inclusive_range_patterns)] - #[cfg(feature = "alloc")] extern crate alloc; @@ -749,7 +747,7 @@ pub struct DelayedFormat { /// Locale used for text. // TODO: Only used with the locale feature. We should make this property // only present when the feature is enabled. - #[allow(dead_code)] + #[cfg(feature = "unstable-locales")] locale: Option, } @@ -757,7 +755,14 @@ pub struct DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { date, time, off: None, items, locale: None } + DelayedFormat { + date, + time, + off: None, + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. @@ -771,7 +776,14 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Off: Offset + fmt::Display, { let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { date, time, off: Some(name_and_diff), items, locale: None } + DelayedFormat { + date, + time, + off: Some(name_and_diff), + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } } /// Makes a new `DelayedFormat` value out of local date and time and locale. diff --git a/src/format/parse.rs b/src/format/parse.rs index fdde32a297..e57fb8a9d2 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -117,10 +117,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st let mut year = try_consume!(scan::number(s, 2, usize::MAX)); let yearlen = prevlen - s.len(); match (yearlen, year) { - (2, 0...49) => { + (2, 0..=49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 - (2, 50...99) => { + (2, 50..=99) => { year += 1900; } // 79 -> 1979 (3, _) => { diff --git a/src/format/parsed.rs b/src/format/parsed.rs index aed780a751..011ad889aa 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -22,7 +22,6 @@ use crate::{Datelike, Timelike}; /// /// - `to_*` methods try to make a concrete date and time value out of set fields. /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[allow(missing_copy_implementations)] #[derive(Clone, PartialEq, Debug, Default)] pub struct Parsed { /// Year. @@ -305,7 +304,7 @@ impl Parsed { // check if present quotient and/or modulo is consistent to the full year. // since the presence of those fields means a positive full year, // we should filter a negative full year first. - (Some(y), q, r @ Some(0...99)) | (Some(y), q, r @ None) => { + (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { if y < 0 { return Err(OUT_OF_RANGE); } @@ -319,7 +318,7 @@ impl Parsed { // the full year is missing but we have quotient and modulo. // reconstruct the full year. make sure that the result is always positive. - (None, Some(q), Some(r @ 0...99)) => { + (None, Some(q), Some(r @ 0..=99)) => { if q < 0 { return Err(OUT_OF_RANGE); } @@ -329,7 +328,7 @@ impl Parsed { // we only have modulo. try to interpret a modulo as a conventional two-digit year. // note: we are affected by Rust issue #18060. avoid multiple range patterns. - (None, None, Some(r @ 0...99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), + (None, None, Some(r @ 0..=99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), // otherwise it is an out-of-bound or insufficient condition. (None, Some(_), None) => Err(NOT_ENOUGH), @@ -500,32 +499,32 @@ impl Parsed { /// It is able to handle leap seconds when given second is 60. pub fn to_naive_time(&self) -> ParseResult { let hour_div_12 = match self.hour_div_12 { - Some(v @ 0...1) => v, + Some(v @ 0..=1) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour_mod_12 = match self.hour_mod_12 { - Some(v @ 0...11) => v, + Some(v @ 0..=11) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour = hour_div_12 * 12 + hour_mod_12; let minute = match self.minute { - Some(v @ 0...59) => v, + Some(v @ 0..=59) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; // we allow omitting seconds or nanoseconds, but they should be in the range. let (second, mut nano) = match self.second.unwrap_or(0) { - v @ 0...59 => (v, 0), + v @ 0..=59 => (v, 0), 60 => (59, 1_000_000_000), _ => return Err(OUT_OF_RANGE), }; nano += match self.nanosecond { - Some(v @ 0...999_999_999) if self.second.is_some() => v, - Some(0...999_999_999) => return Err(NOT_ENOUGH), // second is missing + Some(v @ 0..=999_999_999) if self.second.is_some() => v, + Some(0..=999_999_999) => return Err(NOT_ENOUGH), // second is missing Some(_) => return Err(OUT_OF_RANGE), None => 0, }; diff --git a/src/format/scan.rs b/src/format/scan.rs index ffa213dd81..6969c81a1e 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -14,7 +14,7 @@ use crate::Weekday; /// Assumes that the `pattern` is already converted to lower case. fn equals(s: &str, pattern: &str) -> bool { let mut xs = s.as_bytes().iter().map(|&c| match c { - b'A'...b'Z' => c + 32, + b'A'..=b'Z' => c + 32, _ => c, }); let mut ys = pattern.as_bytes().iter().cloned(); @@ -240,7 +240,7 @@ where // hours (00--99) let hours = match digits(s)? { - (h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), + (h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), _ => return Err(INVALID), }; s = &s[2..]; @@ -252,8 +252,8 @@ where // if the next two items are digits then we have to add minutes let minutes = if let Ok(ds) = digits(s) { match ds { - (m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), - (b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE), + (m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), + (b'6'..=b'9', b'0'..=b'9') => return Err(OUT_OF_RANGE), _ => return Err(INVALID), } } else if allow_missing_minutes { @@ -314,7 +314,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> .as_bytes() .iter() .position(|&c| match c { - b'a'...b'z' | b'A'...b'Z' => false, + b'a'..=b'z' | b'A'..=b'Z' => false, _ => true, }) .unwrap_or(s.len()); diff --git a/src/lib.rs b/src/lib.rs index 62b997a951..80065c3051 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -424,8 +424,6 @@ #![deny(missing_debug_implementations)] #![warn(unreachable_pub)] #![deny(dead_code)] -// lints are added all the time, we test on 1.13 -#![allow(unknown_lints)] #![cfg_attr(not(any(feature = "std", test)), no_std)] #[cfg(feature = "oldtime")] diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1fa2045a85..43b769df39 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -13,7 +13,6 @@ //! but the conversion keeps the valid value valid and the invalid value invalid //! so that the user-facing `NaiveDate` can validate the input as late as possible. -#![allow(dead_code)] // some internal methods have been left for consistency #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] use crate::Weekday; @@ -175,7 +174,6 @@ impl fmt::Debug for YearFlags { pub(super) const MIN_OL: u32 = 1 << 1; pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` -pub(super) const MIN_MDL: u32 = (1 << 6) | (1 << 1); pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; const XX: i8 = -128; @@ -321,12 +319,6 @@ impl Of { YearFlags((of & 0b1111) as u8) } - #[inline] - pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { - let Of(of) = *self; - Of((of & !0b1111) | u32::from(flags)) - } - #[inline] pub(super) fn weekday(&self) -> Weekday { let Of(of) = *self; @@ -416,7 +408,7 @@ impl Mdf { } } - #[inline] + #[cfg(test)] pub(super) fn valid(&self) -> bool { let Mdf(mdf) = *self; let mdl = mdf >> 3; @@ -452,12 +444,6 @@ impl Mdf { Mdf((mdf & !0b1_1111_0000) | (day << 4)) } - #[inline] - pub(super) fn flags(&self) -> YearFlags { - let Mdf(mdf) = *self; - YearFlags((mdf & 0b1111) as u8) - } - #[inline] pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; From d90544b7642a65a8d673f32a8d7efdec3b746ec6 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Wed, 23 Mar 2022 07:11:59 +0100 Subject: [PATCH 023/999] docs(naive): Remove unused imports in examples --- src/naive/datetime/serde.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 3caaab8af7..0a9c7a9d7e 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -66,7 +66,7 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// # extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; -/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// # use chrono::{NaiveDate, NaiveDateTime}; /// use chrono::naive::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -110,7 +110,7 @@ pub mod ts_nanoseconds { /// # #[macro_use] extern crate serde_json; /// # #[macro_use] extern crate serde; /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde::Serialize; /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; /// #[derive(Serialize)] @@ -151,7 +151,7 @@ pub mod ts_nanoseconds { /// # #[macro_use] extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; + /// # use chrono::NaiveDateTime; /// # use serde::Deserialize; /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; /// #[derive(Deserialize)] @@ -216,7 +216,7 @@ pub mod ts_nanoseconds { /// # extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; -/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// # use chrono::{NaiveDate, NaiveDateTime}; /// use chrono::naive::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -260,7 +260,7 @@ pub mod ts_milliseconds { /// # #[macro_use] extern crate serde_json; /// # #[macro_use] extern crate serde; /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde::Serialize; /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; /// #[derive(Serialize)] @@ -301,7 +301,7 @@ pub mod ts_milliseconds { /// # #[macro_use] extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; + /// # use chrono::NaiveDateTime; /// # use serde::Deserialize; /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; /// #[derive(Deserialize)] @@ -366,7 +366,7 @@ pub mod ts_milliseconds { /// # extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; -/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; +/// # use chrono::{NaiveDate, NaiveDateTime}; /// use chrono::naive::serde::ts_seconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -410,7 +410,7 @@ pub mod ts_seconds { /// # #[macro_use] extern crate serde_json; /// # #[macro_use] extern crate serde; /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; + /// # use chrono::{NaiveDate, NaiveDateTime}; /// # use serde::Serialize; /// use chrono::naive::serde::ts_seconds::serialize as to_ts; /// #[derive(Serialize)] @@ -451,7 +451,7 @@ pub mod ts_seconds { /// # #[macro_use] extern crate serde_json; /// # extern crate serde; /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; + /// # use chrono::NaiveDateTime; /// # use serde::Deserialize; /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; /// #[derive(Deserialize)] From 3105ff5bb9f0f816180cc09b66f98563b36331c2 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Wed, 23 Mar 2022 07:22:57 +0100 Subject: [PATCH 024/999] docs(serde): fix some copy & paste errors --- src/datetime/serde.rs | 20 ++++++++++---------- src/naive/datetime/serde.rs | 12 ++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 1f9714536a..1de565ad1b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -262,7 +262,7 @@ pub mod ts_nanoseconds { } } -/// Ser/de to/from timestamps in nanoseconds +/// Ser/de to/from optional timestamps in nanoseconds /// /// Intended for use with `serde`'s `with` attribute. /// @@ -392,7 +392,7 @@ pub mod ts_nanoseconds_option { formatter.write_str("a unix timestamp in nanoseconds or none") } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in nanoseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, @@ -400,7 +400,7 @@ pub mod ts_nanoseconds_option { d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in nanoseconds since the epoch fn visit_none(self) -> Result where E: de::Error, @@ -408,7 +408,7 @@ pub mod ts_nanoseconds_option { Ok(None) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in nanoseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, @@ -696,7 +696,7 @@ pub mod ts_microseconds_option { formatter.write_str("a unix timestamp in microseconds or none") } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in microseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, @@ -704,7 +704,7 @@ pub mod ts_microseconds_option { d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in microseconds since the epoch fn visit_none(self) -> Result where E: de::Error, @@ -712,7 +712,7 @@ pub mod ts_microseconds_option { Ok(None) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in microseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, @@ -1010,7 +1010,7 @@ pub mod ts_milliseconds_option { formatter.write_str("a unix timestamp in milliseconds or none") } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in milliseconds since the epoch fn visit_some(self, d: D) -> Result where D: de::Deserializer<'de>, @@ -1018,7 +1018,7 @@ pub mod ts_milliseconds_option { d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in milliseconds since the epoch fn visit_none(self) -> Result where E: de::Error, @@ -1026,7 +1026,7 @@ pub mod ts_milliseconds_option { Ok(None) } - /// Deserialize a timestamp in seconds since the epoch + /// Deserialize a timestamp in milliseconds since the epoch fn visit_unit(self) -> Result where E: de::Error, diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 0a9c7a9d7e..cad391b007 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -95,7 +95,7 @@ pub mod ts_nanoseconds { use super::ne_timestamp; use crate::NaiveDateTime; - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// Serialize a datetime into an integer number of nanoseconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. /// @@ -136,7 +136,7 @@ pub mod ts_nanoseconds { serializer.serialize_i64(dt.timestamp_nanos()) } - /// Deserialize a `DateTime` from a nanoseconds timestamp + /// Deserialize a `NaiveDateTime` from a nanoseconds timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// @@ -245,7 +245,7 @@ pub mod ts_milliseconds { use super::ne_timestamp; use crate::NaiveDateTime; - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch + /// Serialize a datetime into an integer number of milliseconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. /// @@ -286,7 +286,7 @@ pub mod ts_milliseconds { serializer.serialize_i64(dt.timestamp_millis()) } - /// Deserialize a `DateTime` from a milliseconds timestamp + /// Deserialize a `NaiveDateTime` from a milliseconds timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// @@ -395,7 +395,7 @@ pub mod ts_seconds { use super::ne_timestamp; use crate::NaiveDateTime; - /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// Serialize a datetime into an integer number of seconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. /// @@ -436,7 +436,7 @@ pub mod ts_seconds { serializer.serialize_i64(dt.timestamp()) } - /// Deserialize a `DateTime` from a seconds timestamp + /// Deserialize a `NaiveDateTime` from a seconds timestamp /// /// Intended for use with `serde`s `deserialize_with` attribute. /// From 0ec64a972364891aa3befcb51f388e406f43049d Mon Sep 17 00:00:00 2001 From: Weiyuan Wu Date: Fri, 20 Aug 2021 06:13:37 +0000 Subject: [PATCH 025/999] add optional rkyv support --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/date.rs | 4 +++- src/naive/date.rs | 3 +++ src/naive/datetime/mod.rs | 3 +++ src/naive/time/mod.rs | 3 +++ src/offset/fixed.rs | 3 +++ src/offset/local.rs | 3 +++ src/offset/utc.rs | 3 +++ 9 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8facd028..e596cf406e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Versions with only mechanical changes will be omitted from the following list. * Correct build for wasm32-unknown-emscripten target (#568) * Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) * Fix `duration_round` panic on rounding by `Duration::zero()` (#658) +* Add optional rkyv support. ## 0.4.19 diff --git a/Cargo.toml b/Cargo.toml index 03ad3eb263..0a7c37cd86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.3", optional = true } +rkyv = {version = "0.7", optional = true} [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } diff --git a/src/date.rs b/src/date.rs index a3cb88a0bc..1904378389 100644 --- a/src/date.rs +++ b/src/date.rs @@ -18,7 +18,8 @@ use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; use crate::DateTime; use crate::{Datelike, Weekday}; - +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 calendar date with time zone. /// /// You almost certainly want to be using a [`NaiveDate`] instead of this type. @@ -51,6 +52,7 @@ use crate::{Datelike, Weekday}; /// so the local date and UTC date should be equal for most cases /// even though the raw calculation between `NaiveDate` and `Duration` may not. #[derive(Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Date { date: NaiveDate, offset: Tz::Offset, diff --git a/src/naive/date.rs b/src/naive/date.rs index d0b6b4c33a..70011478b4 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -17,6 +17,8 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{Datelike, Weekday}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -96,6 +98,7 @@ const MAX_BITS: usize = 44; /// /// This is currently the internal format of Chrono's date types. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveDate { ymdf: DateImpl, // (year << 13) | of } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 6d37af442e..9744453bc9 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -20,6 +20,8 @@ use crate::naive::time::{MAX_TIME, MIN_TIME}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Timelike, Weekday}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -70,6 +72,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime { date: MAX_DATE, time: MA /// assert_eq!(dt.num_seconds_from_midnight(), 33011); /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveDateTime { date: NaiveDate, time: NaiveTime, diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8046214d23..ea64dc0810 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -15,6 +15,8 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; @@ -189,6 +191,7 @@ pub(super) const MAX_TIME: NaiveTime = /// Since Chrono alone cannot determine any existence of leap seconds, /// **there is absolutely no guarantee that the leap second read has actually happened**. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveTime { secs: u32, frac: u32, diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 3b31402a4b..9bca837039 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -12,6 +12,8 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Timelike; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// @@ -20,6 +22,7 @@ use crate::Timelike; /// `DateTime` instances. See the [`east`](#method.east) and /// [`west`](#method.west) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct FixedOffset { local_minus_utc: i32, } diff --git a/src/offset/local.rs b/src/offset/local.rs index 3eef187bb8..205e2257f1 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -14,6 +14,8 @@ use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{Date, DateTime}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::{Datelike, Timelike}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. @@ -88,6 +90,7 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec { /// let dt: DateTime = Local.timestamp(0, 0); /// ``` #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Local; impl Local { diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 42e76a9c1d..54c1c022e8 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -9,6 +9,8 @@ use super::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[cfg(feature = "clock")] use crate::{Date, DateTime}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(all( feature = "clock", not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) @@ -33,6 +35,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Utc; #[cfg(feature = "clock")] From c2e9f6130470257955999f6bcc9e972beb3ecafa Mon Sep 17 00:00:00 2001 From: Weiyuan Wu Date: Wed, 23 Mar 2022 20:44:36 +0000 Subject: [PATCH 026/999] tidy up imports --- src/date.rs | 8 +++++--- src/naive/date.rs | 7 ++++--- src/naive/datetime/mod.rs | 4 ++-- src/naive/time/mod.rs | 5 +++-- src/offset/fixed.rs | 5 +++-- src/offset/local.rs | 8 ++++---- src/offset/utc.rs | 15 ++++++++------- 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/date.rs b/src/date.rs index 1904378389..0758cc844f 100644 --- a/src/date.rs +++ b/src/date.rs @@ -3,23 +3,25 @@ //! ISO 8601 calendar date with time zone. -use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, Sub}; use core::{fmt, hash}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + #[cfg(feature = "unstable-locales")] use crate::format::Locale; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; +use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::{Datelike, Weekday}; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; + /// ISO 8601 calendar date with time zone. /// /// You almost certainly want to be using a [`NaiveDate`] instead of this type. diff --git a/src/naive/date.rs b/src/naive/date.rs index 70011478b4..50ba1129a8 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,22 +3,23 @@ //! ISO 8601 calendar date without timezone. -use crate::oldtime::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; + use num_integer::div_mod_floor; use num_traits::ToPrimitive; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; +use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Weekday}; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 9744453bc9..cd1853c338 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -10,6 +10,8 @@ use core::{fmt, str}; use num_integer::div_mod_floor; use num_traits::ToPrimitive; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; @@ -20,8 +22,6 @@ use crate::naive::time::{MAX_TIME, MIN_TIME}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Timelike, Weekday}; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index ea64dc0810..29b0929a06 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -7,7 +7,10 @@ use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; + use num_integer::div_mod_floor; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; @@ -15,8 +18,6 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 9bca837039..df1a990bac 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -5,15 +5,16 @@ use core::fmt; use core::ops::{Add, Sub}; + use num_integer::div_mod_floor; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Timelike; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// diff --git a/src/offset/local.rs b/src/offset/local.rs index 205e2257f1..f1da534212 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -3,19 +3,19 @@ //! The local (system) time zone. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use crate::sys::{self, Timespec}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::naive::NaiveTime; use crate::naive::{NaiveDate, NaiveDateTime}; +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +use crate::sys::{self, Timespec}; use crate::{Date, DateTime}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::{Datelike, Timelike}; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 54c1c022e8..730c03e95f 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -4,19 +4,20 @@ //! The UTC (Coordinated Universal Time) time zone. use core::fmt; - -use super::{FixedOffset, LocalResult, Offset, TimeZone}; -use crate::naive::{NaiveDate, NaiveDateTime}; -#[cfg(feature = "clock")] -use crate::{Date, DateTime}; -#[cfg(feature = "rkyv")] -use rkyv::{Archive, Deserialize, Serialize}; #[cfg(all( feature = "clock", not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) ))] use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::naive::{NaiveDate, NaiveDateTime}; +#[cfg(feature = "clock")] +use crate::{Date, DateTime}; + /// The UTC time zone. This is the most efficient time zone when you don't need the local time. /// It is also used as an offset (which is also a dummy type). /// From 6471b362da7cce1422d226545805c150b92962bc Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Wed, 23 Mar 2022 18:54:55 +0100 Subject: [PATCH 027/999] feat(naive): add serde module for microseconds serialization --- CHANGELOG.md | 1 + src/naive/datetime/serde.rs | 153 ++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e596cf406e..fed11ecd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Versions with only mechanical changes will be omitted from the following list. * Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) * Fix `duration_round` panic on rounding by `Duration::zero()` (#658) * Add optional rkyv support. +* Add support for microseconds timestamps serde serialization for `NaiveDateTime`. ## 0.4.19 diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index cad391b007..996777a9da 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -203,6 +203,159 @@ pub mod ts_nanoseconds { } } +/// Used to serialize/deserialize from microsecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # extern crate serde_json; +/// # extern crate serde; +/// # extern crate chrono; +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// use chrono::naive::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: NaiveDateTime +/// } +/// +/// # fn example() -> Result { +/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use super::ne_timestamp; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # #[macro_use] extern crate serde; + /// # extern crate chrono; + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde::Serialize; + /// use chrono::naive::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_micros()) + } + + /// Deserialize a `NaiveDateTime` from a microseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate serde; + /// # extern crate chrono; + /// # use chrono::NaiveDateTime; + /// # use serde::Deserialize; + /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: NaiveDateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NaiveDateTimeFromMicroSecondsVisitor) + } + + struct NaiveDateTimeFromMicroSecondsVisitor; + + impl<'de> de::Visitor<'de> for NaiveDateTimeFromMicroSecondsVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt( + value / 1_000_000, + ((value % 1_000_000) * 1000) as u32, + ) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + NaiveDateTime::from_timestamp_opt( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, + ) + .ok_or_else(|| E::custom(ne_timestamp(value))) + } + } +} + /// Used to serialize/deserialize from millisecond-precision timestamps /// /// # Example: From a6bf842a030141dbddfbcdf1fd1c8b898baf93cd Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Wed, 23 Mar 2022 20:02:40 +0100 Subject: [PATCH 028/999] feat(naive): add serde modules for optional timestamps - `naive::serde::ts_nanoseconds_option` - `naive::serde::ts_microseconds_option` - `naive::serde::ts_milliseconds_option` - `naive::serde::ts_seconds_option` --- CHANGELOG.md | 1 + src/naive/datetime/serde.rs | 644 +++++++++++++++++++++++++++++++++++- 2 files changed, 633 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed11ecd72..92590cd39b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Versions with only mechanical changes will be omitted from the following list. * Fix `duration_round` panic on rounding by `Duration::zero()` (#658) * Add optional rkyv support. * Add support for microseconds timestamps serde serialization for `NaiveDateTime`. +* Add support for optional timestamps serde serialization for `NaiveDateTime`. ## 0.4.19 diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 996777a9da..a30d43b552 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -170,12 +170,12 @@ pub mod ts_nanoseconds { where D: de::Deserializer<'de>, { - d.deserialize_i64(NaiveDateTimeFromNanoSecondsVisitor) + d.deserialize_i64(NanoSecondsTimestampVisitor) } - struct NaiveDateTimeFromNanoSecondsVisitor; + pub(super) struct NanoSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for NaiveDateTimeFromNanoSecondsVisitor { + impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -203,6 +203,161 @@ pub mod ts_nanoseconds { } } +/// Ser/de to/from optional timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// use chrono::naive::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option +/// } +/// +/// # fn example() -> Result { +/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_nanoseconds::NanoSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + /// Used to serialize/deserialize from microsecond-precision timestamps /// /// # Example: @@ -320,12 +475,12 @@ pub mod ts_microseconds { where D: de::Deserializer<'de>, { - d.deserialize_i64(NaiveDateTimeFromMicroSecondsVisitor) + d.deserialize_i64(MicroSecondsTimestampVisitor) } - struct NaiveDateTimeFromMicroSecondsVisitor; + pub(super) struct MicroSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for NaiveDateTimeFromMicroSecondsVisitor { + impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -356,6 +511,161 @@ pub mod ts_microseconds { } } +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// use chrono::naive::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option +/// } +/// +/// # fn example() -> Result { +/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_microseconds::MicroSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + /// Used to serialize/deserialize from millisecond-precision timestamps /// /// # Example: @@ -473,12 +783,12 @@ pub mod ts_milliseconds { where D: de::Deserializer<'de>, { - d.deserialize_i64(NaiveDateTimeFromMilliSecondsVisitor) + d.deserialize_i64(MilliSecondsTimestampVisitor) } - struct NaiveDateTimeFromMilliSecondsVisitor; + pub(super) struct MilliSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for NaiveDateTimeFromMilliSecondsVisitor { + impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -506,6 +816,161 @@ pub mod ts_milliseconds { } } +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// use chrono::naive::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option +/// } +/// +/// # fn example() -> Result { +/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_milliseconds::MilliSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + /// Used to serialize/deserialize from second-precision timestamps /// /// # Example: @@ -623,12 +1088,12 @@ pub mod ts_seconds { where D: de::Deserializer<'de>, { - d.deserialize_i64(NaiveDateTimeFromSecondsVisitor) + d.deserialize_i64(SecondsTimestampVisitor) } - struct NaiveDateTimeFromSecondsVisitor; + pub(super) struct SecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for NaiveDateTimeFromSecondsVisitor { + impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -653,6 +1118,161 @@ pub mod ts_seconds { } } +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # // We mark this ignored so that we can test on 1.13 (which does not +/// # // support custom derive), and run tests with --ignored on beta and +/// # // nightly to actually trigger these. +/// # +/// # #[macro_use] extern crate serde_derive; +/// # #[macro_use] extern crate serde_json; +/// # extern crate chrono; +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// use chrono::naive::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option +/// } +/// +/// # fn example() -> Result { +/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok(my_s) +/// # } +/// # fn main() { example().unwrap(); } +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_seconds::SecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a second timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + #[test] fn test_serde_serialize() { super::test_encodable_json(serde_json::to_string); From c9db7d1f92a58ae15ce1d5c86cad7728608fbd92 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Mon, 28 Mar 2022 10:28:02 +0200 Subject: [PATCH 029/999] Remove `extern crate` statements from benches, tests & doc examples --- benches/chrono.rs | 3 - benches/serde.rs | 3 - src/datetime/serde.rs | 209 +++++------------------------------ src/lib.rs | 2 - src/month.rs | 1 - src/naive/date.rs | 12 +- src/naive/datetime/mod.rs | 28 ++--- src/naive/datetime/serde.rs | 212 ++++-------------------------------- src/naive/time/mod.rs | 24 ++-- 9 files changed, 86 insertions(+), 408 deletions(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index 01b9caed4f..cc88378eb7 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -1,9 +1,6 @@ //! Benchmarks for chrono that just depend on std #![cfg(feature = "__internal_bench")] -extern crate chrono; -extern crate criterion; - use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use chrono::prelude::*; diff --git a/benches/serde.rs b/benches/serde.rs index b7136efbfb..e9de4408e9 100644 --- a/benches/serde.rs +++ b/benches/serde.rs @@ -1,8 +1,5 @@ #![cfg(feature = "__internal_bench")] -extern crate chrono; -extern crate criterion; - use criterion::{black_box, criterion_group, criterion_main, Criterion}; use chrono::NaiveDateTime; diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 1de565ad1b..42afd6a575 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -119,14 +119,8 @@ impl<'de> de::Deserialize<'de> for DateTime { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -164,14 +158,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; /// #[derive(Serialize)] /// struct S { @@ -203,14 +191,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; /// #[derive(Deserialize)] /// struct S { @@ -269,14 +251,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -313,14 +289,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; /// #[derive(Serialize)] /// struct S { @@ -355,14 +325,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -425,14 +389,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -469,14 +427,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; /// #[derive(Serialize)] /// struct S { @@ -508,14 +460,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; /// #[derive(Deserialize)] /// struct S { @@ -574,14 +520,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -617,14 +557,8 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; /// #[derive(Serialize)] /// struct S { @@ -659,14 +593,8 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -729,14 +657,8 @@ pub mod ts_microseconds_option { /// # Example /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -773,14 +695,8 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; /// #[derive(Serialize)] /// struct S { @@ -812,14 +728,8 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; /// #[derive(Deserialize)] /// struct S { @@ -875,14 +785,8 @@ pub mod ts_milliseconds { /// # Example /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -918,14 +822,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; /// #[derive(Serialize)] /// struct S { @@ -960,14 +858,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::prelude::*; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; /// /// #[derive(Deserialize, PartialEq, Debug)] @@ -1043,14 +935,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_seconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -1087,14 +973,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_seconds::serialize as to_ts; /// #[derive(Serialize)] /// struct S { @@ -1126,14 +1006,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds::deserialize as from_ts; /// #[derive(Deserialize)] /// struct S { @@ -1186,14 +1060,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_seconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -1229,14 +1097,8 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; /// #[derive(Serialize)] /// struct S { @@ -1271,14 +1133,8 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::{DateTime, Utc}; + /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -1334,23 +1190,18 @@ pub mod ts_seconds_option { } } -#[cfg(test)] -extern crate bincode; -#[cfg(test)] -extern crate serde_json; - #[test] fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string, self::serde_json::to_string); + super::test_encodable_json(serde_json::to_string, serde_json::to_string); } #[cfg(feature = "clock")] #[test] fn test_serde_deserialize() { super::test_decodable_json( - |input| self::serde_json::from_str(input), - |input| self::serde_json::from_str(input), - |input| self::serde_json::from_str(input), + |input| serde_json::from_str(input), + |input| serde_json::from_str(input), + |input| serde_json::from_str(input), ); } @@ -1358,7 +1209,7 @@ fn test_serde_deserialize() { fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize, Infinite}; let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); let encoded = serialize(&dt, Infinite).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 80065c3051..c0bf27f038 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,8 +173,6 @@ //! The following illustrates most supported operations to the date and time: //! //! ```rust -//! # extern crate chrono; -//! //! # fn main() { //! use chrono::prelude::*; //! use chrono::Duration; diff --git a/src/month.rs b/src/month.rs index 624cf3ea03..8d549d4a3d 100644 --- a/src/month.rs +++ b/src/month.rs @@ -7,7 +7,6 @@ use core::fmt; /// /// It is possible to convert from a date to a month independently /// ``` -/// # extern crate num_traits; /// use num_traits::FromPrimitive; /// use chrono::prelude::*; /// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); diff --git a/src/naive/date.rs b/src/naive/date.rs index 50ba1129a8..481c3e6e9d 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -878,7 +878,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// use chrono::naive::MAX_DATE; /// @@ -912,7 +912,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// use chrono::naive::MIN_DATE; /// @@ -948,7 +948,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1454,7 +1454,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1495,7 +1495,7 @@ impl AddAssign for NaiveDate { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1538,7 +1538,7 @@ impl SubAssign for NaiveDate { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index cd1853c338..25d7fc0dd8 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -468,7 +468,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -495,7 +495,7 @@ impl NaiveDateTime { /// Overflow returns `None`. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); @@ -506,7 +506,7 @@ impl NaiveDateTime { /// but the addition assumes that it is the only leap second happened. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -551,7 +551,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -578,7 +578,7 @@ impl NaiveDateTime { /// Overflow returns `None`. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); @@ -589,7 +589,7 @@ impl NaiveDateTime { /// but the subtraction assumes that it is the only leap second happened. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -630,7 +630,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -650,7 +650,7 @@ impl NaiveDateTime { /// there were no other leap seconds happened. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); @@ -1227,7 +1227,7 @@ impl Timelike for NaiveDateTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1252,7 +1252,7 @@ impl Timelike for NaiveDateTime { /// but the addition assumes that it is the only leap second happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -1297,7 +1297,7 @@ impl AddAssign for NaiveDateTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1322,7 +1322,7 @@ impl AddAssign for NaiveDateTime { /// but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -1365,7 +1365,7 @@ impl SubAssign for NaiveDateTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1384,7 +1384,7 @@ impl SubAssign for NaiveDateTime { /// seconds happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index a30d43b552..3ac4c00f2b 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -58,15 +58,8 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # extern crate serde_json; -/// # extern crate serde; -/// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -102,16 +95,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; - /// # use serde::Serialize; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; /// #[derive(Serialize)] /// struct S { @@ -143,16 +128,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; /// # use chrono::NaiveDateTime; - /// # use serde::Deserialize; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; /// #[derive(Deserialize)] /// struct S { @@ -210,14 +187,8 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_nanoseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -253,14 +224,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; /// #[derive(Serialize)] /// struct S { @@ -295,14 +260,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -363,15 +322,8 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # extern crate serde_json; -/// # extern crate serde; -/// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -407,16 +359,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; - /// # use serde::Serialize; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_microseconds::serialize as to_micro_ts; /// #[derive(Serialize)] /// struct S { @@ -448,16 +392,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; /// # use chrono::NaiveDateTime; - /// # use serde::Deserialize; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; /// #[derive(Deserialize)] /// struct S { @@ -518,14 +454,8 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_microseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -561,14 +491,8 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_microseconds_option::serialize as to_micro_tsopt; /// #[derive(Serialize)] /// struct S { @@ -603,14 +527,8 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -671,15 +589,8 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # extern crate serde_json; -/// # extern crate serde; -/// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -715,16 +626,8 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; - /// # use serde::Serialize; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; /// #[derive(Serialize)] /// struct S { @@ -756,16 +659,8 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; /// # use chrono::NaiveDateTime; - /// # use serde::Deserialize; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; /// #[derive(Deserialize)] /// struct S { @@ -823,14 +718,8 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -866,14 +755,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_milliseconds_option::serialize as to_milli_tsopt; /// #[derive(Serialize)] /// struct S { @@ -908,14 +791,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; /// #[derive(Deserialize)] /// struct S { @@ -976,15 +853,8 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # extern crate serde_json; -/// # extern crate serde; -/// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_seconds; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -1020,16 +890,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; /// # use chrono::{NaiveDate, NaiveDateTime}; - /// # use serde::Serialize; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_seconds::serialize as to_ts; /// #[derive(Serialize)] /// struct S { @@ -1061,16 +923,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; /// # use chrono::NaiveDateTime; - /// # use serde::Deserialize; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; /// #[derive(Deserialize)] /// struct S { @@ -1125,14 +979,8 @@ pub mod ts_seconds { /// # Example: /// /// ```rust -/// # // We mark this ignored so that we can test on 1.13 (which does not -/// # // support custom derive), and run tests with --ignored on beta and -/// # // nightly to actually trigger these. -/// # -/// # #[macro_use] extern crate serde_derive; -/// # #[macro_use] extern crate serde_json; -/// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; /// use chrono::naive::serde::ts_seconds_option; /// #[derive(Deserialize, Serialize)] /// struct S { @@ -1168,14 +1016,8 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; /// use chrono::naive::serde::ts_seconds_option::serialize as to_tsopt; /// #[derive(Serialize)] /// struct S { @@ -1210,14 +1052,8 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; /// #[derive(Deserialize)] /// struct S { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 29b0929a06..1976e33d31 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -529,7 +529,7 @@ impl NaiveTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hms = NaiveTime::from_hms; @@ -614,7 +614,7 @@ impl NaiveTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hms = NaiveTime::from_hms; @@ -646,7 +646,7 @@ impl NaiveTime { /// # Example /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -675,7 +675,7 @@ impl NaiveTime { /// there were no other leap seconds happened. /// /// ``` - /// # extern crate chrono; fn main() { + /// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// # let since = NaiveTime::signed_duration_since; @@ -1034,7 +1034,7 @@ impl Timelike for NaiveTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1053,7 +1053,7 @@ impl Timelike for NaiveTime { /// The addition wraps around. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); @@ -1065,7 +1065,7 @@ impl Timelike for NaiveTime { /// Leap seconds are handled, but the addition assumes that it is the only leap second happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// let leap = from_hmsm(3, 5, 59, 1_300); @@ -1106,7 +1106,7 @@ impl AddAssign for NaiveTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1123,7 +1123,7 @@ impl AddAssign for NaiveTime { /// The subtraction wraps around. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); @@ -1134,7 +1134,7 @@ impl AddAssign for NaiveTime { /// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// let leap = from_hmsm(3, 5, 59, 1_300); @@ -1177,7 +1177,7 @@ impl SubAssign for NaiveTime { /// # Example /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1198,7 +1198,7 @@ impl SubAssign for NaiveTime { /// there were no other leap seconds happened. /// /// ``` -/// # extern crate chrono; fn main() { +/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); From 0b3a4f5aba5cf08c4137e03ffe29d1066ad3e548 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Mon, 28 Mar 2022 12:05:12 +0200 Subject: [PATCH 030/999] Remove superfluous `fn main()` in doc examples --- README.md | 1 - src/lib.rs | 2 -- src/naive/date.rs | 12 ------------ src/naive/datetime/mod.rs | 28 ---------------------------- src/naive/time/mod.rs | 24 ------------------------ 5 files changed, 67 deletions(-) diff --git a/README.md b/README.md index 48af06cfdb..8ae4aa08e7 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,6 @@ Addition and subtraction is also supported. The following illustrates most supported operations to the date and time: ```rust - use chrono::prelude::*; use chrono::Duration; diff --git a/src/lib.rs b/src/lib.rs index c0bf27f038..2808abb5ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,7 +173,6 @@ //! The following illustrates most supported operations to the date and time: //! //! ```rust -//! # fn main() { //! use chrono::prelude::*; //! use chrono::Duration; //! @@ -208,7 +207,6 @@ //! Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); //! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), //! Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -//! # } //! ``` //! //! ### Formatting and Parsing diff --git a/src/naive/date.rs b/src/naive/date.rs index 481c3e6e9d..7e3e0530c1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -878,7 +878,6 @@ impl NaiveDate { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// use chrono::naive::MAX_DATE; /// @@ -890,7 +889,6 @@ impl NaiveDate { /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(MAX_DATE.checked_add_signed(Duration::days(1)), None); - /// # } /// ``` pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let year = self.year(); @@ -912,7 +910,6 @@ impl NaiveDate { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// use chrono::naive::MIN_DATE; /// @@ -924,7 +921,6 @@ impl NaiveDate { /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(MIN_DATE.checked_sub_signed(Duration::days(1)), None); - /// # } /// ``` pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let year = self.year(); @@ -948,7 +944,6 @@ impl NaiveDate { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -961,7 +956,6 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), Duration::days(365)); /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), Duration::days(365*4 + 1)); /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); - /// # } /// ``` pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { let year1 = self.year(); @@ -1454,7 +1448,6 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1467,7 +1460,6 @@ impl Datelike for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(364), from_ymd(2014, 12, 31)); /// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*4 + 1), from_ymd(2018, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*400 + 97), from_ymd(2414, 1, 1)); -/// # } /// ``` impl Add for NaiveDate { type Output = NaiveDate; @@ -1495,7 +1487,6 @@ impl AddAssign for NaiveDate { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1508,7 +1499,6 @@ impl AddAssign for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(364), from_ymd(2013, 1, 2)); /// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*4 + 1), from_ymd(2010, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*400 + 97), from_ymd(1614, 1, 1)); -/// # } /// ``` impl Sub for NaiveDate { type Output = NaiveDate; @@ -1538,7 +1528,6 @@ impl SubAssign for NaiveDate { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1550,7 +1539,6 @@ impl SubAssign for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), Duration::days(365)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), Duration::days(365*4 + 1)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), Duration::days(365*400 + 97)); -/// # } /// ``` impl Sub for NaiveDate { type Output = OldDuration; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 25d7fc0dd8..950fa239e7 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -468,7 +468,6 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -489,24 +488,20 @@ impl NaiveDateTime { /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(Duration::milliseconds(450)), /// Some(hmsm(3, 5, 8, 430))); - /// # } /// ``` /// /// Overflow returns `None`. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); - /// # } /// ``` /// /// Leap seconds are handled, /// but the addition assumes that it is the only leap second happened. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -525,7 +520,6 @@ impl NaiveDateTime { /// Some(hmsm(3, 5, 50, 300))); /// assert_eq!(leap.checked_add_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); - /// # } /// ``` pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); @@ -551,7 +545,6 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -572,24 +565,20 @@ impl NaiveDateTime { /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(Duration::milliseconds(670)), /// Some(hmsm(3, 5, 6, 780))); - /// # } /// ``` /// /// Overflow returns `None`. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); - /// # } /// ``` /// /// Leap seconds are handled, /// but the subtraction assumes that it is the only leap second happened. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -604,7 +593,6 @@ impl NaiveDateTime { /// Some(hmsm(3, 5, 0, 300))); /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); - /// # } /// ``` pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); @@ -630,7 +618,6 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -643,14 +630,12 @@ impl NaiveDateTime { /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).signed_duration_since(d0.and_hms(0, 0, 0)), /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); - /// # } /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); @@ -658,7 +643,6 @@ impl NaiveDateTime { /// Duration::seconds(3600) + Duration::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0).signed_duration_since(leap), /// Duration::seconds(3600) - Duration::milliseconds(500)); - /// # } /// ``` pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) @@ -1227,7 +1211,6 @@ impl Timelike for NaiveDateTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1245,14 +1228,12 @@ impl Timelike for NaiveDateTime { /// /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); /// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); -/// # } /// ``` /// /// Leap seconds are handled, /// but the addition assumes that it is the only leap second happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -1265,7 +1246,6 @@ impl Timelike for NaiveDateTime { /// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); /// assert_eq!(leap + Duration::days(1), /// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); -/// # } /// ``` impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1297,7 +1277,6 @@ impl AddAssign for NaiveDateTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1315,14 +1294,12 @@ impl AddAssign for NaiveDateTime { /// /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); /// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); -/// # } /// ``` /// /// Leap seconds are handled, /// but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); @@ -1333,7 +1310,6 @@ impl AddAssign for NaiveDateTime { /// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); /// assert_eq!(leap - Duration::days(1), /// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); -/// # } /// ``` impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1365,7 +1341,6 @@ impl SubAssign for NaiveDateTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveDate}; /// /// let from_ymd = NaiveDate::from_ymd; @@ -1377,14 +1352,12 @@ impl SubAssign for NaiveDateTime { /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!(d.and_hms_milli(0, 7, 6, 500) - d0.and_hms(0, 0, 0), /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); -/// # } /// ``` /// /// Leap seconds are handled, but the subtraction assumes that no other leap /// seconds happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); @@ -1392,7 +1365,6 @@ impl SubAssign for NaiveDateTime { /// Duration::seconds(3600) + Duration::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0) - leap, /// Duration::seconds(3600) - Duration::milliseconds(500)); -/// # } /// ``` impl Sub for NaiveDateTime { type Output = OldDuration; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 1976e33d31..1bcfd03a3a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -529,7 +529,6 @@ impl NaiveTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hms = NaiveTime::from_hms; @@ -540,7 +539,6 @@ impl NaiveTime { /// (from_hms(2, 4, 5), 86_400)); /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), /// (from_hms(20, 4, 5), -86_400)); - /// # } /// ``` #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { @@ -614,7 +612,6 @@ impl NaiveTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hms = NaiveTime::from_hms; @@ -625,7 +622,6 @@ impl NaiveTime { /// (from_hms(10, 4, 5), 86_400)); /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(-22)), /// (from_hms(1, 4, 5), -86_400)); - /// # } /// ``` #[inline] pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { @@ -646,7 +642,6 @@ impl NaiveTime { /// # Example /// /// ``` - /// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -668,14 +663,12 @@ impl NaiveTime { /// Duration::seconds(-3600)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), /// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); - /// # } /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` - /// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// # let since = NaiveTime::signed_duration_since; @@ -689,7 +682,6 @@ impl NaiveTime { /// Duration::seconds(1)); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), /// Duration::seconds(61)); - /// # } /// ``` pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { // | | :leap| | | | | | | :leap| | @@ -1034,7 +1026,6 @@ impl Timelike for NaiveTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1047,25 +1038,21 @@ impl Timelike for NaiveTime { /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::milliseconds(80), from_hmsm(3, 5, 7, 80)); /// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(280), from_hmsm(3, 5, 8, 230)); /// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(-980), from_hmsm(3, 5, 6, 970)); -/// # } /// ``` /// /// The addition wraps around. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } /// ``` /// /// Leap seconds are handled, but the addition assumes that it is the only leap second happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// let leap = from_hmsm(3, 5, 59, 1_300); @@ -1076,7 +1063,6 @@ impl Timelike for NaiveTime { /// assert_eq!(leap + Duration::seconds(10), from_hmsm(3, 6, 9, 300)); /// assert_eq!(leap + Duration::seconds(-10), from_hmsm(3, 5, 50, 300)); /// assert_eq!(leap + Duration::days(1), from_hmsm(3, 5, 59, 300)); -/// # } /// ``` impl Add for NaiveTime { type Output = NaiveTime; @@ -1106,7 +1092,6 @@ impl AddAssign for NaiveTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1117,24 +1102,20 @@ impl AddAssign for NaiveTime { /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::milliseconds(80), from_hmsm(3, 5, 6, 920)); /// assert_eq!(from_hmsm(3, 5, 7, 950) - Duration::milliseconds(280), from_hmsm(3, 5, 7, 670)); -/// # } /// ``` /// /// The subtraction wraps around. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } /// ``` /// /// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// let leap = from_hmsm(3, 5, 59, 1_300); @@ -1143,7 +1124,6 @@ impl AddAssign for NaiveTime { /// assert_eq!(leap - Duration::milliseconds(500), from_hmsm(3, 5, 59, 800)); /// assert_eq!(leap - Duration::seconds(60), from_hmsm(3, 5, 0, 300)); /// assert_eq!(leap - Duration::days(1), from_hmsm(3, 6, 0, 300)); -/// # } /// ``` impl Sub for NaiveTime { type Output = NaiveTime; @@ -1177,7 +1157,6 @@ impl SubAssign for NaiveTime { /// # Example /// /// ``` -/// # fn main() { /// use chrono::{Duration, NaiveTime}; /// /// let from_hmsm = NaiveTime::from_hms_milli; @@ -1191,14 +1170,12 @@ impl SubAssign for NaiveTime { /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), Duration::seconds(-3600)); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), /// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); -/// # } /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` -/// # fn main() { /// # use chrono::{Duration, NaiveTime}; /// # let from_hmsm = NaiveTime::from_hms_milli; /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); @@ -1208,7 +1185,6 @@ impl SubAssign for NaiveTime { /// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), Duration::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), /// Duration::seconds(61)); -/// # } /// ``` impl Sub for NaiveTime { type Output = OldDuration; From 4781689a39e88532ac09748b806c53b92d221ca5 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Mon, 28 Mar 2022 17:12:53 +0200 Subject: [PATCH 031/999] Remove fallible `fn main()` in doc examples --- src/datetime/serde.rs | 120 ++++++++---------------------------- src/format/mod.rs | 5 +- src/naive/datetime/serde.rs | 120 ++++++++---------------------------- 3 files changed, 49 insertions(+), 196 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 42afd6a575..8f1ce00601 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -128,7 +128,6 @@ impl<'de> de::Deserialize<'de> for DateTime { /// time: DateTime /// } /// -/// # fn example() -> Result { /// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); /// let my_s = S { /// time: time.clone(), @@ -138,9 +137,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_nanoseconds { use core::fmt; @@ -167,15 +164,12 @@ pub mod ts_nanoseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where @@ -200,11 +194,8 @@ pub mod ts_nanoseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -260,7 +251,6 @@ pub mod ts_nanoseconds { /// time: Option> /// } /// -/// # fn example() -> Result { /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); /// let my_s = S { /// time: time.clone(), @@ -270,9 +260,7 @@ pub mod ts_nanoseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_nanoseconds_option { use core::fmt; @@ -298,15 +286,12 @@ pub mod ts_nanoseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option>, serializer: S) -> Result where @@ -334,11 +319,8 @@ pub mod ts_nanoseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where @@ -398,7 +380,6 @@ pub mod ts_nanoseconds_option { /// time: DateTime /// } /// -/// # fn example() -> Result { /// let time = Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); /// let my_s = S { /// time: time.clone(), @@ -408,9 +389,7 @@ pub mod ts_nanoseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds { use core::fmt; @@ -436,15 +415,12 @@ pub mod ts_microseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where @@ -469,11 +445,8 @@ pub mod ts_microseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -529,7 +502,6 @@ pub mod ts_microseconds { /// time: Option> /// } /// -/// # fn example() -> Result { /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); /// let my_s = S { /// time: time.clone(), @@ -539,9 +511,7 @@ pub mod ts_microseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds_option { use core::fmt; @@ -566,15 +536,12 @@ pub mod ts_microseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option>, serializer: S) -> Result where @@ -602,11 +569,8 @@ pub mod ts_microseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where @@ -666,7 +630,6 @@ pub mod ts_microseconds_option { /// time: DateTime /// } /// -/// # fn example() -> Result { /// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); /// let my_s = S { /// time: time.clone(), @@ -676,9 +639,7 @@ pub mod ts_microseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds { use core::fmt; @@ -704,15 +665,12 @@ pub mod ts_milliseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where @@ -737,11 +695,8 @@ pub mod ts_milliseconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -794,7 +749,6 @@ pub mod ts_milliseconds { /// time: Option> /// } /// -/// # fn example() -> Result { /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); /// let my_s = S { /// time: time.clone(), @@ -804,9 +758,7 @@ pub mod ts_milliseconds { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds_option { use core::fmt; @@ -831,15 +783,12 @@ pub mod ts_milliseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option>, serializer: S) -> Result where @@ -874,16 +823,13 @@ pub mod ts_milliseconds_option { /// time: Option> /// } /// - /// # fn example() -> Result<(), serde_json::Error> { /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; /// assert_eq!(s, E::V(S { time: None })); /// let t: E = serde_json::from_str(r#"{}"#)?; /// assert_eq!(t, E::V(S { time: None })); - /// # Ok(()) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where @@ -944,7 +890,6 @@ pub mod ts_milliseconds_option { /// time: DateTime /// } /// -/// # fn example() -> Result { /// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); /// let my_s = S { /// time: time.clone(), @@ -954,9 +899,7 @@ pub mod ts_milliseconds_option { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds { use core::fmt; @@ -982,15 +925,12 @@ pub mod ts_seconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where @@ -1015,11 +955,8 @@ pub mod ts_seconds { /// time: DateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -1069,7 +1006,6 @@ pub mod ts_seconds { /// time: Option> /// } /// -/// # fn example() -> Result { /// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); /// let my_s = S { /// time: time.clone(), @@ -1079,9 +1015,7 @@ pub mod ts_seconds { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds_option { use core::fmt; @@ -1106,15 +1040,12 @@ pub mod ts_seconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option>, serializer: S) -> Result where @@ -1142,11 +1073,8 @@ pub mod ts_seconds_option { /// time: Option> /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where diff --git a/src/format/mod.rs b/src/format/mod.rs index 5d720c0b57..893496a035 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -18,8 +18,6 @@ //! # Example //! ```rust //! # use std::error::Error; -//! # -//! # fn main() -> Result<(), Box> { //! use chrono::prelude::*; //! //! let date_time = Utc.ymd(2020, 11, 10).and_hms(0, 1, 32); @@ -29,8 +27,7 @@ //! //! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?; //! assert_eq!(parsed, date_time); -//! # Ok(()) -//! # } +//! # Ok::<(), chrono::ParseError>(()) //! ``` #[cfg(feature = "alloc")] diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 3ac4c00f2b..27910fa7d3 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -67,7 +67,6 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// time: NaiveDateTime /// } /// -/// # fn example() -> Result { /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); /// let my_s = S { /// time: time.clone(), @@ -77,9 +76,7 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_nanoseconds { use core::fmt; @@ -104,15 +101,12 @@ pub mod ts_nanoseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where @@ -137,11 +131,8 @@ pub mod ts_nanoseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result where @@ -196,7 +187,6 @@ pub mod ts_nanoseconds { /// time: Option /// } /// -/// # fn example() -> Result { /// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); /// let my_s = S { /// time: time.clone(), @@ -206,9 +196,7 @@ pub mod ts_nanoseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_nanoseconds_option { use core::fmt; @@ -233,15 +221,12 @@ pub mod ts_nanoseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where @@ -269,11 +254,8 @@ pub mod ts_nanoseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -331,7 +313,6 @@ pub mod ts_nanoseconds_option { /// time: NaiveDateTime /// } /// -/// # fn example() -> Result { /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); /// let my_s = S { /// time: time.clone(), @@ -341,9 +322,7 @@ pub mod ts_nanoseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds { use core::fmt; @@ -368,15 +347,12 @@ pub mod ts_microseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where @@ -401,11 +377,8 @@ pub mod ts_microseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result where @@ -463,7 +436,6 @@ pub mod ts_microseconds { /// time: Option /// } /// -/// # fn example() -> Result { /// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); /// let my_s = S { /// time: time.clone(), @@ -473,9 +445,7 @@ pub mod ts_microseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_microseconds_option { use core::fmt; @@ -500,15 +470,12 @@ pub mod ts_microseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where @@ -536,11 +503,8 @@ pub mod ts_microseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -598,7 +562,6 @@ pub mod ts_microseconds_option { /// time: NaiveDateTime /// } /// -/// # fn example() -> Result { /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); /// let my_s = S { /// time: time.clone(), @@ -608,9 +571,7 @@ pub mod ts_microseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds { use core::fmt; @@ -635,15 +596,12 @@ pub mod ts_milliseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where @@ -668,11 +626,8 @@ pub mod ts_milliseconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result where @@ -727,7 +682,6 @@ pub mod ts_milliseconds { /// time: Option /// } /// -/// # fn example() -> Result { /// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); /// let my_s = S { /// time: time.clone(), @@ -737,9 +691,7 @@ pub mod ts_milliseconds { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_milliseconds_option { use core::fmt; @@ -764,15 +716,12 @@ pub mod ts_milliseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where @@ -800,11 +749,8 @@ pub mod ts_milliseconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where @@ -862,7 +808,6 @@ pub mod ts_milliseconds_option { /// time: NaiveDateTime /// } /// -/// # fn example() -> Result { /// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); /// let my_s = S { /// time: time.clone(), @@ -872,9 +817,7 @@ pub mod ts_milliseconds_option { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds { use core::fmt; @@ -899,15 +842,12 @@ pub mod ts_seconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where @@ -932,11 +872,8 @@ pub mod ts_seconds { /// time: NaiveDateTime /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result where @@ -988,7 +925,6 @@ pub mod ts_seconds { /// time: Option /// } /// -/// # fn example() -> Result { /// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)); /// let my_s = S { /// time: time.clone(), @@ -998,9 +934,7 @@ pub mod ts_seconds { /// assert_eq!(as_string, r#"{"time":1526522699}"#); /// let my_s: S = serde_json::from_str(&as_string)?; /// assert_eq!(my_s.time, time); -/// # Ok(my_s) -/// # } -/// # fn main() { example().unwrap(); } +/// # Ok::<(), serde_json::Error>(()) /// ``` pub mod ts_seconds_option { use core::fmt; @@ -1025,15 +959,12 @@ pub mod ts_seconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s = S { /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn serialize(opt: &Option, serializer: S) -> Result where @@ -1061,11 +992,8 @@ pub mod ts_seconds_option { /// time: Option /// } /// - /// # fn example() -> Result { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } + /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where From 82407ffa9e3f594f577868062ac76622eb1cfd52 Mon Sep 17 00:00:00 2001 From: Constantin Nickel Date: Tue, 5 Apr 2022 15:08:46 +0200 Subject: [PATCH 032/999] Update `bincode` to 1.3 --- Cargo.toml | 2 +- src/datetime/serde.rs | 4 ++-- src/naive/date.rs | 4 ++-- src/naive/datetime/serde.rs | 8 ++++---- src/naive/time/serde.rs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a7c37cd86..9e2370d071 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "tim [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } -bincode = { version = "0.8.0" } +bincode = { version = "1.3.0" } num-iter = { version = "0.1.35", default-features = false } doc-comment = { version = "0.3" } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 8f1ce00601..556c3ac99a 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1137,10 +1137,10 @@ fn test_serde_deserialize() { fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize}; let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); - let encoded = serialize(&dt, Infinite).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/naive/date.rs b/src/naive/date.rs index 7e3e0530c1..0bc880f24b 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1879,10 +1879,10 @@ mod serde { fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize}; let d = NaiveDate::from_ymd(2014, 7, 24); - let encoded = serialize(&d, Infinite).unwrap(); + let encoded = serialize(&d).unwrap(); let decoded: NaiveDate = deserialize(&encoded).unwrap(); assert_eq!(d, decoded); } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 27910fa7d3..bad948ad13 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1052,10 +1052,10 @@ fn test_serde_deserialize() { #[test] fn test_serde_bincode() { use crate::naive::NaiveDate; - use bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize}; let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); - let encoded = serialize(&dt, Infinite).unwrap(); + let encoded = serialize(&dt).unwrap(); let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); assert_eq!(dt, decoded); } @@ -1064,7 +1064,7 @@ fn test_serde_bincode() { fn test_serde_bincode_optional() { use crate::prelude::*; use crate::serde::ts_nanoseconds_option; - use bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize}; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1075,7 +1075,7 @@ fn test_serde_bincode_optional() { } let expected = Test { one: Some(1), two: Some(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; - let bytes: Vec = serialize(&expected, Infinite).unwrap(); + let bytes: Vec = serialize(&expected).unwrap(); let actual = deserialize::(&(bytes)).unwrap(); assert_eq!(expected, actual); diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index 0a60ae55bc..6df610ca43 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -54,10 +54,10 @@ fn test_serde_deserialize() { fn test_serde_bincode() { // Bincode is relevant to test separately from JSON because // it is not self-describing. - use bincode::{deserialize, serialize, Infinite}; + use bincode::{deserialize, serialize}; let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - let encoded = serialize(&t, Infinite).unwrap(); + let encoded = serialize(&t).unwrap(); let decoded: NaiveTime = deserialize(&encoded).unwrap(); assert_eq!(t, decoded); } From a12cf7b724a2a9d5a56d9891eecb241b97327c33 Mon Sep 17 00:00:00 2001 From: Yureka Date: Fri, 17 Sep 2021 18:05:37 +0200 Subject: [PATCH 033/999] use stub for anything not unix and not windows --- src/sys/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 2dc3453625..25fd87a5ca 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -16,15 +16,15 @@ use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(any(target_arch = "wasm32", target_env = "sgx"))] +#[cfg(all(not(unix), not(windows)))] #[path = "stub.rs"] mod inner; -#[cfg(all(unix, not(target_arch = "wasm32"), not(target_env = "sgx")))] +#[cfg(unix)] #[path = "unix.rs"] mod inner; -#[cfg(all(windows, not(target_arch = "wasm32"), not(target_env = "sgx")))] +#[cfg(windows)] #[path = "windows.rs"] mod inner; From a9ec3c9ca32fbde2769f75dcc5480fc03d3aae56 Mon Sep 17 00:00:00 2001 From: Yureka Date: Fri, 17 Sep 2021 18:10:29 +0200 Subject: [PATCH 034/999] add wasm32-unknown-emscripten test --- .github/workflows/test.yml | 28 ++++++++++++++++++++++++++++ ci/github.sh | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 710ad46526..b32c9f47f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,6 +121,34 @@ jobs: RUST_VERSION: stable WASM: wasm_simple + wasm_emscripten: + strategy: + matrix: + os: [macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-emscripten + override: true + + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Build and Test + run: bash ci/github.sh + env: + RUST_VERSION: stable + WASM: wasm_emscripten + cross-targets: strategy: matrix: diff --git a/ci/github.sh b/ci/github.sh index 207cb08a04..081a128b90 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -34,6 +34,8 @@ meaningful in the github actions feature matrix UI. test_wasm elif [[ ${WASM:-} == wasm_simple ]]; then test_wasm_simple + elif [[ ${WASM:-} == wasm_emscripten ]]; then + test_wasm_emscripten elif [[ ${CORE:-} == no_std ]]; then test_core elif [[ ${EXHAUSTIVE_TZ:-} == all_tzs ]]; then @@ -112,4 +114,8 @@ test_wasm_simple() { fi } +test_wasm_emscripten() { + runt cargo build --target wasm32-unknown-emscripten +} + main "$@" From 4cfcd405d195c060275c560b4dfc0e796319e6db Mon Sep 17 00:00:00 2001 From: Yureka Date: Fri, 17 Sep 2021 18:28:47 +0200 Subject: [PATCH 035/999] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92590cd39b..8c07750da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add optional rkyv support. * Add support for microseconds timestamps serde serialization for `NaiveDateTime`. * Add support for optional timestamps serde serialization for `NaiveDateTime`. +* Fix build for wasm32-unknown-emscripten (@yu-re-ka #593) ## 0.4.19 From 49838124ff838c7c2717a4adff445efec96c6a0a Mon Sep 17 00:00:00 2001 From: Yureka Date: Sat, 9 Apr 2022 10:21:15 +0200 Subject: [PATCH 036/999] some lint errors from the 1.60 upgrade --- src/offset/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index cc04bc568e..237b5505f1 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -430,6 +430,7 @@ pub trait TimeZone: Sized + Clone { fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. + #[allow(clippy::wrong_self_convention)] fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected @@ -438,6 +439,7 @@ pub trait TimeZone: Sized + Clone { } /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. + #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { self.offset_from_local_datetime(local) .map(|offset| DateTime::from_utc(*local - offset.fix(), offset)) @@ -451,12 +453,14 @@ pub trait TimeZone: Sized + Clone { /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { Date::from_utc(*utc, self.offset_from_utc_date(utc)) } /// Converts the UTC `NaiveDateTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc)) } From 29cbd5945c16d7846b0629563a44fd12b9eff142 Mon Sep 17 00:00:00 2001 From: danielalvsaaker <30574112+danielalvsaaker@users.noreply.github.com> Date: Sat, 9 Apr 2022 12:17:43 +0000 Subject: [PATCH 037/999] Update absolute path to 2018 syntax for WASM/wasmbind feature (#672) --- src/offset/local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index f1da534212..1e598f2b6a 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -155,7 +155,7 @@ impl TimeZone for Local { let mut local = local.clone(); // Get the offset from the js runtime let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - local -= ::Duration::seconds(offset.local_minus_utc() as i64); + local -= crate::Duration::seconds(offset.local_minus_utc() as i64); LocalResult::Single(DateTime::from_utc(local, offset)) } From 5f59b2c24fd9574a9cbd4c0b9abf4c23e30c1142 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 15:15:09 +0200 Subject: [PATCH 038/999] Move sys module into offset, which is the only place using it --- src/lib.rs | 6 ------ src/offset/local.rs | 4 ++-- src/offset/mod.rs | 6 ++++++ src/{ => offset}/sys/mod.rs | 12 ++++++------ src/{ => offset}/sys/stub.rs | 0 src/{ => offset}/sys/unix.rs | 0 src/{ => offset}/sys/windows.rs | 0 7 files changed, 14 insertions(+), 14 deletions(-) rename src/{ => offset}/sys/mod.rs (94%) rename src/{ => offset}/sys/stub.rs (100%) rename src/{ => offset}/sys/unix.rs (100%) rename src/{ => offset}/sys/windows.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 2808abb5ca..d81cfe1e51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -429,12 +429,6 @@ mod oldtime; // this reexport is to aid the transition and should not be in the prelude! pub use oldtime::Duration; -#[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) -))] -mod sys; - #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] use doc_comment::doctest; diff --git a/src/offset/local.rs b/src/offset/local.rs index 1e598f2b6a..13484507be 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -7,12 +7,12 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +use super::sys::{self, Timespec}; use super::{LocalResult, TimeZone}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::naive::NaiveTime; use crate::naive::{NaiveDate, NaiveDateTime}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use crate::sys::{self, Timespec}; use crate::{Date, DateTime}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::{Datelike, Timelike}; diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 237b5505f1..9ec962001e 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -25,6 +25,12 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; use crate::{Date, DateTime}; +#[cfg(all( + feature = "clock", + not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) +))] +mod sys; + /// The conversion result from the local time to the timezone-aware datetime types. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] pub enum LocalResult { diff --git a/src/sys/mod.rs b/src/offset/sys/mod.rs similarity index 94% rename from src/sys/mod.rs rename to src/offset/sys/mod.rs index 25fd87a5ca..cc7ead332b 100644 --- a/src/sys/mod.rs +++ b/src/offset/sys/mod.rs @@ -35,21 +35,21 @@ use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; /// /// For example a timespec of 1.2 seconds after the beginning of the epoch would /// be represented as {sec: 1, nsec: 200000000}. -pub(crate) struct Timespec { - pub(crate) sec: i64, - pub(crate) nsec: i32, +pub(super) struct Timespec { + sec: i64, + nsec: i32, } impl Timespec { /// Constructs a timespec representing the current time in UTC. - pub(crate) fn now() -> Timespec { + pub(super) fn now() -> Timespec { let st = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } } /// Converts this timespec into the system's local time. - pub(crate) fn local(self) -> Tm { + pub(super) fn local(self) -> Tm { let mut tm = Tm { tm_sec: 0, tm_min: 0, @@ -118,7 +118,7 @@ pub(crate) struct Tm { impl Tm { /// Convert time to the seconds from January 1, 1970 - pub(crate) fn to_timespec(&self) -> Timespec { + pub(super) fn to_timespec(&self) -> Timespec { let sec = match self.tm_utcoff { 0 => utc_tm_to_time(self), _ => local_tm_to_time(self), diff --git a/src/sys/stub.rs b/src/offset/sys/stub.rs similarity index 100% rename from src/sys/stub.rs rename to src/offset/sys/stub.rs diff --git a/src/sys/unix.rs b/src/offset/sys/unix.rs similarity index 100% rename from src/sys/unix.rs rename to src/offset/sys/unix.rs diff --git a/src/sys/windows.rs b/src/offset/sys/windows.rs similarity index 100% rename from src/sys/windows.rs rename to src/offset/sys/windows.rs From b36663b03b805cb533d0b7f59d8212096daef25d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 15:23:18 +0200 Subject: [PATCH 039/999] Fold top-level sys module into offset::local --- src/offset/local.rs | 125 ++++++++++++++++++++++++++++++++++++++--- src/offset/mod.rs | 25 ++++----- src/offset/sys/mod.rs | 128 ------------------------------------------ 3 files changed, 128 insertions(+), 150 deletions(-) delete mode 100644 src/offset/sys/mod.rs diff --git a/src/offset/local.rs b/src/offset/local.rs index 13484507be..9bf8e379dd 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -3,12 +3,12 @@ //! The local (system) time zone. +use std::time::{SystemTime, UNIX_EPOCH}; + #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use super::sys::{self, Timespec}; use super::{LocalResult, TimeZone}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::naive::NaiveTime; @@ -17,23 +17,37 @@ use crate::{Date, DateTime}; #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] use crate::{Datelike, Timelike}; +#[cfg(all(not(unix), not(windows)))] +#[path = "sys/stub.rs"] +mod inner; + +#[cfg(unix)] +#[path = "sys/unix.rs"] +mod inner; + +#[cfg(windows)] +#[path = "sys/windows.rs"] +mod inner; + +use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; + /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn tm_to_datetime(mut tm: sys::Tm) -> DateTime { +fn tm_to_datetime(mut tm: Tm) -> DateTime { if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; tm.tm_sec = 59; } #[cfg(not(windows))] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { + fn tm_to_naive_date(tm: &Tm) -> NaiveDate { // from_yo is more efficient than from_ymd (since it's the internal representation). NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) } #[cfg(windows)] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { + fn tm_to_naive_date(tm: &Tm) -> NaiveDate { // ...but tm_yday is broken in Windows (issue #85) NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) } @@ -51,13 +65,13 @@ fn tm_to_datetime(mut tm: sys::Tm) -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec { +fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> Timespec { // well, this exploits an undocumented `Tm::to_timespec` behavior // to get the exact function we want (either `timegm` or `mktime`). // the number 1 is arbitrary but should be non-zero to trigger `mktime`. let tm_utcoff = if local { 1 } else { 0 }; - let tm = sys::Tm { + let tm = Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, tm_hour: d.hour() as i32, @@ -196,6 +210,103 @@ impl TimeZone for Local { } } +/// A record specifying a time value in seconds and nanoseconds, where +/// nanoseconds represent the offset from the given second. +/// +/// For example a timespec of 1.2 seconds after the beginning of the epoch would +/// be represented as {sec: 1, nsec: 200000000}. +pub(super) struct Timespec { + sec: i64, + nsec: i32, +} + +impl Timespec { + /// Constructs a timespec representing the current time in UTC. + pub(super) fn now() -> Timespec { + let st = + SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); + Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } + } + + /// Converts this timespec into the system's local time. + pub(super) fn local(self) -> Tm { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + time_to_local_tm(self.sec, &mut tm); + tm.tm_nsec = self.nsec; + tm + } +} + +/// Holds a calendar date and time broken down into its components (year, month, +/// day, and so on), also called a broken-down time value. +// FIXME: use c_int instead of i32? +#[cfg(feature = "clock")] +#[repr(C)] +pub(crate) struct Tm { + /// Seconds after the minute - [0, 60] + pub(crate) tm_sec: i32, + + /// Minutes after the hour - [0, 59] + pub(crate) tm_min: i32, + + /// Hours after midnight - [0, 23] + pub(crate) tm_hour: i32, + + /// Day of the month - [1, 31] + pub(crate) tm_mday: i32, + + /// Months since January - [0, 11] + pub(crate) tm_mon: i32, + + /// Years since 1900 + pub(crate) tm_year: i32, + + /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + pub(crate) tm_wday: i32, + + /// Days since January 1 - [0, 365] + pub(crate) tm_yday: i32, + + /// Daylight Saving Time flag. + /// + /// This value is positive if Daylight Saving Time is in effect, zero if + /// Daylight Saving Time is not in effect, and negative if this information + /// is not available. + pub(crate) tm_isdst: i32, + + /// Identifies the time zone that was used to compute this broken-down time + /// value, including any adjustment for Daylight Saving Time. This is the + /// number of seconds east of UTC. For example, for U.S. Pacific Daylight + /// Time, the value is `-7*60*60 = -25200`. + pub(crate) tm_utcoff: i32, + + /// Nanoseconds after the second - [0, 109 - 1] + pub(crate) tm_nsec: i32, +} + +impl Tm { + /// Convert time to the seconds from January 1, 1970 + pub(super) fn to_timespec(&self) -> Timespec { + let sec = match self.tm_utcoff { + 0 => utc_tm_to_time(self), + _ => local_tm_to_time(self), + }; + Timespec { sec, nsec: self.tm_nsec } + } +} + #[cfg(test)] mod tests { use super::Local; diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 9ec962001e..eb89680ae9 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -25,11 +25,16 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; use crate::{Date, DateTime}; -#[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) -))] -mod sys; +mod fixed; +pub use self::fixed::FixedOffset; + +#[cfg(feature = "clock")] +mod local; +#[cfg(feature = "clock")] +pub use self::local::Local; + +mod utc; +pub use self::utc::Utc; /// The conversion result from the local time to the timezone-aware datetime types. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] @@ -472,16 +477,6 @@ pub trait TimeZone: Sized + Clone { } } -mod fixed; -#[cfg(feature = "clock")] -mod local; -mod utc; - -pub use self::fixed::FixedOffset; -#[cfg(feature = "clock")] -pub use self::local::Local; -pub use self::utc::Utc; - #[cfg(test)] mod tests { use super::*; diff --git a/src/offset/sys/mod.rs b/src/offset/sys/mod.rs deleted file mode 100644 index cc7ead332b..0000000000 --- a/src/offset/sys/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Platform wrappers for converting UTC times to and from the local time zone. -//! -//! This code was rescued from v0.1 of the time crate, which is no longer -//! maintained. It has been substantially stripped down to the bare minimum -//! required by chrono. - -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(all(not(unix), not(windows)))] -#[path = "stub.rs"] -mod inner; - -#[cfg(unix)] -#[path = "unix.rs"] -mod inner; - -#[cfg(windows)] -#[path = "windows.rs"] -mod inner; - -use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -pub(super) struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - pub(super) fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - - /// Converts this timespec into the system's local time. - pub(super) fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[cfg(feature = "clock")] -#[repr(C)] -pub(crate) struct Tm { - /// Seconds after the minute - [0, 60] - pub(crate) tm_sec: i32, - - /// Minutes after the hour - [0, 59] - pub(crate) tm_min: i32, - - /// Hours after midnight - [0, 23] - pub(crate) tm_hour: i32, - - /// Day of the month - [1, 31] - pub(crate) tm_mday: i32, - - /// Months since January - [0, 11] - pub(crate) tm_mon: i32, - - /// Years since 1900 - pub(crate) tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - pub(crate) tm_wday: i32, - - /// Days since January 1 - [0, 365] - pub(crate) tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - pub(crate) tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - pub(crate) tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - pub(crate) tm_nsec: i32, -} - -impl Tm { - /// Convert time to the seconds from January 1, 1970 - pub(super) fn to_timespec(&self) -> Timespec { - let sec = match self.tm_utcoff { - 0 => utc_tm_to_time(self), - _ => local_tm_to_time(self), - }; - Timespec { sec, nsec: self.tm_nsec } - } -} From 5ac1934bfed432b43c921a102e2cf2685e661126 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 15:32:17 +0200 Subject: [PATCH 040/999] Inline single-use method Tm::to_timespec() --- src/offset/local.rs | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index 9bf8e379dd..fd14835023 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -66,11 +66,6 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> Timespec { - // well, this exploits an undocumented `Tm::to_timespec` behavior - // to get the exact function we want (either `timegm` or `mktime`). - // the number 1 is arbitrary but should be non-zero to trigger `mktime`. - let tm_utcoff = if local { 1 } else { 0 }; - let tm = Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, @@ -81,12 +76,19 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> Timespec { tm_wday: 0, // to_local ignores this tm_yday: 0, // and this tm_isdst: -1, - tm_utcoff, + // This seems pretty fake? + tm_utcoff: if local { 1 } else { 0 }, // do not set this, OS APIs are heavily inconsistent in terms of leap second handling tm_nsec: 0, }; - tm.to_timespec() + Timespec { + sec: match local { + false => utc_tm_to_time(&tm), + true => local_tm_to_time(&tm), + }, + nsec: tm.tm_nsec, + } } /// The local timescale. This is implemented via the standard `time` crate. @@ -296,17 +298,6 @@ pub(crate) struct Tm { pub(crate) tm_nsec: i32, } -impl Tm { - /// Convert time to the seconds from January 1, 1970 - pub(super) fn to_timespec(&self) -> Timespec { - let sec = match self.tm_utcoff { - 0 => utc_tm_to_time(self), - _ => local_tm_to_time(self), - }; - Timespec { sec, nsec: self.tm_nsec } - } -} - #[cfg(test)] mod tests { use super::Local; From 8c53efe3ccb49d9a379e4d0997bb08e119af5873 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 15:40:36 +0200 Subject: [PATCH 041/999] Deduplicate logic in datetime_to_timespec() callers --- src/offset/local.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index fd14835023..72418ef78e 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -65,7 +65,7 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> Timespec { +fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { let tm = Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, @@ -82,13 +82,20 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> Timespec { tm_nsec: 0, }; - Timespec { + let spec = Timespec { sec: match local { false => utc_tm_to_time(&tm), true => local_tm_to_time(&tm), }, nsec: tm.tm_nsec, - } + }; + + // Adjust for leap seconds + let mut tm = spec.local(); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = d.nanosecond() as i32; + + tm_to_datetime(tm) } /// The local timescale. This is implemented via the standard `time` crate. @@ -177,14 +184,7 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let timespec = datetime_to_timespec(local, true); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = local.nanosecond() as i32; - - LocalResult::Single(tm_to_datetime(tm)) + LocalResult::Single(naive_to_local(local, true)) } fn from_utc_date(&self, utc: &NaiveDate) -> Date { @@ -201,14 +201,7 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - let timespec = datetime_to_timespec(utc, false); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = utc.nanosecond() as i32; - - tm_to_datetime(tm) + naive_to_local(utc, false) } } From 279f5907879c9618767e5501ac01f79d5b7a4b38 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 15:46:45 +0200 Subject: [PATCH 042/999] Reorder local module in top-down order --- src/offset/local.rs | 134 ++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index 72418ef78e..07fdfc34d3 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -31,73 +31,6 @@ mod inner; use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn tm_to_datetime(mut tm: Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - #[cfg(not(windows))] - fn tm_to_naive_date(tm: &Tm) -> NaiveDate { - // from_yo is more efficient than from_ymd (since it's the internal representation). - NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) - } - - #[cfg(windows)] - fn tm_to_naive_date(tm: &Tm) -> NaiveDate { - // ...but tm_yday is broken in Windows (issue #85) - NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) - } - - let date = tm_to_naive_date(&tm); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - let offset = FixedOffset::east(tm.tm_utcoff); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - let spec = Timespec { - sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), - }, - nsec: tm.tm_nsec, - }; - - // Adjust for leap seconds - let mut tm = spec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - tm_to_datetime(tm) -} - /// The local timescale. This is implemented via the standard `time` crate. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods @@ -205,6 +138,73 @@ impl TimeZone for Local { } } +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { + let tm = Tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, // yes, C is that strange... + tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + // This seems pretty fake? + tm_utcoff: if local { 1 } else { 0 }, + // do not set this, OS APIs are heavily inconsistent in terms of leap second handling + tm_nsec: 0, + }; + + let spec = Timespec { + sec: match local { + false => utc_tm_to_time(&tm), + true => local_tm_to_time(&tm), + }, + nsec: tm.tm_nsec, + }; + + // Adjust for leap seconds + let mut tm = spec.local(); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = d.nanosecond() as i32; + + tm_to_datetime(tm) +} + +/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. +/// This assumes that `time` is working correctly, i.e. any error is fatal. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +fn tm_to_datetime(mut tm: Tm) -> DateTime { + if tm.tm_sec >= 60 { + tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + tm.tm_sec = 59; + } + + #[cfg(not(windows))] + fn tm_to_naive_date(tm: &Tm) -> NaiveDate { + // from_yo is more efficient than from_ymd (since it's the internal representation). + NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) + } + + #[cfg(windows)] + fn tm_to_naive_date(tm: &Tm) -> NaiveDate { + // ...but tm_yday is broken in Windows (issue #85) + NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) + } + + let date = tm_to_naive_date(&tm); + let time = NaiveTime::from_hms_nano( + tm.tm_hour as u32, + tm.tm_min as u32, + tm.tm_sec as u32, + tm.tm_nsec as u32, + ); + let offset = FixedOffset::east(tm.tm_utcoff); + DateTime::from_utc(date.and_time(time) - offset, offset) +} + /// A record specifying a time value in seconds and nanoseconds, where /// nanoseconds represent the offset from the given second. /// From 4333e39d13cd0781cc935e8363b43b4e46b71071 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Tue, 26 Apr 2022 17:13:23 +0800 Subject: [PATCH 043/999] fix some typos Signed-off-by: cuishuang --- src/datetime/mod.rs | 4 ++-- src/format/scan.rs | 2 +- src/naive/datetime/mod.rs | 2 +- src/naive/isoweek.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 4dd2102377..fe41b4cc0f 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -221,7 +221,7 @@ impl DateTime { /// 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 - /// millenia, I'm curious.) + /// millennia, I'm curious.) /// /// # Example /// @@ -453,7 +453,7 @@ impl DateTime { /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. /// - /// RFC 2822 is the internet message standard that specifices the + /// RFC 2822 is the internet message standard that specifies the /// representation of times in HTTP and email headers. /// /// ``` diff --git a/src/format/scan.rs b/src/format/scan.rs index 6969c81a1e..856fa2f567 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -343,7 +343,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> } } -/// Tries to consume everyting until next whitespace-like symbol. +/// Tries to consume everything until next whitespace-like symbol. /// Does not provide any offset information from the consumed data. pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 950fa239e7..8b28692c42 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -335,7 +335,7 @@ impl NaiveDateTime { /// 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 millenia, I'm curious.) + /// precision over millennia, I'm curious.) /// /// # Example /// diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 2d98257c44..3cadf6b102 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -10,7 +10,7 @@ use super::internals::{DateImpl, Of, YearFlags}; /// ISO 8601 week. /// /// This type, combined with [`Weekday`](../enum.Weekday.html), -/// constitues the ISO 8601 [week date](./struct.NaiveDate.html#week-date). +/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] From 7097f933eb16d4fa5bb94b2a44e7d69c62af755d Mon Sep 17 00:00:00 2001 From: Ethan D Twardy Date: Thu, 12 May 2022 07:45:47 -0500 Subject: [PATCH 044/999] Fix "can't leak crate-private type in stub.rs" #662 (#684) First attempt to fix this issue for wasm32-unknown-* targets by changing visibility of functions not to leak crate-private types. Ensure the default toolchain is used for the wasm_unknown CI test. --- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ ci/github.sh | 6 ++++++ src/offset/sys/stub.rs | 6 +++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b32c9f47f4..708b6e50f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,6 +149,30 @@ jobs: RUST_VERSION: stable WASM: wasm_emscripten + wasm_unknown: + strategy: + matrix: + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + override: true + default: true + + - name: Build and Test + run: bash ci/github.sh + env: + RUST_VERSION: stable + WASM: wasm_unknown + cross-targets: strategy: matrix: diff --git a/ci/github.sh b/ci/github.sh index 081a128b90..6512f9a7ee 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -36,6 +36,8 @@ meaningful in the github actions feature matrix UI. test_wasm_simple elif [[ ${WASM:-} == wasm_emscripten ]]; then test_wasm_emscripten + elif [[ ${WASM:-} == wasm_unknown ]]; then + test_wasm_unknown elif [[ ${CORE:-} == no_std ]]; then test_core elif [[ ${EXHAUSTIVE_TZ:-} == all_tzs ]]; then @@ -118,4 +120,8 @@ test_wasm_emscripten() { runt cargo build --target wasm32-unknown-emscripten } +test_wasm_unknown() { + runt cargo build --target wasm32-unknown-unknown +} + main "$@" diff --git a/src/offset/sys/stub.rs b/src/offset/sys/stub.rs index 9172a85223..616b52fe5e 100644 --- a/src/offset/sys/stub.rs +++ b/src/offset/sys/stub.rs @@ -65,16 +65,16 @@ fn tm_to_time(tm: &Tm) -> i64 { + s } -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { +pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { // FIXME: Add timezone logic time_to_tm(sec, tm); } -pub fn utc_tm_to_time(tm: &Tm) -> i64 { +pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { tm_to_time(tm) } -pub fn local_tm_to_time(tm: &Tm) -> i64 { +pub(super) fn local_tm_to_time(tm: &Tm) -> i64 { // FIXME: Add timezone logic tm_to_time(tm) } From 8f9b08a01fb79bc4fdb84805bb85862cc046eb57 Mon Sep 17 00:00:00 2001 From: Flurin Feuerstein Date: Wed, 18 May 2022 13:37:59 +0200 Subject: [PATCH 045/999] Fix typo in month.rs --- src/month.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/month.rs b/src/month.rs index 8d549d4a3d..a87fe51ee0 100644 --- a/src/month.rs +++ b/src/month.rs @@ -81,7 +81,7 @@ impl Month { /// /// `m`: | `January` | `February` | `...` | `December` /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `December` | `January` | `...` | `November` + /// `m.pred()`: | `December` | `January` | `...` | `November` #[inline] pub fn pred(&self) -> Month { match *self { From 5bf8016068223564d8f29a28c9b4383bca7ec15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Mon, 23 May 2022 19:28:58 +0200 Subject: [PATCH 046/999] Add support for double ended days+weeks iterators This allows users to use `.rev()` on `.iter_days()` and `.iter_weeks()`, in order to iterate on dates backwards. --- CHANGELOG.md | 1 + src/naive/date.rs | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c07750da2..839bdeeda1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add support for microseconds timestamps serde serialization for `NaiveDateTime`. * Add support for optional timestamps serde serialization for `NaiveDateTime`. * Fix build for wasm32-unknown-emscripten (@yu-re-ka #593) +* Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` ## 0.4.19 diff --git a/src/naive/date.rs b/src/naive/date.rs index 0bc880f24b..08c6869323 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1044,7 +1044,7 @@ impl NaiveDate { self.format_with_items(StrftimeItems::new(fmt)) } - /// Returns an iterator that steps by days until the last representable date. + /// Returns an iterator that steps by days across all representable dates. /// /// # Example /// @@ -1064,13 +1064,18 @@ impl NaiveDate { /// count += 1; /// } /// assert_eq!(count, 4); + /// + /// for d in NaiveDate::from_ymd(2016, 3, 1).iter_days().rev().take(4) { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } /// ``` #[inline] pub fn iter_days(&self) -> NaiveDateDaysIterator { NaiveDateDaysIterator { value: *self } } - /// Returns an iterator that steps by weeks until the last representable date. + /// Returns an iterator that steps by weeks across all representable dates. /// /// # Example /// @@ -1090,6 +1095,11 @@ impl NaiveDate { /// count += 1; /// } /// assert_eq!(count, 4); + /// + /// for d in NaiveDate::from_ymd(2016, 3, 19).iter_weeks().rev().take(4) { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } /// ``` #[inline] pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { @@ -1577,6 +1587,17 @@ impl Iterator for NaiveDateDaysIterator { impl ExactSizeIterator for NaiveDateDaysIterator {} +impl DoubleEndedIterator for NaiveDateDaysIterator { + fn next_back(&mut self) -> Option { + if self.value == MIN_DATE { + return None; + } + let current = self.value; + self.value = current.pred(); + Some(current) + } +} + #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateWeeksIterator { value: NaiveDate, @@ -1602,6 +1623,17 @@ impl Iterator for NaiveDateWeeksIterator { impl ExactSizeIterator for NaiveDateWeeksIterator {} +impl DoubleEndedIterator for NaiveDateWeeksIterator { + fn next_back(&mut self) -> Option { + if self.value - MIN_DATE < OldDuration::weeks(1) { + return None; + } + let current = self.value; + self.value = current - OldDuration::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 @@ -2405,10 +2437,12 @@ mod tests { #[test] fn test_day_iterator_limit() { assert_eq!(NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd(-262144, 1, 3).iter_days().rev().take(4).count(), 2); } #[test] fn test_week_iterator_limit() { assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 2); } } From e920210e2f94ad85ff25c30ae902442d4e2872c2 Mon Sep 17 00:00:00 2001 From: Weiyuan Wu Date: Tue, 7 Jun 2022 01:51:02 +0000 Subject: [PATCH 047/999] add missing rkyv derives --- src/datetime/mod.rs | 4 ++++ src/month.rs | 4 ++++ src/naive/isoweek.rs | 4 ++++ src/oldtime.rs | 4 ++++ src/weekday.rs | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index fe41b4cc0f..ab924a92ab 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -32,6 +32,9 @@ use crate::oldtime::Duration as OldDuration; use crate::Date; use crate::{Datelike, Timelike, Weekday}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -79,6 +82,7 @@ pub enum SecondsFormat { /// the general-purpose constructors are all via the methods on the /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. #[derive(Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct DateTime { datetime: NaiveDateTime, offset: Tz::Offset, diff --git a/src/month.rs b/src/month.rs index a87fe51ee0..7b642f9ed9 100644 --- a/src/month.rs +++ b/src/month.rs @@ -1,5 +1,8 @@ use core::fmt; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + /// The month of the year. /// /// This enum is just a convenience implementation. @@ -26,6 +29,7 @@ use core::fmt; // Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub enum Month { /// January January = 0, diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 3cadf6b102..dbddb03499 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -7,6 +7,9 @@ use core::fmt; use super::internals::{DateImpl, Of, YearFlags}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + /// ISO 8601 week. /// /// This type, combined with [`Weekday`](../enum.Weekday.html), @@ -14,6 +17,7 @@ use super::internals::{DateImpl, Of, YearFlags}; /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, diff --git a/src/oldtime.rs b/src/oldtime.rs index 388b71a47d..57d199e2d8 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -16,6 +16,9 @@ use core::{fmt, i64}; #[cfg(any(feature = "std", test))] use std::error::Error; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + /// The number of nanoseconds in a microsecond. const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. @@ -48,6 +51,7 @@ macro_rules! try_opt { /// /// This also allows for the negative duration; see individual methods for details. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Duration { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC diff --git a/src/weekday.rs b/src/weekday.rs index 3b9d839f27..58f61f2fcb 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -1,5 +1,8 @@ use core::fmt; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + /// The day of week. /// /// The order of the days of week depends on the context. @@ -7,6 +10,7 @@ use core::fmt; /// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub enum Weekday { /// Monday. Mon = 0, From 6d392f077e5278262300de7a1de6d864f49c397f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jun 2022 10:41:18 +0200 Subject: [PATCH 048/999] Stop testing very old Ubuntu --- .github/workflows/test-release.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 4072db5a7d..9324524a46 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -12,7 +12,7 @@ jobs: test: strategy: matrix: - os: [ubuntu-18.04, ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] rust_version: [stable] include: # check all tzs on most-recent OS's @@ -30,7 +30,7 @@ jobs: rust_version: beta - os: ubuntu-latest rust_version: nightly - - os: ubuntu-18.04 + - os: ubuntu-20.04 rust_version: 1.32.0 - os: macos-latest rust_version: 1.32.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 708b6e50f4..96cda85889 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: test: strategy: matrix: - os: [ubuntu-18.04, ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] rust_version: [stable] exhaustive_tz: [onetz] check_combinatoric: [no_combinatoric] @@ -38,7 +38,7 @@ jobs: rust_version: beta - os: ubuntu-latest rust_version: nightly - - os: ubuntu-18.04 + - os: ubuntu-20.04 rust_version: 1.32.0 - os: macos-latest rust_version: 1.32.0 From 3a7d0e4806201a994c1c050f218fb1d879b60b5f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jun 2022 10:41:44 +0200 Subject: [PATCH 049/999] Fix formatting in GitHub workflows --- .github/workflows/lint.yml | 2 +- .github/workflows/test-release.yml | 48 ++++++++++++------------- .github/workflows/test.yml | 58 +++++++++++++++--------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c87878f1f6..4c1d62bab7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,4 +23,4 @@ jobs: - name: Run ShellCheck uses: ludeeus/action-shellcheck@0.3.0 with: - check_together: 'y' + check_together: "y" diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 9324524a46..d2d29cb350 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -2,9 +2,9 @@ name: Release Test on: push: - branches: ['rel*'] + branches: ["rel*"] pull_request: - branches: ['rel*'] + branches: ["rel*"] # From here down this should be exactly the same as test.yml @@ -15,27 +15,27 @@ jobs: os: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] rust_version: [stable] include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-20.04 - rust_version: 1.32.0 - - os: macos-latest - rust_version: 1.32.0 - - os: windows-latest - rust_version: 1.32.0 + # check all tzs on most-recent OS's + - os: ubuntu-latest + rust_version: stable + exhaustive_tz: all_tzs + - os: windows-latest + rust_version: stable + exhaustive_tz: all_tzs + - os: macos-latest + rust_version: stable + exhaustive_tz: all_tzs + # test other rust versions + - os: ubuntu-latest + rust_version: beta + - os: ubuntu-latest + rust_version: nightly + - os: ubuntu-20.04 + rust_version: 1.32.0 + - os: macos-latest + rust_version: 1.32.0 + - os: windows-latest + rust_version: 1.32.0 runs-on: ${{ matrix.os }} @@ -98,7 +98,7 @@ jobs: - name: Install node uses: actions/setup-node@v1 with: - node-version: '12' + node-version: "12" - name: Install wasm-pack run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96cda85889..72efc45ff8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: pull_request: branches: [main] paths: - - '**.rs' + - "**.rs" - .github/** - Cargo.toml @@ -19,31 +19,31 @@ jobs: exhaustive_tz: [onetz] check_combinatoric: [no_combinatoric] include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # compilation check - - os: ubuntu-latest - rust_version: stable - check_combinatoric: 'combinatoric' - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-20.04 - rust_version: 1.32.0 - - os: macos-latest - rust_version: 1.32.0 - - os: windows-latest - rust_version: 1.32.0 + # check all tzs on most-recent OS's + - os: ubuntu-latest + rust_version: stable + exhaustive_tz: all_tzs + - os: windows-latest + rust_version: stable + exhaustive_tz: all_tzs + - os: macos-latest + rust_version: stable + exhaustive_tz: all_tzs + # compilation check + - os: ubuntu-latest + rust_version: stable + check_combinatoric: "combinatoric" + # test other rust versions + - os: ubuntu-latest + rust_version: beta + - os: ubuntu-latest + rust_version: nightly + - os: ubuntu-20.04 + rust_version: 1.32.0 + - os: macos-latest + rust_version: 1.32.0 + - os: windows-latest + rust_version: 1.32.0 runs-on: ${{ matrix.os }} @@ -107,7 +107,7 @@ jobs: - name: Install node uses: actions/setup-node@v1 with: - node-version: '12' + node-version: "12" - name: Install wasm-pack run: | @@ -141,7 +141,7 @@ jobs: - name: Install node uses: actions/setup-node@v1 with: - node-version: '12' + node-version: "12" - name: Build and Test run: bash ci/github.sh @@ -203,5 +203,5 @@ jobs: override: true - name: Check docs build without warnings env: - RUSTDOCFLAGS: '-D warnings' + RUSTDOCFLAGS: "-D warnings" run: cargo +nightly doc From 1d33cbcb6ecbcd5ed1c91e1bbf2d3a60c36cab27 Mon Sep 17 00:00:00 2001 From: Yohan Boogaert Date: Wed, 8 Jun 2022 13:06:27 +0200 Subject: [PATCH 050/999] Add `elapsed_years` to `Date` and `DateTime` (#557) Co-authored-by: Cecile Tonglet Co-authored-by: Milo <50248166+Milo123459@users.noreply.github.com> --- AUTHORS.txt | 1 + CHANGELOG.md | 1 + src/date.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/datetime/mod.rs | 18 ++++++++++++++++++ src/datetime/tests.rs | 17 +++++++++++++++++ src/lib.rs | 14 ++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 9501a9d059..e4d7d2ad1d 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -39,3 +39,4 @@ Steve Klabnik Tom Gallacher klutzy kud1ing +Yohan Boogaert diff --git a/CHANGELOG.md b/CHANGELOG.md index 839bdeeda1..39174ef312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Versions with only mechanical changes will be omitted from the following list. * Fix `DurationRound` is not TZ aware (#495) * Implement `DurationRound` for `NaiveDateTime` * Add `DateTime::from_local()` to construct from given local date and time (#572) +* Add a function that calculates the number of years elapsed between now and a given `Date` or `DateTime` (#557) * Correct build for wasm32-unknown-emscripten target (#568) * Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) * Fix `duration_round` panic on rounding by `Duration::zero()` (#658) diff --git a/src/date.rs b/src/date.rs index 0758cc844f..d6d76d8997 100644 --- a/src/date.rs +++ b/src/date.rs @@ -275,6 +275,24 @@ impl Date { pub fn naive_local(&self) -> NaiveDate { self.date } + + /// Retrieves the elapsed years from now to the given [`Date`]. + #[cfg(feature = "clock")] + pub fn elapsed_years(&self) -> u32 { + let now = Utc::today().with_timezone(&self.timezone()); + + let years = if (now.month(), now.day()) < (self.month(), self.day()) { + now.year() - self.year() - 1 + } else { + now.year() - self.year() + }; + + if years.is_positive() { + years as u32 + } else { + 0 + } + } } /// Maps the local date to other date with given conversion function. @@ -498,3 +516,26 @@ where write!(f, "{}{}", self.naive_local(), self.offset) } } + +#[cfg(test)] +mod tests { + use crate::consts::f64; + use crate::offset::Utc; + use crate::oldtime::Duration; + + #[test] + #[cfg(feature = "clock")] + fn test_years_elapsed() { + // This is always at least one year because 1 year = 52.1775 weeks. + let one_year_ago = + Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 1.5).ceil() as i64); + // A bit more than 2 years. + let two_year_ago = + Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 2.5).ceil() as i64); + + assert_eq!(one_year_ago.elapsed_years(), 1); + assert_eq!(two_year_ago.elapsed_years(), 2); + // if the given Date is later than now, the function will always return 0. + assert_eq!((Utc::today() + Duration::weeks(12)).elapsed_years(), 0); + } +} diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ab924a92ab..e742e591b9 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -355,6 +355,24 @@ impl DateTime { pub fn naive_local(&self) -> NaiveDateTime { self.datetime + self.offset.fix() } + + /// Retrieve the elapsed years from now to the given [`DateTime`]. + #[cfg(feature = "clock")] + pub fn elapsed_years(&self) -> u32 { + let now = Utc::now().with_timezone(&self.timezone()); + + let years = + if (now.month(), now.day(), now.time()) < (self.month(), self.day(), self.time()) { + now.year() - self.year() - 1 + } else { + now.year() - self.year() + }; + if years.is_positive() { + years as u32 + } else { + 0 + } + } } impl Default for DateTime { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 8c373d3ba3..86036040d9 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,6 +1,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use super::DateTime; +#[cfg(feature = "clock")] +use crate::consts::f64; use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -407,3 +409,18 @@ fn test_datetime_from_local() { assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); } + +#[test] +#[cfg(feature = "clock")] +fn test_years_elapsed() { + // This is always at least one year because 1 year = 52.1775 weeks. + let one_year_ago = Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 1.5).ceil() as i64); + // A bit more than 2 years. + let two_year_ago = Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 2.5).ceil() as i64); + + assert_eq!(one_year_ago.elapsed_years(), 1); + assert_eq!(two_year_ago.elapsed_years(), 2); + + // If the given DateTime is later than now, the function will always return 0. + assert_eq!((Utc::today() + Duration::weeks(12)).elapsed_years(), 0); +} diff --git a/src/lib.rs b/src/lib.rs index d81cfe1e51..70ade573f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -508,6 +508,20 @@ pub use month::{Month, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; +/// Constants that can be used by all components. +pub mod consts { + /// Constants of type `f32`. + pub mod f32 { + /// Number of weeks in a year. + pub const WEEKS_PER_YEAR: f32 = 52.1775; + } + /// Constants of type `f64`. + pub mod f64 { + /// Number of weeks in a year. + pub const WEEKS_PER_YEAR: f64 = 52.1775; + } +} + #[cfg(feature = "__internal_bench")] #[doc(hidden)] pub use naive::__BenchYearFlags; From 752e69ae1ff600304250a5da117a3dd40f99581a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jun 2022 13:28:02 +0200 Subject: [PATCH 051/999] Morph elapsed_years() API into years_since() --- src/date.rs | 47 +++++++++++++++++++------------------------ src/datetime/mod.rs | 27 ++++++++++++------------- src/datetime/tests.rs | 15 +++++++------- src/lib.rs | 14 ------------- 4 files changed, 42 insertions(+), 61 deletions(-) diff --git a/src/date.rs b/src/date.rs index d6d76d8997..0c9b1eef4f 100644 --- a/src/date.rs +++ b/src/date.rs @@ -276,21 +276,16 @@ impl Date { self.date } - /// Retrieves the elapsed years from now to the given [`Date`]. - #[cfg(feature = "clock")] - pub fn elapsed_years(&self) -> u32 { - let now = Utc::today().with_timezone(&self.timezone()); - - let years = if (now.month(), now.day()) < (self.month(), self.day()) { - now.year() - self.year() - 1 - } else { - now.year() - self.year() - }; - - if years.is_positive() { - years as u32 - } else { - 0 + /// Returns the number of whole years from the given `base` until `self`. + pub fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + if (self.month(), self.day()) < (base.month(), base.day()) { + years -= 1; + } + + match years >= 0 { + true => Some(years as u32), + false => None, } } } @@ -519,23 +514,23 @@ where #[cfg(test)] mod tests { - use crate::consts::f64; - use crate::offset::Utc; - use crate::oldtime::Duration; + use crate::{Duration, Utc}; #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { + const WEEKS_PER_YEAR: f32 = 52.1775; + // This is always at least one year because 1 year = 52.1775 weeks. - let one_year_ago = - Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 1.5).ceil() as i64); + let one_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. - let two_year_ago = - Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 2.5).ceil() as i64); + let two_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + + assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); - assert_eq!(one_year_ago.elapsed_years(), 1); - assert_eq!(two_year_ago.elapsed_years(), 2); - // if the given Date is later than now, the function will always return 0. - assert_eq!((Utc::today() + Duration::weeks(12)).elapsed_years(), 0); + // If the given DateTime is later than now, the function will always return 0. + let future = Utc::today() + Duration::weeks(12); + assert_eq!(Utc::today().years_since(future), None); } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e742e591b9..cc902646e9 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -357,20 +357,19 @@ impl DateTime { } /// Retrieve the elapsed years from now to the given [`DateTime`]. - #[cfg(feature = "clock")] - pub fn elapsed_years(&self) -> u32 { - let now = Utc::now().with_timezone(&self.timezone()); - - let years = - if (now.month(), now.day(), now.time()) < (self.month(), self.day(), self.time()) { - now.year() - self.year() - 1 - } else { - now.year() - self.year() - }; - if years.is_positive() { - years as u32 - } else { - 0 + pub fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + let earlier_time = + (self.month(), self.day(), self.time()) < (base.month(), base.day(), base.time()); + + years -= match earlier_time { + true => 1, + false => 0, + }; + + match years >= 0 { + true => Some(years as u32), + false => None, } } } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 86036040d9..3b46f3f1bf 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,8 +1,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; use super::DateTime; -#[cfg(feature = "clock")] -use crate::consts::f64; use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -413,14 +411,17 @@ fn test_datetime_from_local() { #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { + const WEEKS_PER_YEAR: f32 = 52.1775; + // This is always at least one year because 1 year = 52.1775 weeks. - let one_year_ago = Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 1.5).ceil() as i64); + let one_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. - let two_year_ago = Utc::today() - Duration::weeks((f64::WEEKS_PER_YEAR * 2.5).ceil() as i64); + let two_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); - assert_eq!(one_year_ago.elapsed_years(), 1); - assert_eq!(two_year_ago.elapsed_years(), 2); + assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - assert_eq!((Utc::today() + Duration::weeks(12)).elapsed_years(), 0); + let future = Utc::today() + Duration::weeks(12); + assert_eq!(Utc::today().years_since(future), None); } diff --git a/src/lib.rs b/src/lib.rs index 70ade573f6..d81cfe1e51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -508,20 +508,6 @@ pub use month::{Month, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; -/// Constants that can be used by all components. -pub mod consts { - /// Constants of type `f32`. - pub mod f32 { - /// Number of weeks in a year. - pub const WEEKS_PER_YEAR: f32 = 52.1775; - } - /// Constants of type `f64`. - pub mod f64 { - /// Number of weeks in a year. - pub const WEEKS_PER_YEAR: f64 = 52.1775; - } -} - #[cfg(feature = "__internal_bench")] #[doc(hidden)] pub use naive::__BenchYearFlags; From 7c0bc31a632f73943204ba9c432c5b3e4a03b588 Mon Sep 17 00:00:00 2001 From: Jake Vossen Date: Thu, 9 Jun 2022 02:01:13 -0600 Subject: [PATCH 052/999] Implement std::iter::Sum for Duration (#522) --- AUTHORS.txt | 1 + CHANGELOG.md | 1 + src/oldtime.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index e4d7d2ad1d..caff57915c 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -22,6 +22,7 @@ Eunchong Yu Frans Skarman Huon Wilson Igor Gnatenko +Jake Vossen Jim Turner Jisoo Park Joe Wilm diff --git a/CHANGELOG.md b/CHANGELOG.md index 39174ef312..fe69ed4ab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add support for microseconds timestamps serde serialization/deserialization (#304) * Fix `DurationRound` is not TZ aware (#495) * Implement `DurationRound` for `NaiveDateTime` +* Implement `std::iter::Sum` for `Duration` * Add `DateTime::from_local()` to construct from given local date and time (#572) * Add a function that calculates the number of years elapsed between now and a given `Date` or `DateTime` (#557) * Correct build for wasm32-unknown-emscripten target (#568) diff --git a/src/oldtime.rs b/src/oldtime.rs index 57d199e2d8..eb0c5bcf20 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -377,6 +377,20 @@ impl Div for Duration { } } +#[cfg(any(feature = "std", test))] +impl<'a> std::iter::Sum<&'a Duration> for Duration { + fn sum>(iter: I) -> Duration { + iter.fold(Duration::zero(), |acc, x| acc + *x) + } +} + +#[cfg(any(feature = "std", test))] +impl std::iter::Sum for Duration { + fn sum>(iter: I) -> Duration { + iter.fold(Duration::zero(), |acc, x| acc + x) + } +} + impl fmt::Display for Duration { /// Format a duration using the [ISO 8601] format /// @@ -634,6 +648,27 @@ mod tests { assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); } + #[test] + fn test_duration_sum() { + let duration_list_1 = [Duration::zero(), Duration::seconds(1)]; + let sum_1: Duration = duration_list_1.iter().sum(); + assert_eq!(sum_1, Duration::seconds(1)); + + let duration_list_2 = + [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; + let sum_2: Duration = duration_list_2.iter().sum(); + assert_eq!(sum_2, Duration::seconds(17)); + + let duration_vec = vec![ + Duration::zero(), + Duration::seconds(1), + Duration::seconds(6), + Duration::seconds(10), + ]; + let sum_3: Duration = duration_vec.into_iter().sum(); + assert_eq!(sum_3, Duration::seconds(17)); + } + #[test] fn test_duration_fmt() { assert_eq!(Duration::zero().to_string(), "PT0S"); From cd275798959f4c38c5336f1928fec031f554b5da Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Apr 2022 16:19:51 +0200 Subject: [PATCH 053/999] sys: duplicate vaguely platform-specific code --- src/offset/local.rs | 165 +------------------------------------- src/offset/sys/stub.rs | 150 +++++++++++++++++++++++++++++++++- src/offset/sys/unix.rs | 159 ++++++++++++++++++++++++++++++++++-- src/offset/sys/windows.rs | 153 ++++++++++++++++++++++++++++++++++- 4 files changed, 455 insertions(+), 172 deletions(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index 07fdfc34d3..66442a9dbe 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -3,8 +3,6 @@ //! The local (system) time zone. -use std::time::{SystemTime, UNIX_EPOCH}; - #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -14,8 +12,6 @@ use super::{LocalResult, TimeZone}; use crate::naive::NaiveTime; use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{Date, DateTime}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use crate::{Datelike, Timelike}; #[cfg(all(not(unix), not(windows)))] #[path = "sys/stub.rs"] @@ -29,8 +25,6 @@ mod inner; #[path = "sys/windows.rs"] mod inner; -use inner::{local_tm_to_time, time_to_local_tm, utc_tm_to_time}; - /// The local timescale. This is implemented via the standard `time` crate. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods @@ -58,7 +52,7 @@ impl Local { /// Returns a `DateTime` which corresponds to the current date and time. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] pub fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) + inner::now() } /// Returns a `DateTime` which corresponds to the current date and time. @@ -117,7 +111,7 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - LocalResult::Single(naive_to_local(local, true)) + LocalResult::Single(inner::naive_to_local(local, true)) } fn from_utc_date(&self, utc: &NaiveDate) -> Date { @@ -134,161 +128,8 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - naive_to_local(utc, false) - } -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - let spec = Timespec { - sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), - }, - nsec: tm.tm_nsec, - }; - - // Adjust for leap seconds - let mut tm = spec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - tm_to_datetime(tm) -} - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn tm_to_datetime(mut tm: Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - #[cfg(not(windows))] - fn tm_to_naive_date(tm: &Tm) -> NaiveDate { - // from_yo is more efficient than from_ymd (since it's the internal representation). - NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) - } - - #[cfg(windows)] - fn tm_to_naive_date(tm: &Tm) -> NaiveDate { - // ...but tm_yday is broken in Windows (issue #85) - NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) - } - - let date = tm_to_naive_date(&tm); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - let offset = FixedOffset::east(tm.tm_utcoff); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -pub(super) struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - pub(super) fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } + inner::naive_to_local(utc, false) } - - /// Converts this timespec into the system's local time. - pub(super) fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[cfg(feature = "clock")] -#[repr(C)] -pub(crate) struct Tm { - /// Seconds after the minute - [0, 60] - pub(crate) tm_sec: i32, - - /// Minutes after the hour - [0, 59] - pub(crate) tm_min: i32, - - /// Hours after midnight - [0, 23] - pub(crate) tm_hour: i32, - - /// Day of the month - [1, 31] - pub(crate) tm_mday: i32, - - /// Months since January - [0, 11] - pub(crate) tm_mon: i32, - - /// Years since 1900 - pub(crate) tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - pub(crate) tm_wday: i32, - - /// Days since January 1 - [0, 365] - pub(crate) tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - pub(crate) tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - pub(crate) tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - pub(crate) tm_nsec: i32, } #[cfg(test)] diff --git a/src/offset/sys/stub.rs b/src/offset/sys/stub.rs index 616b52fe5e..9b8950f11e 100644 --- a/src/offset/sys/stub.rs +++ b/src/offset/sys/stub.rs @@ -8,7 +8,155 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::Tm; +use std::time::{SystemTime, UNIX_EPOCH}; + +use super::{FixedOffset, Local}; +use crate::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; + +pub(super) fn now() -> DateTime { + tm_to_datetime(Timespec::now().local()) +} + +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { + let tm = Tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, // yes, C is that strange... + tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + // This seems pretty fake? + tm_utcoff: if local { 1 } else { 0 }, + // do not set this, OS APIs are heavily inconsistent in terms of leap second handling + tm_nsec: 0, + }; + + let spec = Timespec { + sec: match local { + false => utc_tm_to_time(&tm), + true => local_tm_to_time(&tm), + }, + nsec: tm.tm_nsec, + }; + + // Adjust for leap seconds + let mut tm = spec.local(); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = d.nanosecond() as i32; + + tm_to_datetime(tm) +} + +/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. +/// This assumes that `time` is working correctly, i.e. any error is fatal. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +fn tm_to_datetime(mut tm: Tm) -> DateTime { + if tm.tm_sec >= 60 { + tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + tm.tm_sec = 59; + } + + let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1); + let time = NaiveTime::from_hms_nano( + tm.tm_hour as u32, + tm.tm_min as u32, + tm.tm_sec as u32, + tm.tm_nsec as u32, + ); + + let offset = FixedOffset::east(tm.tm_utcoff); + DateTime::from_utc(date.and_time(time) - offset, offset) +} + +/// A record specifying a time value in seconds and nanoseconds, where +/// nanoseconds represent the offset from the given second. +/// +/// For example a timespec of 1.2 seconds after the beginning of the epoch would +/// be represented as {sec: 1, nsec: 200000000}. +struct Timespec { + sec: i64, + nsec: i32, +} + +impl Timespec { + /// Constructs a timespec representing the current time in UTC. + fn now() -> Timespec { + let st = + SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); + Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } + } + + /// Converts this timespec into the system's local time. + fn local(self) -> Tm { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + time_to_local_tm(self.sec, &mut tm); + tm.tm_nsec = self.nsec; + tm + } +} + +/// Holds a calendar date and time broken down into its components (year, month, +/// day, and so on), also called a broken-down time value. +// FIXME: use c_int instead of i32? +#[repr(C)] +struct Tm { + /// Seconds after the minute - [0, 60] + tm_sec: i32, + + /// Minutes after the hour - [0, 59] + tm_min: i32, + + /// Hours after midnight - [0, 23] + tm_hour: i32, + + /// Day of the month - [1, 31] + tm_mday: i32, + + /// Months since January - [0, 11] + tm_mon: i32, + + /// Years since 1900 + tm_year: i32, + + /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + tm_wday: i32, + + /// Days since January 1 - [0, 365] + tm_yday: i32, + + /// Daylight Saving Time flag. + /// + /// This value is positive if Daylight Saving Time is in effect, zero if + /// Daylight Saving Time is not in effect, and negative if this information + /// is not available. + tm_isdst: i32, + + /// Identifies the time zone that was used to compute this broken-down time + /// value, including any adjustment for Daylight Saving Time. This is the + /// number of seconds east of UTC. For example, for U.S. Pacific Daylight + /// Time, the value is `-7*60*60 = -25200`. + tm_utcoff: i32, + + /// Nanoseconds after the second - [0, 109 - 1] + tm_nsec: i32, +} fn time_to_tm(ts: i64, tm: &mut Tm) { let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }; diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 3d2c063aa2..e440e4eaa9 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -8,10 +8,159 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::Tm; -use libc::{self, time_t}; use std::io; use std::mem; +use std::time::{SystemTime, UNIX_EPOCH}; + +use libc::{self, time_t}; + +use super::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime}; +use crate::{Datelike, Timelike}; + +pub(super) fn now() -> DateTime { + tm_to_datetime(Timespec::now().local()) +} + +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { + let tm = Tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, // yes, C is that strange... + tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + // This seems pretty fake? + tm_utcoff: if local { 1 } else { 0 }, + // do not set this, OS APIs are heavily inconsistent in terms of leap second handling + tm_nsec: 0, + }; + + let spec = Timespec { + sec: match local { + false => utc_tm_to_time(&tm), + true => local_tm_to_time(&tm), + }, + nsec: tm.tm_nsec, + }; + + // Adjust for leap seconds + let mut tm = spec.local(); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = d.nanosecond() as i32; + + tm_to_datetime(tm) +} + +/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. +/// This assumes that `time` is working correctly, i.e. any error is fatal. +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +fn tm_to_datetime(mut tm: Tm) -> DateTime { + if tm.tm_sec >= 60 { + tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + tm.tm_sec = 59; + } + + let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1); + let time = NaiveTime::from_hms_nano( + tm.tm_hour as u32, + tm.tm_min as u32, + tm.tm_sec as u32, + tm.tm_nsec as u32, + ); + + let offset = FixedOffset::east(tm.tm_utcoff); + DateTime::from_utc(date.and_time(time) - offset, offset) +} + +/// A record specifying a time value in seconds and nanoseconds, where +/// nanoseconds represent the offset from the given second. +/// +/// For example a timespec of 1.2 seconds after the beginning of the epoch would +/// be represented as {sec: 1, nsec: 200000000}. +struct Timespec { + sec: i64, + nsec: i32, +} + +impl Timespec { + /// Constructs a timespec representing the current time in UTC. + fn now() -> Timespec { + let st = + SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); + Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } + } + + /// Converts this timespec into the system's local time. + fn local(self) -> Tm { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + time_to_local_tm(self.sec, &mut tm); + tm.tm_nsec = self.nsec; + tm + } +} + +/// Holds a calendar date and time broken down into its components (year, month, +/// day, and so on), also called a broken-down time value. +// FIXME: use c_int instead of i32? +#[repr(C)] +struct Tm { + /// Seconds after the minute - [0, 60] + tm_sec: i32, + + /// Minutes after the hour - [0, 59] + tm_min: i32, + + /// Hours after midnight - [0, 23] + tm_hour: i32, + + /// Day of the month - [1, 31] + tm_mday: i32, + + /// Months since January - [0, 11] + tm_mon: i32, + + /// Years since 1900 + tm_year: i32, + + /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + tm_wday: i32, + + /// Days since January 1 - [0, 365] + tm_yday: i32, + + /// Daylight Saving Time flag. + /// + /// This value is positive if Daylight Saving Time is in effect, zero if + /// Daylight Saving Time is not in effect, and negative if this information + /// is not available. + tm_isdst: i32, + + /// Identifies the time zone that was used to compute this broken-down time + /// value, including any adjustment for Daylight Saving Time. This is the + /// number of seconds east of UTC. For example, for U.S. Pacific Daylight + /// Time, the value is `-7*60*60 = -25200`. + tm_utcoff: i32, + + /// Nanoseconds after the second - [0, 109 - 1] + tm_nsec: i32, +} #[cfg(any(target_os = "solaris", target_os = "illumos"))] extern "C" { @@ -77,7 +226,7 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { ret } -pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { +fn time_to_local_tm(sec: i64, tm: &mut Tm) { unsafe { let sec = sec as time_t; let mut out = mem::zeroed(); @@ -103,7 +252,7 @@ pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { } } -pub(super) fn utc_tm_to_time(rust_tm: &Tm) -> i64 { +fn utc_tm_to_time(rust_tm: &Tm) -> i64 { #[cfg(not(any( all(target_os = "android", target_pointer_width = "32"), target_os = "nacl", @@ -119,7 +268,7 @@ pub(super) fn utc_tm_to_time(rust_tm: &Tm) -> i64 { unsafe { timegm(&mut tm) as i64 } } -pub(super) fn local_tm_to_time(rust_tm: &Tm) -> i64 { +fn local_tm_to_time(rust_tm: &Tm) -> i64 { let mut tm = unsafe { mem::zeroed() }; rust_tm_to_tm(rust_tm, &mut tm); unsafe { libc::mktime(&mut tm) as i64 } diff --git a/src/offset/sys/windows.rs b/src/offset/sys/windows.rs index d608d209a1..29e75cf048 100644 --- a/src/offset/sys/windows.rs +++ b/src/offset/sys/windows.rs @@ -8,14 +8,159 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::Tm; use std::io; use std::mem; +use std::time::{SystemTime, UNIX_EPOCH}; use winapi::shared::minwindef::*; use winapi::um::minwinbase::SYSTEMTIME; use winapi::um::timezoneapi::*; +use super::{FixedOffset, Local}; +use crate::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; + +pub(super) fn now() -> DateTime { + tm_to_datetime(Timespec::now().local()) +} + +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { + let tm = Tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, // yes, C is that strange... + tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + // This seems pretty fake? + tm_utcoff: if local { 1 } else { 0 }, + // do not set this, OS APIs are heavily inconsistent in terms of leap second handling + tm_nsec: 0, + }; + + let spec = Timespec { + sec: match local { + false => utc_tm_to_time(&tm), + true => local_tm_to_time(&tm), + }, + nsec: tm.tm_nsec, + }; + + // Adjust for leap seconds + let mut tm = spec.local(); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = d.nanosecond() as i32; + + tm_to_datetime(tm) +} + +/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. +fn tm_to_datetime(mut tm: Tm) -> DateTime { + if tm.tm_sec >= 60 { + tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + tm.tm_sec = 59; + } + + let date = NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32); + let time = NaiveTime::from_hms_nano( + tm.tm_hour as u32, + tm.tm_min as u32, + tm.tm_sec as u32, + tm.tm_nsec as u32, + ); + + let offset = FixedOffset::east(tm.tm_utcoff); + DateTime::from_utc(date.and_time(time) - offset, offset) +} + +/// A record specifying a time value in seconds and nanoseconds, where +/// nanoseconds represent the offset from the given second. +/// +/// For example a timespec of 1.2 seconds after the beginning of the epoch would +/// be represented as {sec: 1, nsec: 200000000}. +struct Timespec { + sec: i64, + nsec: i32, +} + +impl Timespec { + /// Constructs a timespec representing the current time in UTC. + fn now() -> Timespec { + let st = + SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); + Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } + } + + /// Converts this timespec into the system's local time. + fn local(self) -> Tm { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + time_to_local_tm(self.sec, &mut tm); + tm.tm_nsec = self.nsec; + tm + } +} + +/// Holds a calendar date and time broken down into its components (year, month, +/// day, and so on), also called a broken-down time value. +// FIXME: use c_int instead of i32? +#[repr(C)] +struct Tm { + /// Seconds after the minute - [0, 60] + tm_sec: i32, + + /// Minutes after the hour - [0, 59] + tm_min: i32, + + /// Hours after midnight - [0, 23] + tm_hour: i32, + + /// Day of the month - [1, 31] + tm_mday: i32, + + /// Months since January - [0, 11] + tm_mon: i32, + + /// Years since 1900 + tm_year: i32, + + /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + tm_wday: i32, + + /// Days since January 1 - [0, 365] + tm_yday: i32, + + /// Daylight Saving Time flag. + /// + /// This value is positive if Daylight Saving Time is in effect, zero if + /// Daylight Saving Time is not in effect, and negative if this information + /// is not available. + tm_isdst: i32, + + /// Identifies the time zone that was used to compute this broken-down time + /// value, including any adjustment for Daylight Saving Time. This is the + /// number of seconds east of UTC. For example, for U.S. Pacific Daylight + /// Time, the value is `-7*60*60 = -25200`. + tm_utcoff: i32, + + /// Nanoseconds after the second - [0, 109 - 1] + tm_nsec: i32, +} + const HECTONANOSECS_IN_SEC: i64 = 10_000_000; const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; @@ -88,7 +233,7 @@ macro_rules! call { } } -pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { +fn time_to_local_tm(sec: i64, tm: &mut Tm) { let ft = time_to_file_time(sec); unsafe { let mut utc = mem::zeroed(); @@ -110,7 +255,7 @@ pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { } } -pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { +fn utc_tm_to_time(tm: &Tm) -> i64 { unsafe { let mut ft = mem::zeroed(); let sys_time = tm_to_system_time(tm); @@ -119,7 +264,7 @@ pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { } } -pub(super) fn local_tm_to_time(tm: &Tm) -> i64 { +fn local_tm_to_time(tm: &Tm) -> i64 { unsafe { let mut ft = mem::zeroed(); let mut utc = mem::zeroed(); From 945d08e6a7c88b86cc62bdf279d1633d6baf37fe Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 10:55:48 +0200 Subject: [PATCH 054/999] sys/unix: skip conversion to intermediate type --- src/offset/sys/unix.rs | 65 +++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index e440e4eaa9..1b4d6bbd4b 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -8,9 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::io; -use std::mem; use std::time::{SystemTime, UNIX_EPOCH}; +use std::{io, mem, ptr}; use libc::{self, time_t}; @@ -24,28 +23,12 @@ pub(super) fn now() -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - let spec = Timespec { sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), + false => utc_naive_to_unix(d), + true => local_naive_to_unix(d), }, - nsec: tm.tm_nsec, + nsec: 0, }; // Adjust for leap seconds @@ -176,18 +159,6 @@ fn tzset() { unsafe { tzset() } } -fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) { - tm.tm_sec = rust_tm.tm_sec; - tm.tm_min = rust_tm.tm_min; - tm.tm_hour = rust_tm.tm_hour; - tm.tm_mday = rust_tm.tm_mday; - tm.tm_mon = rust_tm.tm_mon; - tm.tm_year = rust_tm.tm_year; - tm.tm_wday = rust_tm.tm_wday; - tm.tm_yday = rust_tm.tm_yday; - tm.tm_isdst = rust_tm.tm_isdst; -} - fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) { rust_tm.tm_sec = tm.tm_sec; rust_tm.tm_min = tm.tm_min; @@ -252,7 +223,7 @@ fn time_to_local_tm(sec: i64, tm: &mut Tm) { } } -fn utc_tm_to_time(rust_tm: &Tm) -> i64 { +fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { #[cfg(not(any( all(target_os = "android", target_pointer_width = "32"), target_os = "nacl", @@ -263,13 +234,29 @@ fn utc_tm_to_time(rust_tm: &Tm) -> i64 { #[cfg(all(target_os = "android", target_pointer_width = "32"))] use libc::timegm64 as timegm; - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); + let mut tm = naive_to_tm(d); unsafe { timegm(&mut tm) as i64 } } -fn local_tm_to_time(rust_tm: &Tm) -> i64 { - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); +fn local_naive_to_unix(d: &NaiveDateTime) -> i64 { + let mut tm = naive_to_tm(d); unsafe { libc::mktime(&mut tm) as i64 } } + +fn naive_to_tm(d: &NaiveDateTime) -> libc::tm { + libc::tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, + tm_year: d.year() - 1900, + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_gmtoff: 0, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_zone: ptr::null_mut(), + } +} From e0beafd94fc1cbc194988344e23d4b26c04e3557 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:00:29 +0200 Subject: [PATCH 055/999] sys/unix: remove pointless platform guards --- src/offset/sys/unix.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 1b4d6bbd4b..eeb09775ca 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -21,7 +21,6 @@ pub(super) fn now() -> DateTime { } /// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { let spec = Timespec { sec: match local { @@ -41,7 +40,6 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn tm_to_datetime(mut tm: Tm) -> DateTime { if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; From 41cfd49d77535a2e5fde1467137466ea1d39db37 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:08:04 +0200 Subject: [PATCH 056/999] sys/unix: clarify type of time_to_local_tm() --- src/offset/sys/unix.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index eeb09775ca..d408b2ebb1 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -78,20 +78,7 @@ impl Timespec { /// Converts this timespec into the system's local time. fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); + let mut tm = time_to_local_tm(self.sec); tm.tm_nsec = self.nsec; tm } @@ -195,7 +182,21 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { ret } -fn time_to_local_tm(sec: i64, tm: &mut Tm) { +fn time_to_local_tm(sec: i64) -> Tm { + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + unsafe { let sec = sec as time_t; let mut out = mem::zeroed(); @@ -217,8 +218,10 @@ fn time_to_local_tm(sec: i64, tm: &mut Tm) { }; #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] let gmtoff = out.tm_gmtoff; - tm_to_rust_tm(&out, gmtoff as i32, tm); + tm_to_rust_tm(&out, gmtoff as i32, &mut tm); } + + tm } fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { From d707394bd23124102f52191db4070df2231b09d1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:13:17 +0200 Subject: [PATCH 057/999] sys/unix: inline uses of Timespec type and methods --- src/offset/sys/unix.rs | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index d408b2ebb1..816b336c8b 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -17,21 +17,21 @@ use super::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime}; use crate::{Datelike, Timelike}; pub(super) fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) + let st = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); + let mut tm = time_to_local_tm(st.as_secs() as i64); + tm.tm_nsec = st.subsec_nanos() as i32; + tm_to_datetime(tm) } /// Converts a local `NaiveDateTime` to the `time::Timespec`. pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let spec = Timespec { - sec: match local { - false => utc_naive_to_unix(d), - true => local_naive_to_unix(d), - }, - nsec: 0, + let unix = match local { + false => utc_naive_to_unix(d), + true => local_naive_to_unix(d), }; // Adjust for leap seconds - let mut tm = spec.local(); + let mut tm = time_to_local_tm(unix); assert_eq!(tm.tm_nsec, 0); tm.tm_nsec = d.nanosecond() as i32; @@ -58,32 +58,6 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { DateTime::from_utc(date.and_time(time) - offset, offset) } -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - - /// Converts this timespec into the system's local time. - fn local(self) -> Tm { - let mut tm = time_to_local_tm(self.sec); - tm.tm_nsec = self.nsec; - tm - } -} - /// Holds a calendar date and time broken down into its components (year, month, /// day, and so on), also called a broken-down time value. // FIXME: use c_int instead of i32? From e35c0b7b0aaeb51fb58cf1fc8a0e4ad3d7463802 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:16:55 +0200 Subject: [PATCH 058/999] sys/unix: move duplicate code into renamed localize() function --- src/offset/sys/unix.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 816b336c8b..57b6b413d8 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -18,9 +18,7 @@ use crate::{Datelike, Timelike}; pub(super) fn now() -> DateTime { let st = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - let mut tm = time_to_local_tm(st.as_secs() as i64); - tm.tm_nsec = st.subsec_nanos() as i32; - tm_to_datetime(tm) + localize(st.as_secs() as i64, st.subsec_nanos() as i32) } /// Converts a local `NaiveDateTime` to the `time::Timespec`. @@ -30,17 +28,15 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime true => local_naive_to_unix(d), }; - // Adjust for leap seconds - let mut tm = time_to_local_tm(unix); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - tm_to_datetime(tm) + localize(unix, d.nanosecond() as i32) } /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. -fn tm_to_datetime(mut tm: Tm) -> DateTime { +fn localize(unix: i64, nanos: i32) -> DateTime { + let mut tm = time_to_local_tm(unix); + tm.tm_nsec = nanos; + if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; tm.tm_sec = 59; From 83d479bf101b14f2061d3f1e825fd967079c5957 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:20:45 +0200 Subject: [PATCH 059/999] sys/unix: inline more single-use functions --- src/offset/sys/unix.rs | 104 +++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 57 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 57b6b413d8..e138ee581d 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -34,9 +34,54 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. fn localize(unix: i64, nanos: i32) -> DateTime { - let mut tm = time_to_local_tm(unix); - tm.tm_nsec = nanos; + let mut tm = Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_utcoff: 0, + tm_nsec: 0, + }; + + unsafe { + let mut out = mem::zeroed(); + if libc::localtime_r(&(unix as time_t), &mut out).is_null() { + panic!("localtime_r failed: {}", io::Error::last_os_error()); + } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + let gmtoff = { + tzset(); + // < 0 means we don't know; assume we're not in DST. + if out.tm_isdst == 0 { + // timezone is seconds west of UTC, tm_gmtoff is seconds east + -timezone + } else if out.tm_isdst > 0 { + -altzone + } else { + -timezone + } + }; + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + let gmtoff = out.tm_gmtoff; + tm.tm_sec = out.tm_sec; + tm.tm_min = out.tm_min; + tm.tm_hour = out.tm_hour; + tm.tm_mday = out.tm_mday; + tm.tm_mon = out.tm_mon; + tm.tm_year = out.tm_year; + tm.tm_wday = out.tm_wday; + tm.tm_yday = out.tm_yday; + tm.tm_isdst = out.tm_isdst; + tm.tm_utcoff = gmtoff as i32; + } + + tm.tm_nsec = nanos; if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; tm.tm_sec = 59; @@ -114,19 +159,6 @@ fn tzset() { unsafe { tzset() } } -fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) { - rust_tm.tm_sec = tm.tm_sec; - rust_tm.tm_min = tm.tm_min; - rust_tm.tm_hour = tm.tm_hour; - rust_tm.tm_mday = tm.tm_mday; - rust_tm.tm_mon = tm.tm_mon; - rust_tm.tm_year = tm.tm_year; - rust_tm.tm_wday = tm.tm_wday; - rust_tm.tm_yday = tm.tm_yday; - rust_tm.tm_isdst = tm.tm_isdst; - rust_tm.tm_utcoff = utcoff; -} - #[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] unsafe fn timegm(tm: *mut libc::tm) -> time_t { use std::env::{remove_var, set_var, var_os}; @@ -152,48 +184,6 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { ret } -fn time_to_local_tm(sec: i64) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - - unsafe { - let sec = sec as time_t; - let mut out = mem::zeroed(); - if libc::localtime_r(&sec, &mut out).is_null() { - panic!("localtime_r failed: {}", io::Error::last_os_error()); - } - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - let gmtoff = { - tzset(); - // < 0 means we don't know; assume we're not in DST. - if out.tm_isdst == 0 { - // timezone is seconds west of UTC, tm_gmtoff is seconds east - -timezone - } else if out.tm_isdst > 0 { - -altzone - } else { - -timezone - } - }; - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - let gmtoff = out.tm_gmtoff; - tm_to_rust_tm(&out, gmtoff as i32, &mut tm); - } - - tm -} - fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { #[cfg(not(any( all(target_os = "android", target_pointer_width = "32"), From f78c833f0252c1cac461caba094c1f59ca076ed4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:31:03 +0200 Subject: [PATCH 060/999] sys/unix: skip conversion to intermediate type --- src/offset/sys/unix.rs | 110 ++++++++++------------------------------- 1 file changed, 27 insertions(+), 83 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index e138ee581d..9cb6f13c38 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -9,7 +9,7 @@ // except according to those terms. use std::time::{SystemTime, UNIX_EPOCH}; -use std::{io, mem, ptr}; +use std::{io, ptr}; use libc::{self, time_t}; @@ -33,8 +33,8 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. /// This assumes that `time` is working correctly, i.e. any error is fatal. -fn localize(unix: i64, nanos: i32) -> DateTime { - let mut tm = Tm { +fn localize(unix: i64, mut nanos: i32) -> DateTime { + let mut tm = libc::tm { tm_sec: 0, tm_min: 0, tm_hour: 0, @@ -44,46 +44,36 @@ fn localize(unix: i64, nanos: i32) -> DateTime { tm_wday: 0, tm_yday: 0, tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_gmtoff: 0, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_zone: ptr::null_mut(), }; unsafe { - let mut out = mem::zeroed(); - if libc::localtime_r(&(unix as time_t), &mut out).is_null() { + if libc::localtime_r(&(unix as time_t), &mut tm).is_null() { panic!("localtime_r failed: {}", io::Error::last_os_error()); } - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - let gmtoff = { - tzset(); - // < 0 means we don't know; assume we're not in DST. - if out.tm_isdst == 0 { - // timezone is seconds west of UTC, tm_gmtoff is seconds east - -timezone - } else if out.tm_isdst > 0 { - -altzone - } else { - -timezone - } - }; - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - let gmtoff = out.tm_gmtoff; - - tm.tm_sec = out.tm_sec; - tm.tm_min = out.tm_min; - tm.tm_hour = out.tm_hour; - tm.tm_mday = out.tm_mday; - tm.tm_mon = out.tm_mon; - tm.tm_year = out.tm_year; - tm.tm_wday = out.tm_wday; - tm.tm_yday = out.tm_yday; - tm.tm_isdst = out.tm_isdst; - tm.tm_utcoff = gmtoff as i32; } - tm.tm_nsec = nanos; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + let offset = unsafe { + tzset(); + // < 0 means we don't know; assume we're not in DST. + if tm.tm_isdst == 0 { + // timezone is seconds west of UTC, tm_gmtoff is seconds east + -timezone + } else if tm.tm_isdst > 0 { + -altzone + } else { + -timezone + } + }; + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + let offset = tm.tm_gmtoff; + if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + nanos += (tm.tm_sec - 59) * 1_000_000_000; tm.tm_sec = 59; } @@ -92,59 +82,13 @@ fn localize(unix: i64, nanos: i32) -> DateTime { tm.tm_hour as u32, tm.tm_min as u32, tm.tm_sec as u32, - tm.tm_nsec as u32, + nanos as u32, ); - let offset = FixedOffset::east(tm.tm_utcoff); + let offset = FixedOffset::east(offset as i32); DateTime::from_utc(date.and_time(time) - offset, offset) } -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[repr(C)] -struct Tm { - /// Seconds after the minute - [0, 60] - tm_sec: i32, - - /// Minutes after the hour - [0, 59] - tm_min: i32, - - /// Hours after midnight - [0, 23] - tm_hour: i32, - - /// Day of the month - [1, 31] - tm_mday: i32, - - /// Months since January - [0, 11] - tm_mon: i32, - - /// Years since 1900 - tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - tm_wday: i32, - - /// Days since January 1 - [0, 365] - tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - tm_nsec: i32, -} - #[cfg(any(target_os = "solaris", target_os = "illumos"))] extern "C" { static timezone: time_t; From 8f5c77728abb02a4c497d98a618797902f31663c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:33:44 +0200 Subject: [PATCH 061/999] sys/unix: extract imports to global level --- src/offset/sys/unix.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 9cb6f13c38..612f7d4e92 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -11,6 +11,15 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::{io, ptr}; +#[cfg(not(any( + all(target_os = "android", target_pointer_width = "32"), + target_os = "nacl", + target_os = "solaris", + target_os = "illumos" +)))] +use libc::timegm; +#[cfg(all(target_os = "android", target_pointer_width = "32"))] +use libc::timegm64 as timegm; use libc::{self, time_t}; use super::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime}; @@ -129,16 +138,6 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { } fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { - #[cfg(not(any( - all(target_os = "android", target_pointer_width = "32"), - target_os = "nacl", - target_os = "solaris", - target_os = "illumos" - )))] - use libc::timegm; - #[cfg(all(target_os = "android", target_pointer_width = "32"))] - use libc::timegm64 as timegm; - let mut tm = naive_to_tm(d); unsafe { timegm(&mut tm) as i64 } } From 6411fcd4563da6b5cef088fce912d4a1b008d105 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:34:29 +0200 Subject: [PATCH 062/999] sys/unix: reorder functions --- src/offset/sys/unix.rs | 66 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 612f7d4e92..869d0f81cd 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -98,18 +98,32 @@ fn localize(unix: i64, mut nanos: i32) -> DateTime { DateTime::from_utc(date.and_time(time) - offset, offset) } -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -extern "C" { - static timezone: time_t; - static altzone: time_t; +fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { + let mut tm = naive_to_tm(d); + unsafe { timegm(&mut tm) as i64 } } -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -fn tzset() { - extern "C" { - fn tzset(); +fn local_naive_to_unix(d: &NaiveDateTime) -> i64 { + let mut tm = naive_to_tm(d); + unsafe { libc::mktime(&mut tm) as i64 } +} + +fn naive_to_tm(d: &NaiveDateTime) -> libc::tm { + libc::tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, + tm_year: d.year() - 1900, + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_gmtoff: 0, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_zone: ptr::null_mut(), } - unsafe { tzset() } } #[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] @@ -137,30 +151,16 @@ unsafe fn timegm(tm: *mut libc::tm) -> time_t { ret } -fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { - let mut tm = naive_to_tm(d); - unsafe { timegm(&mut tm) as i64 } -} - -fn local_naive_to_unix(d: &NaiveDateTime) -> i64 { - let mut tm = naive_to_tm(d); - unsafe { libc::mktime(&mut tm) as i64 } +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +fn tzset() { + extern "C" { + fn tzset(); + } + unsafe { tzset() } } -fn naive_to_tm(d: &NaiveDateTime) -> libc::tm { - libc::tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, - tm_year: d.year() - 1900, - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_gmtoff: 0, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_zone: ptr::null_mut(), - } +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +extern "C" { + static timezone: time_t; + static altzone: time_t; } From c76d02a72cbe7fa7469e4f3a3f82b52e20dc5cd7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 11:39:31 +0200 Subject: [PATCH 063/999] sys/unix: inline some more trivial functions --- src/offset/sys/unix.rs | 50 ++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index 869d0f81cd..a9daa6c33b 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -32,12 +32,28 @@ pub(super) fn now() -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { + let mut tm = libc::tm { + tm_sec: d.second() as i32, + tm_min: d.minute() as i32, + tm_hour: d.hour() as i32, + tm_mday: d.day() as i32, + tm_mon: d.month0() as i32, + tm_year: d.year() - 1900, + tm_wday: 0, // to_local ignores this + tm_yday: 0, // and this + tm_isdst: -1, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_gmtoff: 0, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + tm_zone: ptr::null_mut(), + }; + let unix = match local { - false => utc_naive_to_unix(d), - true => local_naive_to_unix(d), + false => unsafe { timegm(&mut tm) }, + true => unsafe { libc::mktime(&mut tm) }, }; - localize(unix, d.nanosecond() as i32) + localize(unix as i64, d.nanosecond() as i32) } /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. @@ -98,34 +114,6 @@ fn localize(unix: i64, mut nanos: i32) -> DateTime { DateTime::from_utc(date.and_time(time) - offset, offset) } -fn utc_naive_to_unix(d: &NaiveDateTime) -> i64 { - let mut tm = naive_to_tm(d); - unsafe { timegm(&mut tm) as i64 } -} - -fn local_naive_to_unix(d: &NaiveDateTime) -> i64 { - let mut tm = naive_to_tm(d); - unsafe { libc::mktime(&mut tm) as i64 } -} - -fn naive_to_tm(d: &NaiveDateTime) -> libc::tm { - libc::tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, - tm_year: d.year() - 1900, - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_gmtoff: 0, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_zone: ptr::null_mut(), - } -} - #[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] unsafe fn timegm(tm: *mut libc::tm) -> time_t { use std::env::{remove_var, set_var, var_os}; From 4646ec2c1c835ac907fe0c0f97df80014a99b435 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Apr 2022 14:09:53 +0200 Subject: [PATCH 064/999] sys/unix: parse tzinfo files to find local time offset Based on tz-rs commit b8675e2b5794d923f80970daf9ea548cfb998db2, with substantial modifications from me. --- src/lib.rs | 12 + src/offset/local.rs | 2 - src/offset/mod.rs | 3 + src/offset/sys/unix.rs | 151 +----- src/offset/tz_info/mod.rs | 131 +++++ src/offset/tz_info/parser.rs | 334 ++++++++++++ src/offset/tz_info/rule.rs | 911 +++++++++++++++++++++++++++++++++ src/offset/tz_info/timezone.rs | 806 +++++++++++++++++++++++++++++ 8 files changed, 2214 insertions(+), 136 deletions(-) create mode 100644 src/offset/tz_info/mod.rs create mode 100644 src/offset/tz_info/parser.rs create mode 100644 src/offset/tz_info/rule.rs create mode 100644 src/offset/tz_info/timezone.rs diff --git a/src/lib.rs b/src/lib.rs index d81cfe1e51..bf193b8a12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -524,3 +524,15 @@ pub use naive::__BenchYearFlags; pub mod serde { pub use super::datetime::serde::*; } + +/// MSRV 1.42 +#[cfg(test)] +#[macro_export] +macro_rules! matches { + ($expression:expr, $(|)? $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => true, + _ => false + } + } +} diff --git a/src/offset/local.rs b/src/offset/local.rs index 66442a9dbe..af5d5cc37e 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -8,8 +8,6 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use crate::naive::NaiveTime; use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{Date, DateTime}; diff --git a/src/offset/mod.rs b/src/offset/mod.rs index eb89680ae9..8818676f5a 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -33,6 +33,9 @@ mod local; #[cfg(feature = "clock")] pub use self::local::Local; +#[cfg(all(unix, feature = "clock"))] +mod tz_info; + mod utc; pub use self::utc::Utc; diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index a9daa6c33b..e83f9da1fe 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -8,147 +8,30 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{io, ptr}; - -#[cfg(not(any( - all(target_os = "android", target_pointer_width = "32"), - target_os = "nacl", - target_os = "solaris", - target_os = "illumos" -)))] -use libc::timegm; -#[cfg(all(target_os = "android", target_pointer_width = "32"))] -use libc::timegm64 as timegm; -use libc::{self, time_t}; - -use super::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime}; -use crate::{Datelike, Timelike}; +use super::{DateTime, FixedOffset, Local, NaiveDateTime}; +use crate::offset::tz_info::TimeZone; +use crate::Utc; pub(super) fn now() -> DateTime { - let st = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - localize(st.as_secs() as i64, st.subsec_nanos() as i32) + let now = Utc::now(); + DateTime::from_utc(now.naive_utc(), offset(now.timestamp())) } -/// Converts a local `NaiveDateTime` to the `time::Timespec`. pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let mut tm = libc::tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, - tm_year: d.year() - 1900, - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_gmtoff: 0, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_zone: ptr::null_mut(), - }; - - let unix = match local { - false => unsafe { timegm(&mut tm) }, - true => unsafe { libc::mktime(&mut tm) }, - }; - - localize(unix as i64, d.nanosecond() as i32) -} - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -fn localize(unix: i64, mut nanos: i32) -> DateTime { - let mut tm = libc::tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_gmtoff: 0, - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - tm_zone: ptr::null_mut(), - }; - - unsafe { - if libc::localtime_r(&(unix as time_t), &mut tm).is_null() { - panic!("localtime_r failed: {}", io::Error::last_os_error()); - } - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - let offset = unsafe { - tzset(); - // < 0 means we don't know; assume we're not in DST. - if tm.tm_isdst == 0 { - // timezone is seconds west of UTC, tm_gmtoff is seconds east - -timezone - } else if tm.tm_isdst > 0 { - -altzone - } else { - -timezone - } + let offset = match local { + true => offset(d.timestamp()), + false => FixedOffset::east(0), }; - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - let offset = tm.tm_gmtoff; - - if tm.tm_sec >= 60 { - nanos += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - nanos as u32, - ); - - let offset = FixedOffset::east(offset as i32); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -#[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] -unsafe fn timegm(tm: *mut libc::tm) -> time_t { - use std::env::{remove_var, set_var, var_os}; - extern "C" { - fn tzset(); - } - - let ret; - - let current_tz = var_os("TZ"); - set_var("TZ", "UTC"); - tzset(); - - ret = libc::mktime(tm); - - if let Some(tz) = current_tz { - set_var("TZ", tz); - } else { - remove_var("TZ"); - } - tzset(); - - ret -} -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -fn tzset() { - extern "C" { - fn tzset(); - } - unsafe { tzset() } + DateTime::from_utc(*d - offset, offset) } -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -extern "C" { - static timezone: time_t; - static altzone: time_t; +fn offset(unix: i64) -> FixedOffset { + FixedOffset::east( + TimeZone::local() + .expect("unable to parse localtime info") + .find_local_time_type(unix) + .expect("unable to select local time type") + .offset(), + ) } diff --git a/src/offset/tz_info/mod.rs b/src/offset/tz_info/mod.rs new file mode 100644 index 0000000000..bd2693b6bb --- /dev/null +++ b/src/offset/tz_info/mod.rs @@ -0,0 +1,131 @@ +#![deny(missing_docs)] +#![allow(dead_code)] +#![warn(unreachable_pub)] + +use std::num::ParseIntError; +use std::str::Utf8Error; +use std::time::SystemTimeError; +use std::{error, fmt, io}; + +mod timezone; +pub(crate) use timezone::TimeZone; + +mod parser; +mod rule; + +/// Unified error type for everything in the crate +#[derive(Debug)] +pub(crate) enum Error { + /// Date time error + DateTime(&'static str), + /// Local time type search error + FindLocalTimeType(&'static str), + /// Local time type error + LocalTimeType(&'static str), + /// Invalid slice for integer conversion + InvalidSlice(&'static str), + /// Invalid Tzif file + InvalidTzFile(&'static str), + /// Invalid TZ string + InvalidTzString(&'static str), + /// I/O error + Io(io::Error), + /// Out of range error + OutOfRange(&'static str), + /// Integer parsing error + ParseInt(ParseIntError), + /// Date time projection error + ProjectDateTime(&'static str), + /// System time error + SystemTime(SystemTimeError), + /// Time zone error + TimeZone(&'static str), + /// Transition rule error + TransitionRule(&'static str), + /// Unsupported Tzif file + UnsupportedTzFile(&'static str), + /// Unsupported TZ string + UnsupportedTzString(&'static str), + /// UTF-8 error + Utf8(Utf8Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + DateTime(error) => write!(f, "invalid date time: {}", error), + FindLocalTimeType(error) => error.fmt(f), + LocalTimeType(error) => write!(f, "invalid local time type: {}", error), + InvalidSlice(error) => error.fmt(f), + InvalidTzString(error) => write!(f, "invalid TZ string: {}", error), + InvalidTzFile(error) => error.fmt(f), + Io(error) => error.fmt(f), + OutOfRange(error) => error.fmt(f), + ParseInt(error) => error.fmt(f), + ProjectDateTime(error) => error.fmt(f), + SystemTime(error) => error.fmt(f), + TransitionRule(error) => write!(f, "invalid transition rule: {}", error), + TimeZone(error) => write!(f, "invalid time zone: {}", error), + UnsupportedTzFile(error) => error.fmt(f), + UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error), + Utf8(error) => error.fmt(f), + } + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) + } +} + +impl From for Error { + fn from(error: ParseIntError) -> Self { + Error::ParseInt(error) + } +} + +impl From for Error { + fn from(error: SystemTimeError) -> Self { + Error::SystemTime(error) + } +} + +impl From for Error { + fn from(error: Utf8Error) -> Self { + Error::Utf8(error) + } +} + +// MSRV: 1.38 +#[inline] +fn rem_euclid(v: i64, rhs: i64) -> i64 { + let r = v % rhs; + if r < 0 { + if rhs < 0 { + r - rhs + } else { + r + rhs + } + } else { + r + } +} + +/// Number of hours in one day +const HOURS_PER_DAY: i64 = 24; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of seconds in one day +const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY; +/// Number of days in one week +const DAYS_PER_WEEK: i64 = 7; + +/// Month days in a normal year +const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +/// Cumulated month days in a normal year +const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; diff --git a/src/offset/tz_info/parser.rs b/src/offset/tz_info/parser.rs new file mode 100644 index 0000000000..dfbcf7c2df --- /dev/null +++ b/src/offset/tz_info/parser.rs @@ -0,0 +1,334 @@ +use std::io::{self, ErrorKind}; +use std::iter; +use std::num::ParseIntError; +use std::str::{self, FromStr}; + +use super::rule::TransitionRule; +use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition}; +use super::Error; + +#[allow(clippy::map_clone)] // MSRV: 1.36 +pub(super) fn parse(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + let state = State::new(&mut cursor, true)?; + let (state, footer) = match state.header.version { + Version::V1 => match cursor.is_empty() { + true => (state, None), + false => { + return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")) + } + }, + Version::V2 | Version::V3 => { + let state = State::new(&mut cursor, false)?; + (state, Some(cursor.remaining())) + } + }; + + let mut transitions = Vec::with_capacity(state.header.transition_count); + for (arr_time, &local_time_type_index) in + state.transition_times.chunks_exact(state.time_size).zip(state.transition_types) + { + let unix_leap_time = + state.parse_time(&arr_time[0..state.time_size], state.header.version)?; + let local_time_type_index = local_time_type_index as usize; + transitions.push(Transition::new(unix_leap_time, local_time_type_index)); + } + + let mut local_time_types = Vec::with_capacity(state.header.type_count); + for arr in state.local_time_types.chunks_exact(6) { + let ut_offset = read_be_i32(&arr[..4])?; + + let is_dst = match arr[4] { + 0 => false, + 1 => true, + _ => return Err(Error::InvalidTzFile("invalid DST indicator")), + }; + + let char_index = arr[5] as usize; + if char_index >= state.header.char_count { + return Err(Error::InvalidTzFile("invalid time zone name char index")); + } + + let position = match state.names[char_index..].iter().position(|&c| c == b'\0') { + Some(position) => position, + None => return Err(Error::InvalidTzFile("invalid time zone name char index")), + }; + + let name = &state.names[char_index..char_index + position]; + let name = if !name.is_empty() { Some(name) } else { None }; + local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?); + } + + let mut leap_seconds = Vec::with_capacity(state.header.leap_count); + for arr in state.leap_seconds.chunks_exact(state.time_size + 4) { + let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?; + let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?; + leap_seconds.push(LeapSecond::new(unix_leap_time, correction)); + } + + let std_walls_iter = state.std_walls.iter().map(|&i| i).chain(iter::repeat(0)); + let ut_locals_iter = state.ut_locals.iter().map(|&i| i).chain(iter::repeat(0)); + if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) { + return Err(Error::InvalidTzFile( + "invalid couple of standard/wall and UT/local indicators", + )); + } + + let extra_rule = match footer { + Some(footer) => { + let footer = str::from_utf8(footer)?; + if !(footer.starts_with('\n') && footer.ends_with('\n')) { + return Err(Error::InvalidTzFile("invalid footer")); + } + + let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace()); + if tz_string.starts_with(':') || tz_string.contains('\0') { + return Err(Error::InvalidTzFile("invalid footer")); + } + + match tz_string.is_empty() { + true => None, + false => Some(TransitionRule::from_tz_string( + tz_string.as_bytes(), + state.header.version == Version::V3, + )?), + } + } + None => None, + }; + + TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule) +} + +/// TZif data blocks +struct State<'a> { + header: Header, + /// Time size in bytes + time_size: usize, + /// Transition times data block + transition_times: &'a [u8], + /// Transition types data block + transition_types: &'a [u8], + /// Local time types data block + local_time_types: &'a [u8], + /// Time zone names data block + names: &'a [u8], + /// Leap seconds data block + leap_seconds: &'a [u8], + /// UT/local indicators data block + std_walls: &'a [u8], + /// Standard/wall indicators data block + ut_locals: &'a [u8], +} + +impl<'a> State<'a> { + /// Read TZif data blocks + fn new(cursor: &mut Cursor<'a>, first: bool) -> Result { + let header = Header::new(cursor)?; + let time_size = match first { + true => 4, // We always parse V1 first + false => 8, + }; + + Ok(Self { + time_size, + transition_times: cursor.read_exact(header.transition_count * time_size)?, + transition_types: cursor.read_exact(header.transition_count)?, + local_time_types: cursor.read_exact(header.type_count * 6)?, + names: cursor.read_exact(header.char_count)?, + leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?, + std_walls: cursor.read_exact(header.std_wall_count)?, + ut_locals: cursor.read_exact(header.ut_local_count)?, + header, + }) + } + + /// Parse time values + fn parse_time(&self, arr: &[u8], version: Version) -> Result { + match version { + Version::V1 => Ok(read_be_i32(&arr[..4])?.into()), + Version::V2 | Version::V3 => read_be_i64(arr), + } + } +} + +/// TZif header +#[derive(Debug)] +struct Header { + /// TZif version + version: Version, + /// Number of UT/local indicators + ut_local_count: usize, + /// Number of standard/wall indicators + std_wall_count: usize, + /// Number of leap-second records + leap_count: usize, + /// Number of transition times + transition_count: usize, + /// Number of local time type records + type_count: usize, + /// Number of time zone names bytes + char_count: usize, +} + +impl Header { + fn new(cursor: &mut Cursor) -> Result { + let magic = cursor.read_exact(4)?; + if magic != *b"TZif" { + return Err(Error::InvalidTzFile("invalid magic number")); + } + + let version = match cursor.read_exact(1)? { + [0x00] => Version::V1, + [0x32] => Version::V2, + [0x33] => Version::V3, + _ => return Err(Error::UnsupportedTzFile("unsupported TZif version")), + }; + + cursor.read_exact(15)?; + let ut_local_count = cursor.read_be_u32()?; + let std_wall_count = cursor.read_be_u32()?; + let leap_count = cursor.read_be_u32()?; + let transition_count = cursor.read_be_u32()?; + let type_count = cursor.read_be_u32()?; + let char_count = cursor.read_be_u32()?; + + if !(type_count != 0 + && char_count != 0 + && (ut_local_count == 0 || ut_local_count == type_count) + && (std_wall_count == 0 || std_wall_count == type_count)) + { + return Err(Error::InvalidTzFile("invalid header")); + } + + Ok(Self { + version, + ut_local_count: ut_local_count as usize, + std_wall_count: std_wall_count as usize, + leap_count: leap_count as usize, + transition_count: transition_count as usize, + type_count: type_count as usize, + char_count: char_count as usize, + }) + } +} + +/// A `Cursor` contains a slice of a buffer and a read count. +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Cursor<'a> { + /// Slice representing the remaining data to be read + remaining: &'a [u8], + /// Number of already read bytes + read_count: usize, +} + +impl<'a> Cursor<'a> { + /// Construct a new `Cursor` from remaining data + pub(crate) fn new(remaining: &'a [u8]) -> Self { + Self { remaining, read_count: 0 } + } + + pub(crate) fn peek(&self) -> Option<&u8> { + self.remaining().get(0) + } + + /// Returns remaining data + pub(crate) fn remaining(&self) -> &'a [u8] { + self.remaining + } + + /// Returns `true` if data is remaining + pub(crate) fn is_empty(&self) -> bool { + self.remaining.is_empty() + } + + pub(crate) fn read_be_u32(&mut self) -> Result { + let mut buf = [0; 4]; + buf.copy_from_slice(self.read_exact(4)?); + Ok(u32::from_be_bytes(buf)) + } + + /// Read exactly `count` bytes, reducing remaining data and incrementing read count + pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> { + match (self.remaining.get(..count), self.remaining.get(count..)) { + (Some(result), Some(remaining)) => { + self.remaining = remaining; + self.read_count += count; + Ok(result) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + + /// Read bytes and compare them to the provided tag + pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> { + if self.read_exact(tag.len())? == tag { + Ok(()) + } else { + Err(io::Error::from(ErrorKind::InvalidData)) + } + } + + /// Read bytes if the remaining data is prefixed by the provided tag + pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result { + if self.remaining.starts_with(tag) { + self.read_exact(tag.len())?; + Ok(true) + } else { + Ok(false) + } + } + + /// Read bytes as long as the provided predicate is true + pub(crate) fn read_while bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(|x| !f(x)) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } + + // Parse an integer out of the ASCII digits + pub(crate) fn read_int>(&mut self) -> Result { + let bytes = self.read_while(u8::is_ascii_digit)?; + Ok(str::from_utf8(bytes)?.parse()?) + } + + /// Read bytes until the provided predicate is true + pub(crate) fn read_until bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(f) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } +} + +pub(crate) fn read_be_i32(bytes: &[u8]) -> Result { + if bytes.len() != 4 { + return Err(Error::InvalidSlice("too short for i32")); + } + + let mut buf = [0; 4]; + buf.copy_from_slice(bytes); + Ok(i32::from_be_bytes(buf)) +} + +pub(crate) fn read_be_i64(bytes: &[u8]) -> Result { + if bytes.len() != 8 { + return Err(Error::InvalidSlice("too short for i64")); + } + + let mut buf = [0; 8]; + buf.copy_from_slice(bytes); + Ok(i64::from_be_bytes(buf)) +} + +/// TZif version +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Version { + /// Version 1 + V1, + /// Version 2 + V2, + /// Version 3 + V3, +} diff --git a/src/offset/tz_info/rule.rs b/src/offset/tz_info/rule.rs new file mode 100644 index 0000000000..3e2cf2a3a1 --- /dev/null +++ b/src/offset/tz_info/rule.rs @@ -0,0 +1,911 @@ +use std::cmp::Ordering; + +use super::parser::Cursor; +use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; +use super::{ + rem_euclid, Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, + SECONDS_PER_DAY, +}; + +/// Transition rule +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) enum TransitionRule { + /// Fixed local time type + Fixed(LocalTimeType), + /// Alternate local time types + Alternate(AlternateTime), +} + +impl TransitionRule { + /// Parse a POSIX TZ string containing a time zone description, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + /// + /// TZ string extensions from [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536#section-3.3.1) may be used. + /// + pub(super) fn from_tz_string( + tz_string: &[u8], + use_string_extensions: bool, + ) -> Result { + let mut cursor = Cursor::new(tz_string); + + let std_time_zone = Some(parse_name(&mut cursor)?); + let std_offset = parse_offset(&mut cursor)?; + + if cursor.is_empty() { + return Ok(LocalTimeType::new(-std_offset, false, std_time_zone)?.into()); + } + + let dst_time_zone = Some(parse_name(&mut cursor)?); + + let dst_offset = match cursor.peek() { + Some(&b',') => std_offset - 3600, + Some(_) => parse_offset(&mut cursor)?, + None => { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")) + } + }; + + if cursor.is_empty() { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); + } + + cursor.read_tag(b",")?; + let (dst_start, dst_start_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + cursor.read_tag(b",")?; + let (dst_end, dst_end_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + if !cursor.is_empty() { + return Err(Error::InvalidTzString("remaining data after parsing TZ string")); + } + + Ok(AlternateTime::new( + LocalTimeType::new(-std_offset, false, std_time_zone)?, + LocalTimeType::new(-dst_offset, true, dst_time_zone)?, + dst_start, + dst_start_time, + dst_end, + dst_end_time, + )? + .into()) + } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + match self { + TransitionRule::Fixed(local_time_type) => Ok(local_time_type), + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type(unix_time) + } + } + } +} + +impl From for TransitionRule { + fn from(inner: LocalTimeType) -> Self { + TransitionRule::Fixed(inner) + } +} + +impl From for TransitionRule { + fn from(inner: AlternateTime) -> Self { + TransitionRule::Alternate(inner) + } +} + +/// Transition rule representing alternate local time types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct AlternateTime { + /// Local time type for standard time + pub(super) std: LocalTimeType, + /// Local time type for Daylight Saving Time + pub(super) dst: LocalTimeType, + /// Start day of Daylight Saving Time + dst_start: RuleDay, + /// Local start day time of Daylight Saving Time, in seconds + dst_start_time: i32, + /// End day of Daylight Saving Time + dst_end: RuleDay, + /// Local end day time of Daylight Saving Time, in seconds + dst_end_time: i32, +} + +impl AlternateTime { + /// Construct a transition rule representing alternate local time types + fn new( + std: LocalTimeType, + dst: LocalTimeType, + dst_start: RuleDay, + dst_start_time: i32, + dst_end: RuleDay, + dst_end_time: i32, + ) -> Result { + // Overflow is not possible + if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK + && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) + { + return Err(Error::TransitionRule("invalid DST start or end time")); + } + + Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time }) + } + + /// Find the local time type associated to the alternate transition rule at the specified Unix time in seconds + fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + // Overflow is not possible + let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; + let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; + + let current_year = match UtcDateTime::from_timespec(unix_time) { + Ok(dt) => dt.year, + Err(error) => return Err(error), + }; + + // Check if the current year is valid for the following computations + if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) { + return Err(Error::OutOfRange("out of range date time")); + } + + let current_year_dst_start_unix_time = + self.dst_start.unix_time(current_year, dst_start_time_in_utc); + let current_year_dst_end_unix_time = + self.dst_end.unix_time(current_year, dst_end_time_in_utc); + + // Check DST start/end Unix times for previous/current/next years to support for transition day times outside of [0h, 24h] range + let is_dst = + match Ord::cmp(¤t_year_dst_start_unix_time, ¤t_year_dst_end_unix_time) { + Ordering::Less | Ordering::Equal => { + if unix_time < current_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + if unix_time < previous_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + previous_year_dst_start_unix_time <= unix_time + } else { + false + } + } else if unix_time < current_year_dst_end_unix_time { + true + } else { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + if next_year_dst_start_unix_time <= unix_time { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + unix_time < next_year_dst_end_unix_time + } else { + false + } + } + } + Ordering::Greater => { + if unix_time < current_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + if unix_time < previous_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + unix_time < previous_year_dst_end_unix_time + } else { + true + } + } else if unix_time < current_year_dst_start_unix_time { + false + } else { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + if next_year_dst_end_unix_time <= unix_time { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + next_year_dst_start_unix_time <= unix_time + } else { + true + } + } + } + }; + + if is_dst { + Ok(&self.dst) + } else { + Ok(&self.std) + } + } +} + +/// Parse time zone name +fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> { + match cursor.peek() { + Some(b'<') => {} + _ => return Ok(cursor.read_while(u8::is_ascii_alphabetic)?), + } + + cursor.read_exact(1)?; + let unquoted = cursor.read_until(|&x| x == b'>')?; + cursor.read_exact(1)?; + Ok(unquoted) +} + +/// Parse time zone offset +fn parse_offset(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + if hour < 0 || hour > 24 { + return Err(Error::InvalidTzString("invalid offset hour")); + } + if minute < 0 || minute > 59 { + return Err(Error::InvalidTzString("invalid offset minute")); + } + if second < 0 || second > 59 { + return Err(Error::InvalidTzString("invalid offset second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse transition rule time +fn parse_rule_time(cursor: &mut Cursor) -> Result { + let (hour, minute, second) = parse_hhmmss(cursor)?; + + if hour < 0 || hour > 24 { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if minute < 0 || minute > 59 { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if second < 0 || second > 59 { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(hour * 3600 + minute * 60 + second) +} + +/// Parse transition rule time with TZ string extensions +fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + if hour < -167 || hour > 167 { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if minute < 0 || minute > 59 { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if second < 0 || second > 59 { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse hours, minutes and seconds +fn parse_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32), Error> { + let hour = cursor.read_int()?; + + let mut minute = 0; + let mut second = 0; + + if cursor.read_optional_tag(b":")? { + minute = cursor.read_int()?; + + if cursor.read_optional_tag(b":")? { + second = cursor.read_int()?; + } + } + + Ok((hour, minute, second)) +} + +/// Parse signed hours, minutes and seconds +fn parse_signed_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32, i32), Error> { + let mut sign = 1; + if let Some(&c) = cursor.peek() { + if c == b'+' || c == b'-' { + cursor.read_exact(1)?; + if c == b'-' { + sign = -1; + } + } + } + + let (hour, minute, second) = parse_hhmmss(cursor)?; + Ok((sign, hour, minute, second)) +} + +/// Transition rule day +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum RuleDay { + /// Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + Julian1WithoutLeap(u16), + /// Zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + Julian0WithLeap(u16), + /// Day represented by a month, a month week and a week day + MonthWeekday { + /// Month in `[1, 12]` + month: u8, + /// Week of the month in `[1, 5]`, with `5` representing the last week of the month + week: u8, + /// Day of the week in `[0, 6]` from Sunday + week_day: u8, + }, +} + +impl RuleDay { + /// Parse transition rule + fn parse(cursor: &mut Cursor, use_string_extensions: bool) -> Result<(Self, i32), Error> { + let date = match cursor.peek() { + Some(b'M') => { + cursor.read_exact(1)?; + let month = cursor.read_int()?; + cursor.read_tag(b".")?; + let week = cursor.read_int()?; + cursor.read_tag(b".")?; + let week_day = cursor.read_int()?; + RuleDay::month_weekday(month, week, week_day)? + } + Some(b'J') => { + cursor.read_exact(1)?; + RuleDay::julian_1(cursor.read_int()?)? + } + _ => RuleDay::julian_0(cursor.read_int()?)?, + }; + + Ok(( + date, + match (cursor.read_optional_tag(b"/")?, use_string_extensions) { + (false, _) => 2 * 3600, + (true, true) => parse_rule_time_extended(cursor)?, + (true, false) => parse_rule_time(cursor)?, + }, + )) + } + + /// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + fn julian_1(julian_day_1: u16) -> Result { + if julian_day_1 < 1 || julian_day_1 > 365 { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian1WithoutLeap(julian_day_1)) + } + + /// Construct a transition rule day represented by a zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + fn julian_0(julian_day_0: u16) -> Result { + if julian_day_0 > 365 { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian0WithLeap(julian_day_0)) + } + + /// Construct a transition rule day represented by a month, a month week and a week day + fn month_weekday(month: u8, week: u8, week_day: u8) -> Result { + if month < 1 || month > 12 { + return Err(Error::TransitionRule("invalid rule day month")); + } + + if week < 1 || week > 5 { + return Err(Error::TransitionRule("invalid rule day week")); + } + + if week_day > 6 { + return Err(Error::TransitionRule("invalid rule day week day")); + } + + Ok(RuleDay::MonthWeekday { month, week, week_day }) + } + + /// Get the transition date for the provided year + /// + /// ## Outputs + /// + /// * `month`: Month in `[1, 12]` + /// * `month_day`: Day of the month in `[1, 31]` + fn transition_date(&self, year: i32) -> (usize, i64) { + match *self { + RuleDay::Julian1WithoutLeap(year_day) => { + let year_day = year_day as i64; + + let month = match CUMUL_DAY_IN_MONTHS_NORMAL_YEAR.binary_search(&(year_day - 1)) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = year_day - CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + + (month, month_day) + } + RuleDay::Julian0WithLeap(year_day) => { + let leap = is_leap_year(year) as i64; + + let cumul_day_in_months = [ + 0, + 31, + 59 + leap, + 90 + leap, + 120 + leap, + 151 + leap, + 181 + leap, + 212 + leap, + 243 + leap, + 273 + leap, + 304 + leap, + 334 + leap, + ]; + + let year_day = year_day as i64; + + let month = match cumul_day_in_months.binary_search(&year_day) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = 1 + year_day - cumul_day_in_months[month - 1]; + + (month, month_day) + } + RuleDay::MonthWeekday { month: rule_month, week, week_day } => { + let leap = is_leap_year(year) as i64; + + let month = rule_month as usize; + + let mut day_in_month = DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + if month == 2 { + day_in_month += leap; + } + + let week_day_of_first_month_day = + rem_euclid(4 + days_since_unix_epoch(year, month, 1), DAYS_PER_WEEK); + let first_week_day_occurence_in_month = + 1 + rem_euclid(week_day as i64 - week_day_of_first_month_day, DAYS_PER_WEEK); + + let mut month_day = + first_week_day_occurence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; + if month_day > day_in_month { + month_day -= DAYS_PER_WEEK + } + + (month, month_day) + } + } + } + + /// Returns the UTC Unix time in seconds associated to the transition date for the provided year + fn unix_time(&self, year: i32, day_time_in_utc: i64) -> i64 { + let (month, month_day) = self.transition_date(year); + days_since_unix_epoch(year, month, month_day) * SECONDS_PER_DAY + day_time_in_utc + } +} + +/// UTC date time exprimed in the [proleptic gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct UtcDateTime { + /// Year + pub(crate) year: i32, + /// Month in `[1, 12]` + pub(crate) month: u8, + /// Day of the month in `[1, 31]` + pub(crate) month_day: u8, + /// Hours since midnight in `[0, 23]` + pub(crate) hour: u8, + /// Minutes in `[0, 59]` + pub(crate) minute: u8, + /// Seconds in `[0, 60]`, with a possible leap second + pub(crate) second: u8, +} + +impl UtcDateTime { + /// Construct a UTC date time from a Unix time in seconds and nanoseconds + pub(crate) fn from_timespec(unix_time: i64) -> Result { + let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) { + Some(seconds) => seconds, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + let mut remaining_days = seconds / SECONDS_PER_DAY; + let mut remaining_seconds = seconds % SECONDS_PER_DAY; + if remaining_seconds < 0 { + remaining_seconds += SECONDS_PER_DAY; + remaining_days -= 1; + } + + let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS; + remaining_days %= DAYS_PER_400_YEARS; + if remaining_days < 0 { + remaining_days += DAYS_PER_400_YEARS; + cycles_400_years -= 1; + } + + let cycles_100_years = Ord::min(remaining_days / DAYS_PER_100_YEARS, 3); + remaining_days -= cycles_100_years * DAYS_PER_100_YEARS; + + let cycles_4_years = Ord::min(remaining_days / DAYS_PER_4_YEARS, 24); + remaining_days -= cycles_4_years * DAYS_PER_4_YEARS; + + let remaining_years = Ord::min(remaining_days / DAYS_PER_NORMAL_YEAR, 3); + remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR; + + let mut year = OFFSET_YEAR + + remaining_years + + cycles_4_years * 4 + + cycles_100_years * 100 + + cycles_400_years * 400; + + let mut month = 0; + while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() { + let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month]; + if remaining_days < days { + break; + } + remaining_days -= days; + month += 1; + } + month += 2; + + if month >= MONTHS_PER_YEAR as usize { + month -= MONTHS_PER_YEAR as usize; + year += 1; + } + month += 1; + + let month_day = 1 + remaining_days; + + let hour = remaining_seconds / SECONDS_PER_HOUR; + let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; + let second = remaining_seconds % SECONDS_PER_MINUTE; + + let year = match year >= i32::min_value() as i64 && year <= i32::max_value() as i64 { + true => year as i32, + false => return Err(Error::OutOfRange("i64 is out of range for i32")), + }; + + Ok(Self { + year, + month: month as u8, + month_day: month_day as u8, + hour: hour as u8, + minute: minute as u8, + second: second as u8, + }) + } +} + +/// Number of nanoseconds in one second +const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000; +/// Number of seconds in one minute +const SECONDS_PER_MINUTE: i64 = 60; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of minutes in one hour +const MINUTES_PER_HOUR: i64 = 60; +/// Number of months in one year +const MONTHS_PER_YEAR: i64 = 12; +/// Number of days in a normal year +const DAYS_PER_NORMAL_YEAR: i64 = 365; +/// Number of days in 4 years (including 1 leap year) +const DAYS_PER_4_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 4 + 1; +/// Number of days in 100 years (including 24 leap years) +const DAYS_PER_100_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 100 + 24; +/// Number of days in 400 years (including 97 leap years) +const DAYS_PER_400_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 400 + 97; +/// Unix time at `2000-03-01T00:00:00Z` (Wednesday) +const UNIX_OFFSET_SECS: i64 = 951868800; +/// Offset year +const OFFSET_YEAR: i64 = 2000; +/// Month days in a leap year from March +const DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH: [i64; 12] = + [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + +/// Compute the number of days since Unix epoch (`1970-01-01T00:00:00Z`). +/// +/// ## Inputs +/// +/// * `year`: Year +/// * `month`: Month in `[1, 12]` +/// * `month_day`: Day of the month in `[1, 31]` +pub(crate) fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 { + let is_leap_year = is_leap_year(year); + + let year = year as i64; + + let mut result = (year - 1970) * 365; + + if year >= 1970 { + result += (year - 1968) / 4; + result -= (year - 1900) / 100; + result += (year - 1600) / 400; + + if is_leap_year && month < 3 { + result -= 1; + } + } else { + result += (year - 1972) / 4; + result -= (year - 2000) / 100; + result += (year - 2000) / 400; + + if is_leap_year && month >= 3 { + result += 1; + } + } + + result += CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1; + + result +} + +/// Check if a year is a leap year +pub(crate) fn is_leap_year(year: i32) -> bool { + year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) +} + +#[cfg(test)] +mod tests { + use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule}; + use crate::matches; + use crate::offset::tz_info::timezone::Transition; + use crate::offset::tz_info::{Error, TimeZone}; + + #[test] + fn test_quoted() -> Result<(), Error> { + let transition_rule = TransitionRule::from_tz_string(b"<-03>+3<+03>-3,J1,J365", false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(10800, true, Some(b"+03"))?, + RuleDay::julian_1(1)?, + 7200, + RuleDay::julian_1(365)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_full() -> Result<(), Error> { + let tz_string = b"NZST-12:00:00NZDT-13:00:00,M10.1.0/02:00:00,M3.3.0/02:00:00"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_dst() -> Result<(), Error> { + let tz_string = b"IST-1GMT0,M10.5.0,M3.5.0/1"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_hour() -> Result<(), Error> { + let tz_string = b"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_all_year_dst() -> Result<(), Error> { + let tz_string = b"EST5EDT,0/0,J365/25"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_v3_file() -> Result<(), Error> { + let bytes = b"TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\x1c\x20\0\0IST\0TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x04\0\0\0\0\x7f\xe8\x17\x80\0\0\0\x1c\x20\0\0IST\0\x01\x01\x0aIST-2IDT,M3.4.4/26,M10.5.0\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![Transition::new(2145916800, 0)], + vec![LocalTimeType::new(7200, false, Some(b"IST"))?], + Vec::new(), + Some(TransitionRule::from(AlternateTime::new( + LocalTimeType::new(7200, false, Some(b"IST"))?, + LocalTimeType::new(10800, true, Some(b"IDT"))?, + RuleDay::month_weekday(3, 4, 4)?, + 93600, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + )?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_rule_day() -> Result<(), Error> { + let rule_day_j1 = RuleDay::julian_1(60)?; + assert_eq!(rule_day_j1.transition_date(2000), (3, 1)); + assert_eq!(rule_day_j1.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j1.unix_time(2000, 43200), 951912000); + + let rule_day_j0 = RuleDay::julian_0(59)?; + assert_eq!(rule_day_j0.transition_date(2000), (2, 29)); + assert_eq!(rule_day_j0.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j0.unix_time(2000, 43200), 951825600); + + let rule_day_mwd = RuleDay::month_weekday(2, 5, 2)?; + assert_eq!(rule_day_mwd.transition_date(2000), (2, 29)); + assert_eq!(rule_day_mwd.transition_date(2001), (2, 27)); + assert_eq!(rule_day_mwd.unix_time(2000, 43200), 951825600); + assert_eq!(rule_day_mwd.unix_time(2001, 43200), 983275200); + + Ok(()) + } + + #[test] + fn test_transition_rule() -> Result<(), Error> { + let transition_rule_fixed = TransitionRule::from(LocalTimeType::new(-36000, false, None)?); + assert_eq!(transition_rule_fixed.find_local_time_type(0)?.offset(), -36000); + + let transition_rule_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )?); + + assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.offset(), 46800); + assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.offset(), 46800); + + let transition_rule_negative_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )?); + + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.offset(), 0); + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.offset(), 0); + + let transition_rule_negative_time_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(0, false, None)?, + LocalTimeType::new(0, true, None)?, + RuleDay::julian_0(100)?, + 0, + RuleDay::julian_0(101)?, + -86500, + )?); + + assert!(transition_rule_negative_time_1.find_local_time_type(8639899)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639900)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639999)?.is_dst()); + assert!(transition_rule_negative_time_1.find_local_time_type(8640000)?.is_dst()); + + let transition_rule_negative_time_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )?); + + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032399)?.offset(), + -10800 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032400)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781199)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781200)?.offset(), + -10800 + ); + + let transition_rule_all_year_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )?); + + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.offset(), -14400); + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.offset(), -14400); + + Ok(()) + } + + #[test] + fn test_transition_rule_overflow() -> Result<(), Error> { + let transition_rule_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-1, false, None)?, + LocalTimeType::new(-1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let transition_rule_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(1, false, None)?, + LocalTimeType::new(1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let min_unix_time = -67768100567971200; + let max_unix_time = 67767976233532799; + + assert!(matches!( + transition_rule_1.find_local_time_type(min_unix_time), + Err(Error::OutOfRange(_)) + )); + assert!(matches!( + transition_rule_2.find_local_time_type(max_unix_time), + Err(Error::OutOfRange(_)) + )); + + Ok(()) + } +} diff --git a/src/offset/tz_info/timezone.rs b/src/offset/tz_info/timezone.rs new file mode 100644 index 0000000000..f61d090e84 --- /dev/null +++ b/src/offset/tz_info/timezone.rs @@ -0,0 +1,806 @@ +//! Types related to a time zone. + +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::{fmt, str}; + +use super::rule::{AlternateTime, TransitionRule}; +use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; + +/// Time zone +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct TimeZone { + /// List of transitions + transitions: Vec, + /// List of local time types (cannot be empty) + local_time_types: Vec, + /// List of leap seconds + leap_seconds: Vec, + /// Extra transition rule applicable after the last transition + extra_rule: Option, +} + +impl TimeZone { + /// Returns local time zone. + /// + /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. + /// + pub(crate) fn local() -> Result { + Self::from_posix_tz("localtime") + } + + /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + fn from_posix_tz(tz_string: &str) -> Result { + if tz_string.is_empty() { + return Err(Error::InvalidTzString("empty TZ string")); + } + + if tz_string == "localtime" { + return Self::from_tz_data(&fs::read("/etc/localtime")?); + } + + let mut chars = tz_string.chars(); + if chars.next() == Some(':') { + return Self::from_file(&mut find_tz_file(chars.as_str())?); + } + + if let Ok(mut file) = find_tz_file(tz_string) { + return Self::from_file(&mut file); + } + + // TZ string extensions are not allowed + let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace()); + let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?; + Self::new( + vec![], + match rule { + TransitionRule::Fixed(local_time_type) => vec![local_time_type], + TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst], + }, + vec![], + Some(rule), + ) + } + + /// Construct a time zone + pub(super) fn new( + transitions: Vec, + local_time_types: Vec, + leap_seconds: Vec, + extra_rule: Option, + ) -> Result { + let new = Self { transitions, local_time_types, leap_seconds, extra_rule }; + new.as_ref().validate()?; + Ok(new) + } + + /// Construct a time zone from the contents of a time zone file + fn from_file(file: &mut File) -> Result { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + Self::from_tz_data(&bytes) + } + + /// Construct a time zone from the contents of a time zone file + /// + /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). + pub(super) fn from_tz_data(bytes: &[u8]) -> Result { + parser::parse(bytes) + } + + /// Construct a time zone with the specified UTC offset in seconds + fn fixed(ut_offset: i32) -> Result { + Ok(Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::with_offset(ut_offset)?], + leap_seconds: Vec::new(), + extra_rule: None, + }) + } + + /// Construct the time zone associated to UTC + fn utc() -> Self { + Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::UTC], + leap_seconds: Vec::new(), + extra_rule: None, + } + } + + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + self.as_ref().find_local_time_type(unix_time) + } + + /// Returns a reference to the time zone + fn as_ref(&self) -> TimeZoneRef { + TimeZoneRef { + transitions: &self.transitions, + local_time_types: &self.local_time_types, + leap_seconds: &self.leap_seconds, + extra_rule: &self.extra_rule, + } + } +} + +/// Reference to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct TimeZoneRef<'a> { + /// List of transitions + transitions: &'a [Transition], + /// List of local time types (cannot be empty) + local_time_types: &'a [LocalTimeType], + /// List of leap seconds + leap_seconds: &'a [LeapSecond], + /// Extra transition rule applicable after the last transition + extra_rule: &'a Option, +} + +impl<'a> TimeZoneRef<'a> { + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> { + let extra_rule = match self.transitions.last() { + None => match self.extra_rule { + Some(extra_rule) => extra_rule, + None => return Ok(&self.local_time_types[0]), + }, + Some(last_transition) => { + let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) { + Ok(unix_leap_time) => unix_leap_time, + Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + Err(err) => return Err(err), + }; + + if unix_leap_time >= last_transition.unix_leap_time { + match self.extra_rule { + Some(extra_rule) => extra_rule, + None => { + return Err(Error::FindLocalTimeType( + "no local time type is available for the specified timestamp", + )) + } + } + } else { + let index = match self + .transitions + .binary_search_by_key(&unix_leap_time, Transition::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let local_time_type_index = if index > 0 { + self.transitions[index - 1].local_time_type_index + } else { + 0 + }; + return Ok(&self.local_time_types[local_time_type_index]); + } + } + }; + + match extra_rule.find_local_time_type(unix_time) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } + + /// Check time zone inputs + fn validate(&self) -> Result<(), Error> { + // Check local time types + let local_time_types_size = self.local_time_types.len(); + if local_time_types_size == 0 { + return Err(Error::TimeZone("list of local time types must not be empty")); + } + + // Check transitions + let mut i_transition = 0; + while i_transition < self.transitions.len() { + if self.transitions[i_transition].local_time_type_index >= local_time_types_size { + return Err(Error::TimeZone("invalid local time type index")); + } + + if i_transition + 1 < self.transitions.len() + && self.transitions[i_transition].unix_leap_time + >= self.transitions[i_transition + 1].unix_leap_time + { + return Err(Error::TimeZone("invalid transition")); + } + + i_transition += 1; + } + + // 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) + { + return Err(Error::TimeZone("invalid leap second")); + } + + let min_interval = SECONDS_PER_28_DAYS - 1; + + let mut i_leap_second = 0; + while i_leap_second < self.leap_seconds.len() { + if i_leap_second + 1 < self.leap_seconds.len() { + let x0 = &self.leap_seconds[i_leap_second]; + let x1 = &self.leap_seconds[i_leap_second + 1]; + + 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)); + + if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) { + return Err(Error::TimeZone("invalid leap second")); + } + } + i_leap_second += 1; + } + + // Check extra rule + let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) { + (Some(rule), Some(trans)) => (rule, trans), + _ => return Ok(()), + }; + + let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index]; + let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) { + Ok(unix_time) => unix_time, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) { + Ok(rule_local_time_type) => rule_local_time_type, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset + && last_local_time_type.is_dst == rule_local_time_type.is_dst + && match (&last_local_time_type.name, &rule_local_time_type.name) { + (Some(x), Some(y)) => x.equal(y), + (None, None) => true, + _ => false, + }; + + if !check { + return Err(Error::TimeZone( + "extra transition rule is inconsistent with the last transition", + )); + } + + Ok(()) + } + + /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone + fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { + let mut unix_leap_time = unix_time; + + let mut i = 0; + while i < self.leap_seconds.len() { + let leap_second = &self.leap_seconds[i]; + + if unix_leap_time < leap_second.unix_leap_time { + break; + } + + unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) { + Some(unix_leap_time) => unix_leap_time, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + i += 1; + } + + Ok(unix_leap_time) + } + + /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone + fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { + if unix_leap_time == i64::min_value() { + return Err(Error::OutOfRange("out of range operation")); + } + + let index = match self + .leap_seconds + .binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 }; + + match unix_leap_time.checked_sub(correction as i64) { + Some(unix_time) => Ok(unix_time), + None => Err(Error::OutOfRange("out of range operation")), + } + } + + /// The UTC time zone + const UTC: TimeZoneRef<'static> = TimeZoneRef { + transitions: &[], + local_time_types: &[LocalTimeType::UTC], + leap_seconds: &[], + extra_rule: &None, + }; +} + +/// Transition of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct Transition { + /// Unix leap time + unix_leap_time: i64, + /// Index specifying the local time type of the transition + local_time_type_index: usize, +} + +impl Transition { + /// Construct a TZif file transition + pub(super) fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { + Self { unix_leap_time, local_time_type_index } + } + + /// Returns Unix leap time + fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// Leap second of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct LeapSecond { + /// Unix leap time + unix_leap_time: i64, + /// Leap second correction + correction: i32, +} + +impl LeapSecond { + /// Construct a TZif file leap second + pub(super) fn new(unix_leap_time: i64, correction: i32) -> Self { + Self { unix_leap_time, correction } + } + + /// Returns Unix leap time + fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// ASCII-encoded fixed-capacity string, used for storing time zone names +#[derive(Copy, Clone, Eq, PartialEq)] +struct TimeZoneName { + /// Length-prefixed string buffer + bytes: [u8; 8], +} + +impl TimeZoneName { + /// Construct a time zone name + fn new(input: &[u8]) -> Result { + let len = input.len(); + + if len < 3 || len > 7 { + return Err(Error::LocalTimeType( + "time zone name must have between 3 and 7 characters", + )); + } + + let mut bytes = [0; 8]; + bytes[0] = input.len() as u8; + + let mut i = 0; + while i < len { + let b = input[i]; + match b { + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} + _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), + } + + bytes[i + 1] = b; + i += 1; + } + + Ok(Self { bytes }) + } + + /// Returns time zone name as a byte slice + fn as_bytes(&self) -> &[u8] { + match self.bytes[0] { + 3 => &self.bytes[1..4], + 4 => &self.bytes[1..5], + 5 => &self.bytes[1..6], + 6 => &self.bytes[1..7], + 7 => &self.bytes[1..8], + _ => unreachable!(), + } + } + + /// Check if two time zone names are equal + fn equal(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl AsRef for TimeZoneName { + fn as_ref(&self) -> &str { + // SAFETY: ASCII is valid UTF-8 + unsafe { str::from_utf8_unchecked(self.as_bytes()) } + } +} + +impl fmt::Debug for TimeZoneName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +/// Local time type associated to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct LocalTimeType { + /// Offset from UTC in seconds + pub(super) ut_offset: i32, + /// Daylight Saving Time indicator + is_dst: bool, + /// Time zone name + name: Option, +} + +impl LocalTimeType { + /// Construct a local time type + pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result { + if ut_offset == i32::min_value() { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + let name = match name { + Some(name) => TimeZoneName::new(name)?, + None => return Ok(Self { ut_offset, is_dst, name: None }), + }; + + Ok(Self { ut_offset, is_dst, name: Some(name) }) + } + + /// Construct a local time type with the specified UTC offset in seconds + pub(super) fn with_offset(ut_offset: i32) -> Result { + if ut_offset == i32::min_value() { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + Ok(Self { ut_offset, is_dst: false, name: None }) + } + + /// Returns offset from UTC in seconds + pub(crate) fn offset(&self) -> i32 { + self.ut_offset + } + + /// Returns daylight saving time indicator + pub(super) fn is_dst(&self) -> bool { + self.is_dst + } + + pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None }; +} + +/// Open the TZif file corresponding to a TZ string +fn find_tz_file(path: impl AsRef) -> Result { + // Don't check system timezone directories on non-UNIX platforms + #[cfg(not(unix))] + return Ok(File::open(path)?); + + #[cfg(unix)] + { + let path = path.as_ref(); + if path.is_absolute() { + return Ok(File::open(path)?); + } + + for folder in &ZONE_INFO_DIRECTORIES { + if let Ok(file) = File::open(PathBuf::from(folder).join(path)) { + return Ok(file); + } + } + + Err(Error::Io(io::ErrorKind::NotFound.into())) + } +} + +#[inline] +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; 3] = + ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo"]; + +/// Number of seconds in one week +pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK; +/// Number of seconds in 28 days +const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; + +#[cfg(test)] +mod tests { + use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; + use crate::matches; + use crate::offset::tz_info::Error; + + #[test] + fn test_no_dst() -> Result<(), Error> { + let tz_string = b"HST10"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into()); + Ok(()) + } + + #[test] + fn test_error() -> Result<(), Error> { + assert!(matches!( + TransitionRule::from_tz_string(b"IST-1GMT0", false), + Err(Error::UnsupportedTzString(_)) + )); + assert!(matches!( + TransitionRule::from_tz_string(b"EET-2EEST", false), + Err(Error::UnsupportedTzString(_)) + )); + + Ok(()) + } + + #[test] + fn test_v1_file_with_leap_seconds() -> Result<(), Error> { + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_v2_file() -> Result<(), Error> { + let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![ + Transition::new(-2334101314, 1), + Transition::new(-1157283000, 2), + Transition::new(-1155436200, 1), + Transition::new(-880198200, 3), + Transition::new(-769395600, 4), + Transition::new(-765376200, 1), + Transition::new(-712150200, 5), + ], + vec![ + LocalTimeType::new(-37886, false, Some(b"LMT"))?, + LocalTimeType::new(-37800, false, Some(b"HST"))?, + LocalTimeType::new(-34200, true, Some(b"HDT"))?, + LocalTimeType::new(-34200, true, Some(b"HWT"))?, + LocalTimeType::new(-34200, true, Some(b"HPT"))?, + LocalTimeType::new(-36000, false, Some(b"HST"))?, + ], + Vec::new(), + Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1156939200)?, + LocalTimeType::new(-34200, true, Some(b"HDT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(1546300800)?, + LocalTimeType::new(-36000, false, Some(b"HST"))? + ); + + Ok(()) + } + + #[test] + fn test_tz_ascii_str() -> Result<(), Error> { + assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); + assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); + assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); + assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); + assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); + assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); + + assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); + + Ok(()) + } + + #[test] + fn test_time_zone() -> Result<(), Error> { + let utc = LocalTimeType::UTC; + let cet = LocalTimeType::with_offset(3600)?; + + let utc_local_time_types = vec![utc]; + let fixed_extra_rule = TransitionRule::from(cet); + + let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?; + let time_zone_2 = + TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?; + let time_zone_3 = + TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?; + let time_zone_4 = TimeZone::new( + vec![Transition::new(i32::min_value().into(), 0), Transition::new(0, 1)], + vec![utc, cet], + Vec::new(), + Some(fixed_extra_rule), + )?; + + assert_eq!(*time_zone_1.find_local_time_type(0)?, utc); + assert_eq!(*time_zone_2.find_local_time_type(0)?, cet); + + assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc); + assert!(matches!(time_zone_3.find_local_time_type(0), Err(Error::FindLocalTimeType(_)))); + + assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc); + assert_eq!(*time_zone_4.find_local_time_type(0)?, cet); + + let time_zone_err = TimeZone::new( + vec![Transition::new(0, 0)], + utc_local_time_types, + vec![], + Some(fixed_extra_rule), + ); + assert!(time_zone_err.is_err()); + + Ok(()) + } + + #[test] + fn test_time_zone_from_posix_tz() -> Result<(), Error> { + #[cfg(unix)] + { + let time_zone_local = TimeZone::local()?; + let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?; + let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?; + let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?; + + assert_eq!(time_zone_local, time_zone_local_1); + assert_eq!(time_zone_local, time_zone_local_2); + assert_eq!(time_zone_local, time_zone_local_3); + + let time_zone_utc = TimeZone::from_posix_tz("UTC")?; + assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); + } + + assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err()); + assert!(TimeZone::from_posix_tz("").is_err()); + + Ok(()) + } + + #[test] + fn test_leap_seconds() -> Result<(), Error> { + let time_zone = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + let time_zone_ref = time_zone.as_ref(); + + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601))); + + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624))); + + Ok(()) + } + + #[test] + fn test_leap_seconds_overflow() -> Result<(), Error> { + let time_zone_err = TimeZone::new( + vec![Transition::new(i64::min_value(), 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + Some(TransitionRule::from(LocalTimeType::UTC)), + ); + assert!(time_zone_err.is_err()); + + let time_zone = TimeZone::new( + vec![Transition::new(i64::max_value(), 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + None, + )?; + assert!(matches!( + time_zone.find_local_time_type(i64::max_value()), + Err(Error::FindLocalTimeType(_)) + )); + + Ok(()) + } +} From f5218bfa13b2edd92363f498707da788f9635e5e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 14:11:53 +0200 Subject: [PATCH 065/999] sys/unix: cache time zone info --- src/offset/sys/unix.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/offset/sys/unix.rs b/src/offset/sys/unix.rs index e83f9da1fe..3013797cf1 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/sys/unix.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::sync::Once; + use super::{DateTime, FixedOffset, Local, NaiveDateTime}; use crate::offset::tz_info::TimeZone; use crate::Utc; @@ -27,11 +29,17 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime } fn offset(unix: i64) -> FixedOffset { + let info = unsafe { + INIT.call_once(|| { + INFO = Some(TimeZone::local().expect("unable to parse localtime info")); + }); + INFO.as_ref().unwrap() + }; + FixedOffset::east( - TimeZone::local() - .expect("unable to parse localtime info") - .find_local_time_type(unix) - .expect("unable to select local time type") - .offset(), + info.find_local_time_type(unix).expect("unable to select local time type").offset(), ) } + +static mut INFO: Option = None; +static INIT: Once = Once::new(); From 2e711e79364485c12818db07c872ab7ae54ae60c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 15:11:25 +0200 Subject: [PATCH 066/999] Clarify module hierarchy --- src/offset/{local.rs => local/mod.rs} | 9 ++++++--- src/offset/{sys => local}/stub.rs | 0 src/offset/{ => local}/tz_info/mod.rs | 0 src/offset/{ => local}/tz_info/parser.rs | 0 src/offset/{ => local}/tz_info/rule.rs | 4 ++-- src/offset/{ => local}/tz_info/timezone.rs | 2 +- src/offset/{sys => local}/unix.rs | 2 +- src/offset/{sys => local}/windows.rs | 0 src/offset/mod.rs | 3 --- 9 files changed, 10 insertions(+), 10 deletions(-) rename src/offset/{local.rs => local/mod.rs} (98%) rename src/offset/{sys => local}/stub.rs (100%) rename src/offset/{ => local}/tz_info/mod.rs (100%) rename src/offset/{ => local}/tz_info/parser.rs (100%) rename src/offset/{ => local}/tz_info/rule.rs (99%) rename src/offset/{ => local}/tz_info/timezone.rs (99%) rename src/offset/{sys => local}/unix.rs (97%) rename src/offset/{sys => local}/windows.rs (100%) diff --git a/src/offset/local.rs b/src/offset/local/mod.rs similarity index 98% rename from src/offset/local.rs rename to src/offset/local/mod.rs index af5d5cc37e..2f34a67f35 100644 --- a/src/offset/local.rs +++ b/src/offset/local/mod.rs @@ -12,17 +12,20 @@ use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{Date, DateTime}; #[cfg(all(not(unix), not(windows)))] -#[path = "sys/stub.rs"] +#[path = "stub.rs"] mod inner; #[cfg(unix)] -#[path = "sys/unix.rs"] +#[path = "unix.rs"] mod inner; #[cfg(windows)] -#[path = "sys/windows.rs"] +#[path = "windows.rs"] mod inner; +#[cfg(unix)] +mod tz_info; + /// The local timescale. This is implemented via the standard `time` crate. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods diff --git a/src/offset/sys/stub.rs b/src/offset/local/stub.rs similarity index 100% rename from src/offset/sys/stub.rs rename to src/offset/local/stub.rs diff --git a/src/offset/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs similarity index 100% rename from src/offset/tz_info/mod.rs rename to src/offset/local/tz_info/mod.rs diff --git a/src/offset/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs similarity index 100% rename from src/offset/tz_info/parser.rs rename to src/offset/local/tz_info/parser.rs diff --git a/src/offset/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs similarity index 99% rename from src/offset/tz_info/rule.rs rename to src/offset/local/tz_info/rule.rs index 3e2cf2a3a1..98feb7c3aa 100644 --- a/src/offset/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -639,10 +639,10 @@ pub(crate) fn is_leap_year(year: i32) -> bool { #[cfg(test)] mod tests { + use super::super::timezone::Transition; + use super::super::{Error, TimeZone}; use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule}; use crate::matches; - use crate::offset::tz_info::timezone::Transition; - use crate::offset::tz_info::{Error, TimeZone}; #[test] fn test_quoted() -> Result<(), Error> { diff --git a/src/offset/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs similarity index 99% rename from src/offset/tz_info/timezone.rs rename to src/offset/local/tz_info/timezone.rs index f61d090e84..3266b05b73 100644 --- a/src/offset/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -533,9 +533,9 @@ const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; #[cfg(test)] mod tests { + use super::super::Error; use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; use crate::matches; - use crate::offset::tz_info::Error; #[test] fn test_no_dst() -> Result<(), Error> { diff --git a/src/offset/sys/unix.rs b/src/offset/local/unix.rs similarity index 97% rename from src/offset/sys/unix.rs rename to src/offset/local/unix.rs index 3013797cf1..0cd5406a44 100644 --- a/src/offset/sys/unix.rs +++ b/src/offset/local/unix.rs @@ -10,8 +10,8 @@ use std::sync::Once; +use super::tz_info::TimeZone; use super::{DateTime, FixedOffset, Local, NaiveDateTime}; -use crate::offset::tz_info::TimeZone; use crate::Utc; pub(super) fn now() -> DateTime { diff --git a/src/offset/sys/windows.rs b/src/offset/local/windows.rs similarity index 100% rename from src/offset/sys/windows.rs rename to src/offset/local/windows.rs diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 8818676f5a..eb89680ae9 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -33,9 +33,6 @@ mod local; #[cfg(feature = "clock")] pub use self::local::Local; -#[cfg(all(unix, feature = "clock"))] -mod tz_info; - mod utc; pub use self::utc::Utc; From 2caab3493e39377cc371fbcac8a73446d35679ab Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 1 May 2022 15:55:54 +1000 Subject: [PATCH 067/999] Test Local functions against date command (#682) --- src/offset/local/mod.rs | 121 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 2f34a67f35..8468c21859 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -137,7 +137,126 @@ impl TimeZone for Local { mod tests { use super::Local; use crate::offset::TimeZone; - use crate::Datelike; + use crate::{Datelike, Duration, NaiveDate, NaiveDateTime, Timelike}; + + use std::{path, process}; + + #[cfg(unix)] + fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { + let output = process::Command::new(path) + .arg("-d") + .arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour())) + .arg("+%Y-%m-%d %H:%M:%S %:z") + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + + // The below would be preferred. At this stage neither earliest() or latest() + // seems to be consistent with the output of the `date` command, so we simply + // compare both. + // let local = Local + // .from_local_datetime(&NaiveDate::from_ymd(year, month, day).and_hms(hour, 5, 1)) + // // looks like the "date" command always returns a given time when it is ambiguous + // .earliest(); + + // if let Some(local) = local { + // assert_eq!(format!("{}\n", local), date_command_str); + // } else { + // // we are in a "Spring forward gap" due to DST, and so date also returns "" + // assert_eq!("", date_command_str); + // } + + // This is used while a decision is made wheter the `date` output needs to + // be exactly matched, or whether LocalResult::Ambigious should be handled + // differently + match Local.from_local_datetime( + &NaiveDate::from_ymd(dt.year(), dt.month(), dt.day()).and_hms(dt.hour(), 5, 1), + ) { + crate::LocalResult::Ambiguous(a, b) => { + assert!( + format!("{}\n", a) == date_command_str + || format!("{}\n", b) == date_command_str + ) + } + crate::LocalResult::Single(a) => { + assert_eq!(format!("{}\n", a), date_command_str); + } + crate::LocalResult::None => { + assert_eq!("", date_command_str); + } + } + } + + #[test] + #[cfg(unix)] + fn try_verify_against_date_command() { + let date_path = "/usr/bin/date"; + + if !path::Path::new(date_path).exists() { + // date command not found, skipping + // avoid running this on macOS, which has path /bin/date + // as the required CLI arguments are not present in the + // macOS build. + return; + } + + let mut date = NaiveDate::from_ymd(1975, 1, 1).and_hms(0, 0, 0); + + while date.year() < 2078 { + if (1975..=1977).contains(&date.year()) + || (2020..=2022).contains(&date.year()) + || (2073..=2077).contains(&date.year()) + { + verify_against_date_command_local(date_path, date); + } + + date += crate::Duration::hours(1); + } + } + + #[test] + fn verify_correct_offsets() { + let now = Local::now(); + let from_local = Local.from_local_datetime(&now.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&now.naive_utc()); + + assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(now, from_local); + assert_eq!(now, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_past() { + // let distant_past = Local::now() - Duration::days(365 * 100); + let distant_past = Local::now() - Duration::days(250 * 31); + let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); + + assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_past, from_local); + assert_eq!(distant_past, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_future() { + let distant_future = Local::now() + Duration::days(250 * 31); + let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); + + assert_eq!( + distant_future.offset().local_minus_utc(), + from_local.offset().local_minus_utc() + ); + assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_future, from_local); + assert_eq!(distant_future, from_utc); + } #[test] fn test_local_date_sanity_check() { From 50eb7e363df2da182839f5c0e3ff2168d10ee7d3 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 13 May 2022 21:37:31 +1000 Subject: [PATCH 068/999] handle localtime ambiguity --- src/offset/local/mod.rs | 6 +- src/offset/local/stub.rs | 8 +- src/offset/local/tz_info/rule.rs | 135 +++++++++++++++++++++++++++ src/offset/local/tz_info/timezone.rs | 128 +++++++++++++++++++++++-- src/offset/local/unix.rs | 54 ++++++++--- src/offset/local/windows.rs | 7 +- 6 files changed, 305 insertions(+), 33 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 8468c21859..cae9278bb4 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -112,7 +112,7 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - LocalResult::Single(inner::naive_to_local(local, true)) + inner::naive_to_local(local, true) } fn from_utc_date(&self, utc: &NaiveDate) -> Date { @@ -129,7 +129,9 @@ impl TimeZone for Local { #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - inner::naive_to_local(utc, false) + // this is OK to unwrap as getting local time from a UTC + // timestamp is never ambiguous + inner::naive_to_local(utc, false).unwrap() } } diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 9b8950f11e..d4d76c5a9b 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -11,7 +11,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; pub(super) fn now() -> DateTime { tm_to_datetime(Timespec::now().local()) @@ -19,7 +19,7 @@ pub(super) fn now() -> DateTime { /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { let tm = Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, @@ -49,7 +49,7 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime assert_eq!(tm.tm_nsec, 0); tm.tm_nsec = d.nanosecond() as i32; - tm_to_datetime(tm) + LocalResult::Single(tm_to_datetime(tm)) } /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. @@ -116,7 +116,7 @@ impl Timespec { /// day, and so on), also called a broken-down time value. // FIXME: use c_int instead of i32? #[repr(C)] -struct Tm { +pub(super) struct Tm { /// Seconds after the minute - [0, 60] tm_sec: i32, diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 98feb7c3aa..7e9ed7e98d 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -78,6 +78,22 @@ impl TransitionRule { } } } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type_from_local( + &self, + local_time: i64, + year: i32, + ) -> Result, Error> { + match self { + TransitionRule::Fixed(local_time_type) => { + Ok(crate::LocalResult::Single(*local_time_type)) + } + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type_from_local(local_time, year) + } + } + } } impl From for TransitionRule { @@ -211,6 +227,125 @@ impl AlternateTime { Ok(&self.std) } } + + fn find_local_time_type_from_local( + &self, + local_time: i64, + current_year: i32, + ) -> Result, Error> { + // Check if the current year is valid for the following computations + if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) { + return Err(Error::OutOfRange("out of range date time")); + } + + let dst_start_transition_start = + self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time); + let dst_start_transition_end = self.dst_start.unix_time(current_year, 0) + + i64::from(self.dst_start_time) + + i64::from(self.dst.ut_offset) + - i64::from(self.std.ut_offset); + + let dst_end_transition_start = + self.dst_end.unix_time(current_year, 0) + i64::from(self.dst_end_time); + let dst_end_transition_end = self.dst_end.unix_time(current_year, 0) + + i64::from(self.dst_end_time) + + i64::from(self.std.ut_offset) + - i64::from(self.dst.ut_offset); + + match self.std.ut_offset.cmp(&self.dst.ut_offset) { + Ordering::Equal => Ok(crate::LocalResult::Single(self.std)), + Ordering::Less => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // northern hemisphere + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_start_transition_start { + Ok(crate::LocalResult::Single(self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::LocalResult::None) + } else if local_time >= dst_start_transition_end + && local_time < dst_end_transition_end + { + Ok(crate::LocalResult::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + } else { + Ok(crate::LocalResult::Single(self.std)) + } + } else { + // southern hemisphere regular DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_end_transition_end { + Ok(crate::LocalResult::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + } else if local_time > dst_end_transition_end + && local_time < dst_start_transition_start + { + Ok(crate::LocalResult::Single(self.std)) + } else if local_time >= dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::LocalResult::None) + } else { + Ok(crate::LocalResult::Single(self.dst)) + } + } + } + Ordering::Greater => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // southern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_start_transition_end { + Ok(crate::LocalResult::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_end_transition_start + { + Ok(crate::LocalResult::Single(self.dst)) + } else if local_time >= dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::LocalResult::None) + } else { + Ok(crate::LocalResult::Single(self.std)) + } + } else { + // northern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_end_transition_start { + Ok(crate::LocalResult::Single(self.dst)) + } else if local_time > dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::LocalResult::None) + } else if local_time >= dst_end_transition_end + && local_time < dst_start_transition_end + { + Ok(crate::LocalResult::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + } else { + Ok(crate::LocalResult::Single(self.dst)) + } + } + } + } + } } /// Parse time zone name diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 3266b05b73..6401fdec33 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -3,7 +3,7 @@ use std::fs::{self, File}; use std::io::{self, Read}; use std::path::{Path, PathBuf}; -use std::{fmt, str}; +use std::{cmp::Ordering, fmt, str}; use super::rule::{AlternateTime, TransitionRule}; use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; @@ -27,7 +27,11 @@ impl TimeZone { /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. /// pub(crate) fn local() -> Result { - Self::from_posix_tz("localtime") + if let Ok(tz) = std::env::var("TZ") { + Self::from_posix_tz(&tz) + } else { + Self::from_posix_tz("localtime") + } } /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). @@ -114,6 +118,15 @@ impl TimeZone { self.as_ref().find_local_time_type(unix_time) } + // should we pass NaiveDateTime all the way through to this fn? + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: i64, + year: i32, + ) -> Result, Error> { + self.as_ref().find_local_time_type_from_local(local_time, year) + } + /// Returns a reference to the time zone fn as_ref(&self) -> TimeZoneRef { TimeZoneRef { @@ -188,6 +201,91 @@ impl<'a> TimeZoneRef<'a> { } } + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: i64, + year: i32, + ) -> Result, Error> { + // #TODO: this is wrong as we need 'local_time_to_local_leap_time ? + // but ... does the local time even include leap seconds ?? + // let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) { + // Ok(unix_leap_time) => unix_leap_time, + // Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + // Err(err) => return Err(err), + // }; + let local_leap_time = local_time; + + // if we have at least one transition, + // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions + if !self.transitions.is_empty() { + let mut prev = Some(self.local_time_types[0]); + + for transition in self.transitions { + let after_ltt = self.local_time_types[transition.local_time_type_index]; + + // the end and start here refers to where the time starts prior to the transition + // and where it ends up after. not the temporal relationship. + let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset); + let transition_start = + transition.unix_leap_time + i64::from(prev.unwrap().ut_offset); + + match transition_start.cmp(&transition_end) { + Ordering::Greater => { + // bakwards transition, eg from DST to regular + // this means a given local time could have one of two possible offsets + if local_leap_time < transition_end { + return Ok(crate::LocalResult::Single(prev.unwrap())); + } else if local_leap_time >= transition_end + && local_leap_time <= transition_start + { + if prev.unwrap().ut_offset < after_ltt.ut_offset { + return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt)); + } else { + return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap())); + } + } + } + Ordering::Equal => { + // should this ever happen? presumably we have to handle it anyway. + if local_leap_time < transition_start { + return Ok(crate::LocalResult::Single(prev.unwrap())); + } else if local_leap_time == transition_end { + if prev.unwrap().ut_offset < after_ltt.ut_offset { + return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt)); + } else { + return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap())); + } + } + } + Ordering::Less => { + // forwards transition, eg from regular to DST + // this means that times that are skipped are invalid local times + if local_leap_time <= transition_start { + return Ok(crate::LocalResult::Single(prev.unwrap())); + } else if local_leap_time < transition_end { + return Ok(crate::LocalResult::None); + } else if local_leap_time == transition_end { + return Ok(crate::LocalResult::Single(after_ltt)); + } + } + } + + // try the next transition, we are fully after this one + prev = Some(after_ltt); + } + }; + + if let Some(extra_rule) = self.extra_rule { + match extra_rule.find_local_time_type_from_local(local_time, year) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } else { + Ok(crate::LocalResult::Single(self.local_time_types[0])) + } + } + /// Check time zone inputs fn validate(&self) -> Result<(), Error> { // Check local time types @@ -710,14 +808,24 @@ mod tests { fn test_time_zone_from_posix_tz() -> Result<(), Error> { #[cfg(unix)] { - let time_zone_local = TimeZone::local()?; - let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?; - let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?; - let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?; - - assert_eq!(time_zone_local, time_zone_local_1); - assert_eq!(time_zone_local, time_zone_local_2); - assert_eq!(time_zone_local, time_zone_local_3); + // if the TZ var is set, this essentially _overrides_ the + // time set by the localtime symlink + // so just ensure that ::local() acts as expected + // in this case + if let Ok(tz) = std::env::var("TZ") { + let time_zone_local = TimeZone::local()?; + let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; + assert_eq!(time_zone_local, time_zone_local_1); + } else { + let time_zone_local = TimeZone::local()?; + let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?; + let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?; + let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?; + + assert_eq!(time_zone_local, time_zone_local_1); + assert_eq!(time_zone_local, time_zone_local_2); + assert_eq!(time_zone_local, time_zone_local_3); + } let time_zone_utc = TimeZone::from_posix_tz("UTC")?; assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 0cd5406a44..959b8e0267 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,23 +12,31 @@ use std::sync::Once; use super::tz_info::TimeZone; use super::{DateTime, FixedOffset, Local, NaiveDateTime}; -use crate::Utc; +use crate::{Datelike, LocalResult, Utc}; pub(super) fn now() -> DateTime { - let now = Utc::now(); - DateTime::from_utc(now.naive_utc(), offset(now.timestamp())) + let now = Utc::now().naive_utc(); + DateTime::from_utc(now, offset(now, false).unwrap()) } -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { - let offset = match local { - true => offset(d.timestamp()), - false => FixedOffset::east(0), - }; - - DateTime::from_utc(*d - offset, offset) +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { + if local { + match offset(*d, true) { + LocalResult::None => LocalResult::None, + LocalResult::Ambiguous(early, late) => LocalResult::Ambiguous( + DateTime::from_utc(*d - early, early), + DateTime::from_utc(*d - late, late), + ), + LocalResult::Single(offset) => { + LocalResult::Single(DateTime::from_utc(*d - offset, offset)) + } + } + } else { + LocalResult::Single(DateTime::from_utc(*d, offset(*d, false).unwrap())) + } } -fn offset(unix: i64) -> FixedOffset { +fn offset(d: NaiveDateTime, local: bool) -> LocalResult { let info = unsafe { INIT.call_once(|| { INFO = Some(TimeZone::local().expect("unable to parse localtime info")); @@ -36,9 +44,27 @@ fn offset(unix: i64) -> FixedOffset { INFO.as_ref().unwrap() }; - FixedOffset::east( - info.find_local_time_type(unix).expect("unable to select local time type").offset(), - ) + if local { + // we pass through the year as the year of a local point in time must either be valid in that locale, or + // the entire time was skipped in which case we will return LocalResult::None anywa. + match info + .find_local_time_type_from_local(d.timestamp(), d.year()) + .expect("unable to select local time type") + { + LocalResult::None => LocalResult::None, + LocalResult::Ambiguous(early, late) => LocalResult::Ambiguous( + FixedOffset::east(early.offset()), + FixedOffset::east(late.offset()), + ), + LocalResult::Single(tt) => LocalResult::Single(FixedOffset::east(tt.offset())), + } + } else { + LocalResult::Single(FixedOffset::east( + info.find_local_time_type(d.timestamp()) + .expect("unable to select local time type") + .offset(), + )) + } } static mut INFO: Option = None; diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 29e75cf048..b4c0ee675f 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -17,14 +17,14 @@ use winapi::um::minwinbase::SYSTEMTIME; use winapi::um::timezoneapi::*; use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; pub(super) fn now() -> DateTime { tm_to_datetime(Timespec::now().local()) } /// Converts a local `NaiveDateTime` to the `time::Timespec`. -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime { +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { let tm = Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, @@ -54,7 +54,8 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime assert_eq!(tm.tm_nsec, 0); tm.tm_nsec = d.nanosecond() as i32; - tm_to_datetime(tm) + // #TODO - there should be ambiguous cases, investigate? + LocalResult::Single(tm_to_datetime(tm)) } /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. From ad03bcbdcb27c7010c21fca0f8a3440b69e994fc Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 26 May 2022 15:28:24 -0400 Subject: [PATCH 069/999] Check for overflow when parsing datetime --- src/datetime/tests.rs | 1 + src/format/parsed.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3b46f3f1bf..fbf959e10d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -126,6 +126,7 @@ fn test_datetime_rfc2822_and_rfc3339() { DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), Ok(edt.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) ); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 011ad889aa..4665fcaf7c 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -626,6 +626,12 @@ impl Parsed { let offset = self.offset.ok_or(NOT_ENOUGH)?; let datetime = self.to_naive_datetime_with_offset(offset)?; let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + + // this is used to prevent an overflow when calling FixedOffset::from_local_datetime + datetime + .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) + .ok_or(OUT_OF_RANGE)?; + match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), From 150f6d103620640704dc5d55f59a51d0b8ae5c54 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 26 May 2022 15:28:59 -0400 Subject: [PATCH 070/999] Added the change and my username to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe69ed4ab6..7a26d3f836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add support for optional timestamps serde serialization for `NaiveDateTime`. * Fix build for wasm32-unknown-emscripten (@yu-re-ka #593) * Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` +* Fix panicking when parsing a `DateTime` (@botahamec) ## 0.4.19 From d7c743e47adfdcf653ed612682c06a9622db61bd Mon Sep 17 00:00:00 2001 From: Bryan Henry Date: Thu, 9 Jun 2022 02:34:09 -0700 Subject: [PATCH 071/999] Make ParseErrorKind public and available through ParseError::kind() (#588) Co-authored-by: Dirkjan Ochtman Co-authored-by: Erlend Langseth <3rlendhl@gmail.com> --- CHANGELOG.md | 1 + src/format/mod.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a26d3f836..7562212808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add support for microseconds timestamps serde serialization for `NaiveDateTime`. * Add support for optional timestamps serde serialization for `NaiveDateTime`. * Fix build for wasm32-unknown-emscripten (@yu-re-ka #593) +* Make `ParseErrorKind` public and available through `ParseError::kind()` (#588) * Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` * Fix panicking when parsing a `DateTime` (@botahamec) diff --git a/src/format/mod.rs b/src/format/mod.rs index 893496a035..2089c4c06a 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -336,9 +336,16 @@ macro_rules! internal_fix { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct ParseError(ParseErrorKind); +impl ParseError { + /// The category of parse error + pub fn kind(&self) -> ParseErrorKind { + self.0 + } +} + /// The category of parse error #[derive(Debug, Clone, PartialEq, Eq, Copy)] -enum ParseErrorKind { +pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, @@ -366,6 +373,10 @@ enum ParseErrorKind { /// There was an error on the formatting string, or there were non-supported formating items. BadFormat, + + // TODO: Change this to `#[non_exhaustive]` (on the enum) when MSRV is increased + #[doc(hidden)] + __Nonexhaustive, } /// Same as `Result`. @@ -381,6 +392,7 @@ impl fmt::Display for ParseError { ParseErrorKind::TooShort => write!(f, "premature end of input"), ParseErrorKind::TooLong => write!(f, "trailing input"), ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"), + _ => unreachable!(), } } } From 3c0d274aaa9d9f4a39ab18d8ea501c62d1ffdde8 Mon Sep 17 00:00:00 2001 From: 0xc0001a2040 <38496922+MrGunflame@users.noreply.github.com> Date: Sat, 11 Jun 2022 07:55:34 +0200 Subject: [PATCH 072/999] Add AddAssign/SubAssign implementation to DateTime/Date (#698) * Impl AddAssign and SubAssign for Date and DateTime * Add tests for AddAssign and SubAssign * Add tests for AddAssign and SubAssign on Date * AddAssign/SubAssign impls mimic their checked methods * Gate tests for Local timezone behind clock feature * make sure add/sub_assign_local tests cross a DST transition if relevant Co-authored-by: Eric Sheppard --- src/date.rs | 92 ++++++++++++++++++++++++++++++++++++++++++- src/datetime/mod.rs | 22 ++++++++++- src/datetime/tests.rs | 74 ++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 3 deletions(-) diff --git a/src/date.rs b/src/date.rs index 0c9b1eef4f..4f08c33887 100644 --- a/src/date.rs +++ b/src/date.rs @@ -6,7 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash}; #[cfg(feature = "rkyv")] @@ -479,6 +479,13 @@ impl Add for Date { } } +impl AddAssign for Date { + #[inline] + fn add_assign(&mut self, rhs: OldDuration) { + self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed"); + } +} + impl Sub for Date { type Output = Date; @@ -488,6 +495,13 @@ impl Sub for Date { } } +impl SubAssign for Date { + #[inline] + fn sub_assign(&mut self, rhs: OldDuration) { + self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed"); + } +} + impl Sub> for Date { type Output = OldDuration; @@ -514,7 +528,13 @@ where #[cfg(test)] mod tests { - use crate::{Duration, Utc}; + use super::Date; + + use crate::oldtime::Duration; + use crate::{FixedOffset, NaiveDate, Utc}; + + #[cfg(feature = "clock")] + use crate::offset::{Local, TimeZone}; #[test] #[cfg(feature = "clock")] @@ -533,4 +553,72 @@ mod tests { let future = Utc::today() + Duration::weeks(12); assert_eq!(Utc::today().years_since(future), None); } + + #[test] + fn test_date_add_assign() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let date = Date::::from_utc(naivedate, Utc); + let mut date_add = date; + + date_add += Duration::days(5); + assert_eq!(date_add, date + Duration::days(5)); + + let timezone = FixedOffset::east(60 * 60); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + Duration::days(5)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + Duration::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_add_assign_local() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + + let date = Local.from_utc_date(&naivedate); + let mut date_add = date; + + date_add += Duration::days(5); + assert_eq!(date_add, date + Duration::days(5)); + } + + #[test] + fn test_date_sub_assign() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let date = Date::::from_utc(naivedate, Utc); + let mut date_sub = date; + + date_sub -= Duration::days(5); + assert_eq!(date_sub, date - Duration::days(5)); + + let timezone = FixedOffset::east(60 * 60); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - Duration::days(5)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - Duration::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_sub_assign_local() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + + let date = Local.from_utc_date(&naivedate); + let mut date_sub = date; + + date_sub -= Duration::days(5); + assert_eq!(date_sub, date - Duration::days(5)); + } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index cc902646e9..6af2abb64a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -11,7 +11,7 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; @@ -852,6 +852,16 @@ impl Add for DateTime { } } +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: OldDuration) { + let datetime = + self.datetime.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime); + } +} + impl Sub for DateTime { type Output = DateTime; @@ -861,6 +871,16 @@ impl Sub for DateTime { } } +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: OldDuration) { + let datetime = + self.datetime.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime) + } +} + impl Sub> for DateTime { type Output = OldDuration; diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index fbf959e10d..d9b11757c8 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -426,3 +426,77 @@ fn test_years_elapsed() { let future = Utc::today() + Duration::weeks(12); assert_eq!(Utc::today().years_since(future), None); } + +#[test] +fn test_datetime_add_assign() { + let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_add = datetime; + + datetime_add += Duration::seconds(60); + assert_eq!(datetime_add, datetime + Duration::seconds(60)); + + let timezone = FixedOffset::east(60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + Duration::seconds(60)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + Duration::seconds(60)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_add_assign_local() { + let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_add = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_add += Duration::days(1); + assert_eq!(datetime_add, datetime + Duration::days(i)) + } +} + +#[test] +fn test_datetime_sub_assign() { + let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(12, 0, 0); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_sub = datetime; + + datetime_sub -= Duration::minutes(90); + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + + let timezone = FixedOffset::east(60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_sub_assign_local() { + let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_sub -= Duration::days(1); + assert_eq!(datetime_sub, datetime - Duration::days(i)) + } +} From 8dd7245f85d230aeb5216f934d3385376f27bbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Estrella?= <2049686+sestrella@users.noreply.github.com> Date: Wed, 15 Jun 2022 07:01:18 -0500 Subject: [PATCH 073/999] [#654] Get week start and end days (#666) Co-authored-by: David Mazarro Co-authored-by: Jorge Guerra --- .gitignore | 1 + CHANGELOG.md | 1 + src/lib.rs | 2 +- src/naive/date.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++- src/naive/mod.rs | 2 +- 5 files changed, 98 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index a9d37c560c..9fac28caba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.tool-versions diff --git a/CHANGELOG.md b/CHANGELOG.md index 7562212808..f6a1a3bd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Versions with only mechanical changes will be omitted from the following list. * Make `ParseErrorKind` public and available through `ParseError::kind()` (#588) * Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` * Fix panicking when parsing a `DateTime` (@botahamec) +* Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) ## 0.4.19 diff --git a/src/lib.rs b/src/lib.rs index bf193b8a12..1bd4bb937a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -487,7 +487,7 @@ pub use format::{ParseError, ParseResult}; pub mod naive; #[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] diff --git a/src/naive/date.rs b/src/naive/date.rs index 08c6869323..3e566b75aa 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -5,7 +5,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; use num_integer::div_mod_floor; @@ -19,7 +19,7 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Weekday}; +use crate::{Datelike, Duration, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -50,6 +50,70 @@ const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_0 #[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)] +pub struct NaiveWeek { + date: NaiveDate, + start: Weekday, +} + +impl NaiveWeek { + /// Returns a date representing the first day of the week. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// assert!(week.first_day() <= date); + /// ``` + #[inline] + pub fn first_day(&self) -> NaiveDate { + let start = self.start.num_days_from_monday(); + let end = self.date.weekday().num_days_from_monday(); + let days = if start > end { 7 - start + end } else { end - start }; + self.date - Duration::days(days.into()) + } + + /// Returns a date representing the last day of the week. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// assert!(week.last_day() >= date); + /// ``` + #[inline] + pub fn last_day(&self) -> NaiveDate { + self.first_day() + Duration::days(6) + } + + /// Returns a [`RangeInclusive`] representing the whole week bounded by + /// [first_day](./struct.NaiveWeek.html#method.first_day) and + /// [last_day](./struct.NaiveWeek.html#method.last_day) functions. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// let days = week.days(); + /// assert!(days.contains(&date)); + /// ``` + #[inline] + pub fn days(&self) -> RangeInclusive { + self.first_day()..=self.last_day() + } +} + /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date](#calendar-date) /// from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -1105,6 +1169,13 @@ impl NaiveDate { pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { NaiveDateWeeksIterator { value: *self } } + + /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] + /// specified. + #[inline] + pub fn week(&self, start: Weekday) -> NaiveWeek { + NaiveWeek { date: *self, start } + } } impl Datelike for NaiveDate { @@ -2445,4 +2516,25 @@ mod tests { assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 2); } + + #[test] + fn test_naiveweek() { + let date = NaiveDate::from_ymd(2022, 5, 18); + let asserts = vec![ + (Weekday::Mon, "2022-05-16", "2022-05-22"), + (Weekday::Tue, "2022-05-17", "2022-05-23"), + (Weekday::Wed, "2022-05-18", "2022-05-24"), + (Weekday::Thu, "2022-05-12", "2022-05-18"), + (Weekday::Fri, "2022-05-13", "2022-05-19"), + (Weekday::Sat, "2022-05-14", "2022-05-20"), + (Weekday::Sun, "2022-05-15", "2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%Y-%m-%d")); + assert!(days.contains(&date)); + } + } } diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f23a731a76..171cac36bb 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -10,7 +10,7 @@ mod internals; mod isoweek; mod time; -pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; +pub use self::date::{NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] #[allow(deprecated)] pub use self::datetime::rustc_serialize::TsSeconds; From 13e1d483657a2e4d0f06a8692432cb90c4e98e9a Mon Sep 17 00:00:00 2001 From: metent Date: Fri, 17 Jun 2022 06:25:18 +0530 Subject: [PATCH 074/999] Remove unused libc dependency from Cargo.toml (#710) * Remove unused libc dependency from Cargo.toml * Add libc feature to prevent potentially breaking change * Document libc dependency removal --- CHANGELOG.md | 1 + Cargo.toml | 4 ++-- README.md | 4 ++-- src/lib.rs | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a1a3bd19..b29520ef99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Versions with only mechanical changes will be omitted from the following list. * Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` * Fix panicking when parsing a `DateTime` (@botahamec) * Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) +* Remove libc dependency from Cargo.toml. ## 0.4.19 diff --git a/Cargo.toml b/Cargo.toml index 9e2370d071..2f749fcc0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,9 @@ name = "chrono" [features] default = ["clock", "std", "oldtime"] alloc = [] +libc = [] std = [] -clock = ["libc", "std", "winapi"] +clock = ["std", "winapi"] oldtime = ["time"] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] @@ -31,7 +32,6 @@ __internal_bench = ["criterion"] __doctest = [] [dependencies] -libc = { version = "0.2.69", optional = true } time = { version = "0.1.43", optional = true } num-integer = { version = "0.1.36", default-features = false } num-traits = { version = "0.2", default-features = false } diff --git a/README.md b/README.md index 8ae4aa08e7..59d4eb7139 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ Default features: - `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` and adds interoperation with standard library types and traits. -- `clock`: enables reading the system time (`now`), independent of whether - `std::time::SystemTime` is present, depends on having a libc. +- `clock`: Enables reading the system time (`now`) that depends on the standard library for +UNIX-like operating systems and the Windows API (`winapi`) for Windows. Optional features: diff --git a/src/lib.rs b/src/lib.rs index 1bd4bb937a..b68a25bead 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,8 +42,8 @@ //! - `std`: Enables functionality that depends on the standard library. This //! is a superset of `alloc` and adds interoperation with standard library types //! and traits. -//! - `clock`: enables reading the system time (`now`), independent of whether -//! `std::time::SystemTime` is present, depends on having a libc. +//! - `clock`: Enables reading the system time (`now`) that depends on the standard library for +//! UNIX-like operating systems and the Windows API (`winapi`) for Windows. //! //! Optional features: //! From cb9239828a07c868a5e1fcd0414d559b61163249 Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 19 Jun 2022 08:10:17 -0400 Subject: [PATCH 075/999] `NaiveDateTime` to `DateTime` helper method (#711) * Added a and_timezone method * Added a couple of tests * Added the change to the changelog * Wrong name, silly * Fixed formatting * Changed the method name * Added a disclaimer to the documentation --- CHANGELOG.md | 1 + src/naive/datetime/mod.rs | 18 +++++++++++++++++- src/naive/datetime/tests.rs | 15 ++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b29520ef99..dc3a65642b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Versions with only mechanical changes will be omitted from the following list. * Fix panicking when parsing a `DateTime` (@botahamec) * Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) * Remove libc dependency from Cargo.toml. +* Add the `and_local_timezone` method to `NaiveDateTime` ## 0.4.19 diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 8b28692c42..e2119e002d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -21,7 +21,7 @@ use crate::naive::date::{MAX_DATE, MIN_DATE}; use crate::naive::time::{MAX_TIME, MIN_TIME}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Timelike, Weekday}; +use crate::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Weekday}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -722,6 +722,22 @@ impl NaiveDateTime { pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } + + /// Converts the `NaiveDateTime` into the timezone-aware `DateTime` + /// with the provided timezone, if possible. + /// + /// This is experimental and might be removed in the future. Feel free to + /// let us know what you think about this API. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4).and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.timezone(), Utc); + pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { + tz.from_local_datetime(self) + } } impl Datelike for NaiveDateTime { diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index ea5f91790d..01887332c8 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,7 +1,7 @@ use super::NaiveDateTime; use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE}; use crate::oldtime::Duration; -use crate::Datelike; +use crate::{Datelike, FixedOffset, Utc}; use std::i64; #[test] @@ -240,3 +240,16 @@ fn test_nanosecond_range() { NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) ); } + +#[test] +fn test_and_timezone() { + let ndt = NaiveDate::from_ymd(2022, 6, 15).and_hms(18, 59, 36); + let dt_utc = ndt.and_local_timezone(Utc).unwrap(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); + + let offset_tz = FixedOffset::west(4 * 3600); + let dt_offset = ndt.and_local_timezone(offset_tz).unwrap(); + assert_eq!(dt_offset.naive_local(), ndt); + assert_eq!(dt_offset.timezone(), offset_tz); +} From b1d74aef688c27fccc738c64746535905903471a Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 21 Jun 2022 22:15:47 +1000 Subject: [PATCH 076/999] remove experimental and clarify why function is fallible --- src/naive/datetime/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e2119e002d..8bb46c89e7 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -726,8 +726,13 @@ impl NaiveDateTime { /// Converts the `NaiveDateTime` into the timezone-aware `DateTime` /// with the provided timezone, if possible. /// - /// This is experimental and might be removed in the future. Feel free to - /// let us know what you think about this API. + /// This can fail in cases where the local time represented by the `NaiveDateTime` + /// is not a valid local timestamp in the target timezone due to an offset transition + /// for example if the target timezone had a change from +00:00 to +01:00 + /// occuring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 + /// could never occur. Similarly, if the offset transitioned in the opposite direction + /// then there would be two local times of 2015-09-05 23:56:04, one at +00:00 and one + /// at +01:00. /// /// # Example /// From 3ea7149bc76ef90687ee39c6a55a6844c7844b54 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 12 Apr 2022 10:33:01 +0200 Subject: [PATCH 077/999] Bump version to 0.4.20-beta.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2f749fcc0b..64d3d06e0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.19" +version = "0.4.20-beta.1" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 39ac80a6a51b2a5081750c451feb261c1cb43960 Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 19 Jun 2022 18:36:59 -0400 Subject: [PATCH 078/999] Removed a couple of zero-prefixed literals --- benches/chrono.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index cc88378eb7..c44a2a15b6 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -36,13 +36,13 @@ fn bench_datetime_from_str(c: &mut Criterion) { fn bench_datetime_to_rfc2822(c: &mut Criterion) { let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); + let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); } fn bench_datetime_to_rfc3339(c: &mut Criterion) { let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); + let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); } From 3550128fa096d08057b98e5d808eee6bcd196ded Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 4 Jul 2022 13:43:06 +0200 Subject: [PATCH 079/999] Prefer associated consts for MIN and MAX values --- src/date.rs | 15 ++++++-- src/datetime/mod.rs | 13 +++++-- src/format/parsed.rs | 12 +++--- src/lib.rs | 2 + src/naive/date.rs | 76 +++++++++++++++++++------------------ src/naive/datetime/mod.rs | 23 ++++++----- src/naive/datetime/tests.rs | 12 +++--- src/naive/isoweek.rs | 10 ++--- src/naive/mod.rs | 2 + src/naive/time/mod.rs | 7 ++-- src/traits.rs | 3 +- 11 files changed, 100 insertions(+), 75 deletions(-) diff --git a/src/date.rs b/src/date.rs index 4f08c33887..6d6a6a6f85 100644 --- a/src/date.rs +++ b/src/date.rs @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::format::Locale; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::{DelayedFormat, Item, StrftimeItems}; -use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime}; +use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; @@ -61,9 +61,13 @@ pub struct Date { } /// The minimum possible `Date`. -pub const MIN_DATE: Date = Date { date: naive::MIN_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] +pub const MIN_DATE: Date = Date::::MIN_UTC; /// The maximum possible `Date`. -pub const MAX_DATE: Date = Date { date: naive::MAX_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] +pub const MAX_DATE: Date = Date::::MAX_UTC; impl Date { /// Makes a new `Date` with given *UTC* date and offset. @@ -288,6 +292,11 @@ impl Date { false => None, } } + + /// The minimum possible `Date`. + pub const MIN_UTC: Date = Date { date: NaiveDate::MIN, offset: Utc }; + /// The maximum possible `Date`. + pub const MAX_UTC: Date = Date { date: NaiveDate::MAX, offset: Utc }; } /// Maps the local date to other date with given conversion function. diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 6af2abb64a..f814b23180 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -24,7 +24,7 @@ use crate::format::DelayedFormat; use crate::format::Locale; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item}; -use crate::naive::{self, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +use crate::naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; @@ -89,9 +89,11 @@ pub struct DateTime { } /// The minimum possible `DateTime`. -pub const MIN_DATETIME: DateTime = DateTime { datetime: naive::MIN_DATETIME, offset: Utc }; +#[deprecated(since = "0.4.20", note = "Use DateTime::MIN_UTC instead")] +pub const MIN_DATETIME: DateTime = DateTime::::MIN_UTC; /// The maximum possible `DateTime`. -pub const MAX_DATETIME: DateTime = DateTime { datetime: naive::MAX_DATETIME, offset: Utc }; +#[deprecated(since = "0.4.20", note = "Use DateTime::MAX_UTC instead")] +pub const MAX_DATETIME: DateTime = DateTime::::MAX_UTC; impl DateTime { /// Makes a new `DateTime` with given *UTC* datetime and offset. @@ -372,6 +374,11 @@ impl DateTime { false => None, } } + + /// The minimum possible `DateTime`. + pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; + /// The maximum possible `DateTime`. + pub const MAX_UTC: DateTime = DateTime { datetime: NaiveDateTime::MAX, offset: Utc }; } impl Default for DateTime { diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 4665fcaf7c..3a72cb3d59 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -698,7 +698,7 @@ impl Parsed { mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; - use crate::naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE}; + use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Datelike; use crate::Weekday::*; @@ -828,7 +828,7 @@ mod tests { assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE)); - let max_year = MAX_DATE.year(); + let max_year = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, year_mod_100: max_year % 100, month: 1, day: 1), @@ -1081,14 +1081,14 @@ mod tests { // more timestamps let max_days_from_year_1970 = - MAX_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); let year_0_from_year_1970 = NaiveDate::from_ymd(0, 1, 1).signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); let min_days_from_year_1970 = - MIN_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); assert_eq!( parse!(timestamp: min_days_from_year_1970.num_seconds()), - ymdhms(MIN_DATE.year(), 1, 1, 0, 0, 0) + ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: year_0_from_year_1970.num_seconds()), @@ -1096,7 +1096,7 @@ mod tests { ); assert_eq!( parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), - ymdhms(MAX_DATE.year(), 12, 31, 23, 59, 59) + ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); // leap seconds #1: partial fields diff --git a/src/lib.rs b/src/lib.rs index b68a25bead..5395a3aecd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -472,11 +472,13 @@ macro_rules! try_opt { } mod date; +#[allow(deprecated)] pub use date::{Date, MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] pub use datetime::rustc_serialize::TsSeconds; +#[allow(deprecated)] pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; diff --git a/src/naive/date.rs b/src/naive/date.rs index 3e566b75aa..1fc895d11a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -169,30 +169,32 @@ pub struct NaiveDate { } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). -pub const MIN_DATE: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MIN instead")] +pub const MIN_DATE: NaiveDate = NaiveDate::MIN; /// The maximum possible `NaiveDate` (December 31, 262143 CE). -pub const MAX_DATE: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] +pub const MAX_DATE: NaiveDate = NaiveDate::MAX; -// as it is hard to verify year flags in `MIN_DATE` and `MAX_DATE`, +// 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(MIN_YEAR, 1, 1); let calculated_max = NaiveDate::from_ymd(MAX_YEAR, 12, 31); assert!( - MIN_DATE == calculated_min, - "`MIN_DATE` should have a year flag {:?}", + NaiveDate::MIN == calculated_min, + "`NaiveDate::MIN` should have a year flag {:?}", calculated_min.of().flags() ); assert!( - MAX_DATE == calculated_max, - "`MAX_DATE` should have a year flag {:?}", + 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 = MAX_DATE.signed_duration_since(MIN_DATE).num_seconds(); + 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), @@ -887,11 +889,10 @@ impl NaiveDate { /// /// ``` /// use chrono::NaiveDate; - /// use chrono::naive::MAX_DATE; /// /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ_opt(), /// Some(NaiveDate::from_ymd(2015, 6, 4))); - /// assert_eq!(MAX_DATE.succ_opt(), None); + /// assert_eq!(NaiveDate::MAX.succ_opt(), None); /// ``` #[inline] pub fn succ_opt(&self) -> Option { @@ -924,11 +925,10 @@ impl NaiveDate { /// /// ``` /// use chrono::NaiveDate; - /// use chrono::naive::MIN_DATE; /// /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred_opt(), /// Some(NaiveDate::from_ymd(2015, 6, 2))); - /// assert_eq!(MIN_DATE.pred_opt(), None); + /// assert_eq!(NaiveDate::MIN.pred_opt(), None); /// ``` #[inline] pub fn pred_opt(&self) -> Option { @@ -943,7 +943,6 @@ impl NaiveDate { /// /// ``` /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MAX_DATE; /// /// let d = NaiveDate::from_ymd(2015, 9, 5); /// assert_eq!(d.checked_add_signed(Duration::days(40)), @@ -952,7 +951,7 @@ impl NaiveDate { /// Some(NaiveDate::from_ymd(2015, 7, 27))); /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MAX_DATE.checked_add_signed(Duration::days(1)), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let year = self.year(); @@ -975,7 +974,6 @@ impl NaiveDate { /// /// ``` /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MIN_DATE; /// /// let d = NaiveDate::from_ymd(2015, 9, 5); /// assert_eq!(d.checked_sub_signed(Duration::days(40)), @@ -984,7 +982,7 @@ impl NaiveDate { /// Some(NaiveDate::from_ymd(2015, 10, 15))); /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MIN_DATE.checked_sub_signed(Duration::days(1)), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let year = self.year(); @@ -1176,6 +1174,11 @@ impl NaiveDate { pub fn week(&self, start: Weekday) -> NaiveWeek { NaiveWeek { date: *self, start } } + + /// The minimum possible `NaiveDate` (January 1, 262145 BCE). + pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; + /// The maximum possible `NaiveDate` (December 31, 262143 CE). + pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { @@ -1640,18 +1643,18 @@ impl Iterator for NaiveDateDaysIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if self.value == MAX_DATE { + if self.value == NaiveDate::MAX { return None; } - // current < MAX_DATE from here on: + // current < NaiveDate::MAX from here on: let current = self.value; - // This can't panic because current is < MAX_DATE: + // This can't panic because current is < NaiveDate::MAX: self.value = current.succ(); Some(current) } fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_days(); + let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_days(); (exact_size as usize, Some(exact_size as usize)) } } @@ -1660,7 +1663,7 @@ impl ExactSizeIterator for NaiveDateDaysIterator {} impl DoubleEndedIterator for NaiveDateDaysIterator { fn next_back(&mut self) -> Option { - if self.value == MIN_DATE { + if self.value == NaiveDate::MIN { return None; } let current = self.value; @@ -1678,7 +1681,7 @@ impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if MAX_DATE - self.value < OldDuration::weeks(1) { + if NaiveDate::MAX - self.value < OldDuration::weeks(1) { return None; } let current = self.value; @@ -1687,7 +1690,7 @@ impl Iterator for NaiveDateWeeksIterator { } fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_weeks(); + let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_weeks(); (exact_size as usize, Some(exact_size as usize)) } } @@ -1696,7 +1699,7 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - MIN_DATE < OldDuration::weeks(1) { + if self.value - NaiveDate::MIN < OldDuration::weeks(1) { return None; } let current = self.value; @@ -1834,8 +1837,8 @@ where assert_eq!(to_string(&NaiveDate::from_ymd(2014, 7, 24)).ok(), Some(r#""2014-07-24""#.into())); assert_eq!(to_string(&NaiveDate::from_ymd(0, 1, 1)).ok(), Some(r#""0000-01-01""#.into())); assert_eq!(to_string(&NaiveDate::from_ymd(-1, 12, 31)).ok(), Some(r#""-0001-12-31""#.into())); - assert_eq!(to_string(&MIN_DATE).ok(), Some(r#""-262144-01-01""#.into())); - assert_eq!(to_string(&MAX_DATE).ok(), Some(r#""+262143-12-31""#.into())); + assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262144-01-01""#.into())); + assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262143-12-31""#.into())); } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] @@ -1852,8 +1855,8 @@ where assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); assert_eq!(from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31))); - assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(MIN_DATE)); - assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(MAX_DATE)); + assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(NaiveDate::MAX)); // bad formats assert!(from_str(r#""""#).is_err()); @@ -1994,8 +1997,7 @@ mod serde { #[cfg(test)] mod tests { use super::NaiveDate; - use super::{MAX_DATE, MAX_DAYS_FROM_YEAR_0, MAX_YEAR}; - use super::{MIN_DATE, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; + use super::{MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; use crate::oldtime::Duration; use crate::{Datelike, Weekday}; use std::{i32, u32}; @@ -2004,7 +2006,7 @@ mod tests { fn test_readme_doomsday() { use num_iter::range_inclusive; - for y in range_inclusive(MIN_DATE.year(), MAX_DATE.year()) { + for y in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { // even months let d4 = NaiveDate::from_ymd(y, 4, 4); let d6 = NaiveDate::from_ymd(y, 6, 6); @@ -2167,10 +2169,10 @@ mod tests { assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); } - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce()), Some(MIN_DATE)); - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce() - 1), None); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce()), Some(MAX_DATE)); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce() + 1), None); + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); } #[test] @@ -2296,7 +2298,7 @@ mod tests { assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(MAX_DATE.year(), 12, 31).succ_opt(), None); + assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); } #[test] @@ -2306,7 +2308,7 @@ mod tests { assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); - assert_eq!(ymd(MIN_DATE.year(), 1, 1).pred_opt(), None); + assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); } #[test] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 8bb46c89e7..1984c286a6 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -17,8 +17,6 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; -use crate::naive::date::{MAX_DATE, MIN_DATE}; -use crate::naive::time::{MAX_TIME, MIN_TIME}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Weekday}; @@ -42,9 +40,11 @@ mod tests; const MAX_SECS_BITS: usize = 44; /// The minimum possible `NaiveDateTime`. -pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime { date: MIN_DATE, time: MIN_TIME }; +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] +pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; /// The maximum possible `NaiveDateTime`. -pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime { date: MAX_DATE, time: MAX_TIME }; +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MAX instead")] +pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ISO 8601 combined date and time without timezone. /// @@ -743,6 +743,11 @@ impl NaiveDateTime { pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { tz.from_local_datetime(self) } + + /// The minimum possible `NaiveDateTime`. + pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; + /// The maximum possible `NaiveDateTime`. + pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; } impl Datelike for NaiveDateTime { @@ -1547,11 +1552,11 @@ where Some(r#""-0001-12-31T23:59:59.000000007""#.into()) ); assert_eq!( - to_string(&MIN_DATE.and_hms(0, 0, 0)).ok(), + to_string(&NaiveDate::MIN.and_hms(0, 0, 0)).ok(), Some(r#""-262144-01-01T00:00:00""#.into()) ); assert_eq!( - to_string(&MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(), + to_string(&NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(), Some(r#""+262143-12-31T23:59:60.999999999""#.into()) ); } @@ -1586,14 +1591,14 @@ where from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)) ); - assert_eq!(from_str(r#""-262144-01-01T00:00:00""#).ok(), Some(MIN_DATE.and_hms(0, 0, 0))); + assert_eq!(from_str(r#""-262144-01-01T00:00:00""#).ok(), Some(NaiveDate::MIN.and_hms(0, 0, 0))); assert_eq!( from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)) ); assert_eq!( from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)) ); // bad formats diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 01887332c8..8efc46b19b 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE}; +use crate::naive::NaiveDate; use crate::oldtime::Duration; use crate::{Datelike, FixedOffset, Utc}; use std::i64; @@ -40,18 +40,18 @@ fn test_datetime_add() { // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = MAX_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((MAX_DATE.year(), 12, 31, 0, 0, 0))); + let max_days_from_year_0 = NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86399), - Some((MAX_DATE.year(), 12, 31, 23, 59, 59)), + Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); - let min_days_from_year_0 = MIN_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((MIN_DATE.year(), 1, 1, 0, 0, 0))); + let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index dbddb03499..31d44cb602 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -146,22 +146,22 @@ impl fmt::Debug for IsoWeek { #[cfg(test)] mod tests { - use crate::naive::{internals, MAX_DATE, MIN_DATE}; + use crate::naive::{internals, NaiveDate}; use crate::Datelike; #[test] fn test_iso_week_extremes() { - let minweek = MIN_DATE.iso_week(); - let maxweek = MAX_DATE.iso_week(); + let minweek = NaiveDate::MIN.iso_week(); + let maxweek = NaiveDate::MAX.iso_week(); assert_eq!(minweek.year(), internals::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); - assert_eq!(format!("{:?}", minweek), MIN_DATE.format("%G-W%V").to_string()); + assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); - assert_eq!(format!("{:?}", maxweek), MAX_DATE.format("%G-W%V").to_string()); + assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } } diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 171cac36bb..2ddd9a0134 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -10,10 +10,12 @@ mod internals; mod isoweek; mod time; +#[allow(deprecated)] pub use self::date::{NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] #[allow(deprecated)] pub use self::datetime::rustc_serialize::TsSeconds; +#[allow(deprecated)] pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; pub use self::isoweek::IsoWeek; pub use self::time::NaiveTime; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 1bcfd03a3a..efb0ae61c0 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -28,10 +28,6 @@ mod serde; #[cfg(test)] mod tests; -pub(super) const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; -pub(super) const MAX_TIME: NaiveTime = - NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; - /// ISO 8601 time without timezone. /// Allows for the nanosecond precision and optional leap second representation. /// @@ -804,6 +800,9 @@ impl NaiveTime { let (hour, min) = div_mod_floor(mins, 60); (hour, min, sec) } + + pub(super) const MIN: Self = Self { secs: 0, frac: 0 }; + pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; } impl Timelike for NaiveTime { diff --git a/src/traits.rs b/src/traits.rs index b3905e670d..163a12852d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -180,7 +180,6 @@ pub trait Timelike: Sized { #[cfg(test)] mod tests { use super::Datelike; - use crate::naive::{MAX_DATE, MIN_DATE}; use crate::{Duration, NaiveDate}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. @@ -222,7 +221,7 @@ mod tests { use num_iter::range_inclusive; - for year in range_inclusive(MIN_DATE.year(), MAX_DATE.year()) { + for year in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { let jan1_year = NaiveDate::from_ymd(year, 1, 1); assert_eq!( jan1_year.num_days_from_ce(), From 686f72038e5cfa41c312cefa8d4569744cbff887 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 4 Jul 2022 13:51:03 +0200 Subject: [PATCH 080/999] Simplify cross installation --- .github/workflows/test.yml | 2 +- ci/install-cross.sh | 43 -------------------------------------- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 ci/install-cross.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72efc45ff8..b1a02b8ca9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -184,7 +184,7 @@ jobs: - uses: actions/checkout@v2 - name: Install cross - run: bash ci/install-cross.sh + run: cargo install cross - uses: Swatinem/rust-cache@v1 diff --git a/ci/install-cross.sh b/ci/install-cross.sh deleted file mode 100644 index b5786f1f60..0000000000 --- a/ci/install-cross.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -euxo pipefail - -main() { - local target= - if [[ "$(os)" == "ubuntu-latest" || "$(os)" == Linux ]]; then - target=x86_64-unknown-linux-musl - # shellcheck disable=SC2209 - sort=sort - else - target=x86_64-apple-darwin - sort=gsort - fi - - # This fetches latest stable release - local tag - tag=$(git ls-remote --tags --refs --exit-code https://github.com/rust-embedded/cross \ - | cut -d/ -f3 \ - | grep -E '^v[0.1.0-9.]+$' \ - | $sort --version-sort \ - | tail -n1) - - curl -LSfs https://japaric.github.io/trust/install.sh | \ - sh -s -- \ - --force \ - --git rust-embedded/cross \ - --tag "$tag" \ - --target $target - - cross --version -} - -# get an os name, either github's pre-set one or the result of uname -os() { - if [ -n "${OS_NAME:-}" ]; then - echo "$OS_NAME" - return - fi - uname -} - -main From 051e1170c41477ce162301c8711110a4577c1a23 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 7 Jul 2022 23:21:04 +1000 Subject: [PATCH 081/999] fix clippy lint --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5395a3aecd..fb71d10c61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -421,6 +421,9 @@ #![warn(unreachable_pub)] #![deny(dead_code)] #![cfg_attr(not(any(feature = "std", test)), no_std)] +// can remove this if/when rustc-serialize support is removed +// keeps clippy happy in the meantime +#![cfg_attr(feature = "rustc-serialize", allow(deprecated))] #[cfg(feature = "oldtime")] extern crate time as oldtime; From 07e4dcef00196a52779a0c159d1dda452720fdb8 Mon Sep 17 00:00:00 2001 From: Andre Braga Reis Date: Fri, 15 Jul 2022 00:56:55 +0100 Subject: [PATCH 082/999] Fix Duration::abs when nanos is not zero --- src/oldtime.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/oldtime.rs b/src/oldtime.rs index eb0c5bcf20..53ad546725 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -249,7 +249,11 @@ impl Duration { /// Returns the duration as an absolute (non-negative) value. #[inline] pub fn abs(&self) -> Duration { - Duration { secs: self.secs.abs(), nanos: self.nanos } + if self.secs < 0 && self.nanos != 0 { + Duration { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } + } else { + Duration { secs: self.secs.abs(), nanos: self.nanos } + } } /// The minimum possible `Duration`: `i64::MIN` milliseconds. @@ -610,6 +614,19 @@ mod tests { assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none()); } + #[test] + fn test_duration_abs() { + assert_eq!(Duration::milliseconds(1300).abs(), Duration::milliseconds(1300)); + assert_eq!(Duration::milliseconds(1000).abs(), Duration::milliseconds(1000)); + assert_eq!(Duration::milliseconds(300).abs(), Duration::milliseconds(300)); + assert_eq!(Duration::milliseconds(0).abs(), Duration::milliseconds(0)); + assert_eq!(Duration::milliseconds(-300).abs(), Duration::milliseconds(300)); + assert_eq!(Duration::milliseconds(-700).abs(), Duration::milliseconds(700)); + assert_eq!(Duration::milliseconds(-1000).abs(), Duration::milliseconds(1000)); + assert_eq!(Duration::milliseconds(-1300).abs(), Duration::milliseconds(1300)); + assert_eq!(Duration::milliseconds(-1700).abs(), Duration::milliseconds(1700)); + } + #[test] fn test_duration_mul() { assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); From 187819ff43e0e4da351b3ea4ac2d3076e06e8251 Mon Sep 17 00:00:00 2001 From: Andre Braga Reis Date: Thu, 14 Jul 2022 22:56:45 +0100 Subject: [PATCH 083/999] Update the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3a65642b..8ac5ebcb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) * Remove libc dependency from Cargo.toml. * Add the `and_local_timezone` method to `NaiveDateTime` +* Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos ## 0.4.19 From acd4ecf09fd0e5e35e2b5d5e074f6e1cc77172fc Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 24 Jul 2022 12:17:38 +0200 Subject: [PATCH 084/999] Add compatibility with rfc2822 comments (#733) * Add compatibility with rfc2822 comments * Refactor rfc2822 comment parser --- CHANGELOG.md | 1 + src/format/parse.rs | 26 +++++++++++++--- src/format/scan.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac5ebcb85..768011d77d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Versions with only mechanical changes will be omitted from the following list. * Remove libc dependency from Cargo.toml. * Add the `and_local_timezone` method to `NaiveDateTime` * Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos +* Add compatibility with rfc2822 comments (#733) ## 0.4.19 diff --git a/src/format/parse.rs b/src/format/parse.rs index e57fb8a9d2..f84a515dc5 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -53,7 +53,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // an adapted RFC 2822 syntax from Section 3.3 and 4.3: // - // date-time = [ day-of-week "," ] date 1*S time *S + // c-char = + // c-escape = "\" + // comment = "(" *(comment / c-char / c-escape) ")" *S + // date-time = [ day-of-week "," ] date 1*S time *S *comment // day-of-week = *S day-name *S // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year @@ -79,9 +82,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // // - we do not recognize a folding white space (FWS) or comment (CFWS). // for our purposes, instead, we accept any sequence of Unicode - // white space characters (denoted here to `S`). any actual RFC 2822 - // parser is expected to parse FWS and/or CFWS themselves and replace - // it with a single SP (`%x20`); this is legitimate. + // white space characters (denoted here to `S`). For comments, we accept + // any text within parentheses while respecting escaped parentheses. + // Any actual RFC 2822 parser is expected to parse FWS and/or CFWS themselves + // and replace it with a single SP (`%x20`); this is legitimate. // // - two-digit year < 50 should be interpreted by adding 2000. // two-digit year >= 50 or three-digit year should be interpreted @@ -145,6 +149,14 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_offset(i64::from(offset))?; } + // optional comments + s = s.trim_left(); + while let Ok((s_out, ())) = scan::comment_2822(s) { + // Trim left after every found comment, as comments are allowed to have whitespace + // between them + s = s_out.trim_left(); + } + Ok((s, ())) } @@ -817,6 +829,12 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ( + "Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\\( (a)\\(( \\t ) ) \\\\( \\) ))", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // complex trailing comment + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC\\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second diff --git a/src/format/scan.rs b/src/format/scan.rs index 856fa2f567..674ac33ba7 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -348,3 +348,76 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) } + +/// Tries to consume an RFC2822 comment +pub(super) fn comment_2822(mut s: &str) -> ParseResult<(&str, ())> { + macro_rules! next_char { + () => {{ + let c = s.bytes().nth(0).ok_or(TOO_SHORT)?; + s = &s[1..]; + c + }}; + } + + // Make sure the first letter is a `(` + if b'(' != next_char!() { + Err(INVALID)?; + } + + let mut depth = 1; // start with 1 as we already encountered a '(' + loop { + match next_char!() { + // If we encounter `\`, ignore the next character as it is escaped. + b'\\' => { + next_char!(); + } + + // If we encounter `(`, open a parantheses context. + b'(' => { + depth += 1; + } + + // If we encounter `)`, close a parentheses context. + // If all are closed, we found the end of the comment. + b')' => { + depth -= 1; + if depth == 0 { + break; + } + } + + // Ignore all other characters + _ => (), + }; + } + + Ok((s, ())) +} + +#[cfg(test)] +#[test] +fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + ("(\\)", Err(TOO_SHORT)), + ("(\\()", Ok("")), + ("(\\))", Ok("")), + ("(\\\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!(expected, actual); + } +} From 0903ab1a19426fd43a96ba7f2ed86b9fc08bf292 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Mon, 25 Jul 2022 22:12:35 +1000 Subject: [PATCH 085/999] use thread_local cache of parsed version of previous TzInfo file or TZ variable (#728) minor fixes improve ergonomics, cache mtime for 1s 1.32 fixes avoid excess Cache::default() calls add back cfg_attr --- src/offset/local/unix.rs | 142 +++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 959b8e0267..6cec6b751c 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::sync::Once; +use std::{cell::RefCell, env, fs, time::SystemTime}; use super::tz_info::TimeZone; use super::{DateTime, FixedOffset, Local, NaiveDateTime}; @@ -16,56 +16,126 @@ use crate::{Datelike, LocalResult, Utc}; pub(super) fn now() -> DateTime { let now = Utc::now().naive_utc(); - DateTime::from_utc(now, offset(now, false).unwrap()) + naive_to_local(&now, false).unwrap() } pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { - if local { - match offset(*d, true) { - LocalResult::None => LocalResult::None, - LocalResult::Ambiguous(early, late) => LocalResult::Ambiguous( - DateTime::from_utc(*d - early, early), - DateTime::from_utc(*d - late, late), - ), - LocalResult::Single(offset) => { - LocalResult::Single(DateTime::from_utc(*d - offset, offset)) + TZ_INFO.with(|maybe_cache| { + maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) + }) +} + +// we have to store the `Cache` in an option as it can't +// be initalized in a static context. +thread_local! { + static TZ_INFO: RefCell> = Default::default(); +} + +enum Source { + LocalTime { mtime: SystemTime, last_checked: SystemTime }, + // we don't bother storing the contents of the environment variable in this case. + // changing the environment while the process is running is generally not reccomended + Environment, +} + +impl Default for Source { + fn default() -> Source { + // use of var_os avoids allocating, which is nice + // as we are only going to discard the string anyway + // but we must ensure the contents are valid unicode + // otherwise the behaivour here would be different + // to that in `naive_to_local` + match env::var_os("TZ") { + Some(ref s) if s.to_str().is_some() => Source::Environment, + Some(_) | None => Source::LocalTime { + mtime: fs::symlink_metadata("/etc/localtime") + .expect("localtime should exist") + .modified() + .unwrap(), + last_checked: SystemTime::now(), + }, + } + } +} + +impl Source { + fn out_of_date(&mut self) -> bool { + let now = SystemTime::now(); + let prev = match self { + Source::LocalTime { mtime, last_checked } => match now.duration_since(*last_checked) { + Ok(d) if d.as_secs() < 1 => return false, + Ok(_) | Err(_) => *mtime, + }, + Source::Environment => return false, + }; + + match Source::default() { + Source::LocalTime { mtime, .. } => { + *self = Source::LocalTime { mtime, last_checked: now }; + prev != mtime + } + // will only reach here if TZ has been set while + // the process is running + Source::Environment => { + *self = Source::Environment; + true } } - } else { - LocalResult::Single(DateTime::from_utc(*d, offset(*d, false).unwrap())) } } -fn offset(d: NaiveDateTime, local: bool) -> LocalResult { - let info = unsafe { - INIT.call_once(|| { - INFO = Some(TimeZone::local().expect("unable to parse localtime info")); - }); - INFO.as_ref().unwrap() - }; +struct Cache { + zone: TimeZone, + source: Source, +} + +impl Default for Cache { + fn default() -> Cache { + Cache { + zone: TimeZone::local().expect("unable to parse localtime info"), + source: Source::default(), + } + } +} + +impl Cache { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult> { + if self.source.out_of_date() { + *self = Cache::default(); + } + + if !local { + let offset = FixedOffset::east( + self.zone + .find_local_time_type(d.timestamp()) + .expect("unable to select local time type") + .offset(), + ); + + return LocalResult::Single(DateTime::from_utc(d, offset)); + } - if local { // we pass through the year as the year of a local point in time must either be valid in that locale, or // the entire time was skipped in which case we will return LocalResult::None anywa. - match info + match self + .zone .find_local_time_type_from_local(d.timestamp(), d.year()) .expect("unable to select local time type") { LocalResult::None => LocalResult::None, - LocalResult::Ambiguous(early, late) => LocalResult::Ambiguous( - FixedOffset::east(early.offset()), - FixedOffset::east(late.offset()), - ), - LocalResult::Single(tt) => LocalResult::Single(FixedOffset::east(tt.offset())), + LocalResult::Ambiguous(early, late) => { + let early_offset = FixedOffset::east(early.offset()); + let late_offset = FixedOffset::east(late.offset()); + + LocalResult::Ambiguous( + DateTime::from_utc(d - early_offset, early_offset), + DateTime::from_utc(d - late_offset, late_offset), + ) + } + LocalResult::Single(tt) => { + let offset = FixedOffset::east(tt.offset()); + LocalResult::Single(DateTime::from_utc(d - offset, offset)) + } } - } else { - LocalResult::Single(FixedOffset::east( - info.find_local_time_type(d.timestamp()) - .expect("unable to select local time type") - .offset(), - )) } } - -static mut INFO: Option = None; -static INIT: Once = Once::new(); From ad1fe57f9baae0b11d4df267a57d99dae5abe2c7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 25 Jul 2022 13:39:56 +0200 Subject: [PATCH 086/999] Simplify RFC 2822 comment parser --- src/format/parse.rs | 5 +--- src/format/scan.rs | 64 +++++++++++++++++---------------------------- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index f84a515dc5..c377614f6a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -150,11 +150,8 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } // optional comments - s = s.trim_left(); while let Ok((s_out, ())) = scan::comment_2822(s) { - // Trim left after every found comment, as comments are allowed to have whitespace - // between them - s = s_out.trim_left(); + s = s_out; } Ok((s, ())) diff --git a/src/format/scan.rs b/src/format/scan.rs index 674ac33ba7..3143e7901e 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -349,49 +349,33 @@ pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) } -/// Tries to consume an RFC2822 comment -pub(super) fn comment_2822(mut s: &str) -> ParseResult<(&str, ())> { - macro_rules! next_char { - () => {{ - let c = s.bytes().nth(0).ok_or(TOO_SHORT)?; - s = &s[1..]; - c - }}; - } - - // Make sure the first letter is a `(` - if b'(' != next_char!() { - Err(INVALID)?; - } - - let mut depth = 1; // start with 1 as we already encountered a '(' - loop { - match next_char!() { - // If we encounter `\`, ignore the next character as it is escaped. - b'\\' => { - next_char!(); - } - - // If we encounter `(`, open a parantheses context. - b'(' => { - depth += 1; - } - - // If we encounter `)`, close a parentheses context. - // If all are closed, we found the end of the comment. - b')' => { - depth -= 1; - if depth == 0 { - break; - } - } - - // Ignore all other characters - _ => (), +/// Tries to consume an RFC2822 comment including preceding ` `. +/// +/// Returns the remaining string after the closing parenthesis. +pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> { + use CommentState::*; + + let mut state = Start; + for (i, c) in s.bytes().enumerate() { + state = match (state, c) { + (Start, b' ') => Start, + (Start, b'(') => Next(1), + (Next(1), b')') => return Ok((&s[i + 1..], ())), + (Next(depth), b'\\') => Escape(depth), + (Next(depth), b'(') => Next(depth + 1), + (Next(depth), b')') => Next(depth - 1), + (Next(depth), _) | (Escape(depth), _) => Next(depth), + _ => return Err(INVALID), }; } - Ok((s, ())) + Err(TOO_SHORT) +} + +enum CommentState { + Start, + Next(usize), + Escape(usize), } #[cfg(test)] From b2430c2b771bc53fd2bd94052cc91d81f31f8032 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Mon, 25 Jul 2022 22:35:20 +1000 Subject: [PATCH 087/999] change to raw strings --- src/format/parse.rs | 4 ++-- src/format/scan.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index c377614f6a..7ff5ca0c35 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -828,10 +828,10 @@ fn test_rfc2822() { ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment ( - "Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\\( (a)\\(( \\t ) ) \\\\( \\) ))", + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok("Tue, 20 Jan 2015 17:35:20 -0800"), ), // complex trailing comment - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC\\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second diff --git a/src/format/scan.rs b/src/format/scan.rs index 3143e7901e..cf139c0ad1 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -392,10 +392,10 @@ fn test_rfc2822_comments() { ("((()))", Ok("")), ("(x(x(x)x)x)", Ok("")), ("( x ( x ( x ) x ) x )", Ok("")), - ("(\\)", Err(TOO_SHORT)), - ("(\\()", Ok("")), - ("(\\))", Ok("")), - ("(\\\\)", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), ("(()())", Ok("")), ("( x ( x ) x ( x ) x )", Ok("")), ]; From 5eacc0a698d52ca29e598551a85a493b8938c65e Mon Sep 17 00:00:00 2001 From: Finomnis Date: Mon, 25 Jul 2022 16:31:17 +0200 Subject: [PATCH 088/999] Fixed incorrect whitespace parsing; added more tests --- src/format/parse.rs | 5 +++++ src/format/scan.rs | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 7ff5ca0c35..0e2db8fae9 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -832,6 +832,11 @@ fn test_rfc2822() { Ok("Tue, 20 Jan 2015 17:35:20 -0800"), ), // complex trailing comment (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second diff --git a/src/format/scan.rs b/src/format/scan.rs index cf139c0ad1..9a40903436 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -355,10 +355,11 @@ pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> { use CommentState::*; + let s = s.trim_start(); + let mut state = Start; for (i, c) in s.bytes().enumerate() { state = match (state, c) { - (Start, b' ') => Start, (Start, b'(') => Next(1), (Next(1), b')') => return Ok((&s[i + 1..], ())), (Next(depth), b'\\') => Escape(depth), @@ -383,9 +384,12 @@ enum CommentState { 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("")), @@ -402,6 +406,10 @@ fn test_rfc2822_comments() { for (test_in, expected) in testdata { let actual = comment_2822(test_in).map(|(s, _)| s); - assert_eq!(expected, actual); + assert_eq!( + expected, actual, + "{:?} expected to produce {:?}, but produced {:?}.", + test_in, expected, actual + ); } } From 26e231af33083852a0ada3f915985d5fbc654b7f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 26 Jul 2022 10:06:42 +0200 Subject: [PATCH 089/999] Bump version to 0.4.20-rc.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 64d3d06e0e..8913d90565 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.20-beta.1" +version = "0.4.20-rc.1" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 309cbeb0547c845153c3df1f2d9ce91fbf85c180 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 27 Jul 2022 15:47:12 +0930 Subject: [PATCH 090/999] Fixed typo in deprecation notice --- src/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/date.rs b/src/date.rs index 6d6a6a6f85..a8c8033b11 100644 --- a/src/date.rs +++ b/src/date.rs @@ -62,7 +62,7 @@ pub struct Date { /// The minimum possible `Date`. #[allow(deprecated)] -#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] +#[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")] pub const MIN_DATE: Date = Date::::MIN_UTC; /// The maximum possible `Date`. #[allow(deprecated)] From 353f1795b60255f44012e0c1421d857995948b17 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 27 Jul 2022 14:30:50 +0200 Subject: [PATCH 091/999] Implement std::error::Error for ParseWeekdayError --- src/weekday.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/weekday.rs b/src/weekday.rs index 58f61f2fcb..03e06570ab 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -191,6 +191,15 @@ pub struct ParseWeekdayError { pub(crate) _dummy: (), } +#[cfg(feature = "std")] +impl std::error::Error for ParseWeekdayError {} + +impl fmt::Display for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("{:?}", self)) + } +} + impl fmt::Debug for ParseWeekdayError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ParseWeekdayError {{ .. }}") From 56f80e43668c8d3087cb5333d9c5e4d796ae8a9f Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 28 Jul 2022 20:34:01 +1000 Subject: [PATCH 092/999] run on all branches and also for changes in the ci folder --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1a02b8ca9..bdbaa40fb4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,10 +4,10 @@ on: push: branches: [main] pull_request: - branches: [main] paths: - "**.rs" - .github/** + - .ci/** - Cargo.toml jobs: From 27c05589ac269596fe4b047552bc735ebe9ee44c Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 28 Jul 2022 20:45:36 +1000 Subject: [PATCH 093/999] dont use stub.rs when wasmbind feature is enabled no longer require wasmbind feature to get js-sys on wasm32-unknown-unknown target fix comment and remove wasmbind feature from tests add wasi test add wasi test --- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ CHANGELOG.md | 1 + Cargo.toml | 6 +++--- README.md | 1 - ci/github.sh | 10 ++++++++-- src/datetime/mod.rs | 6 +++--- src/lib.rs | 1 - src/offset/local/mod.rs | 29 ++++++++++++++++++++++------- src/offset/local/stub.rs | 4 ++-- src/offset/utc.rs | 9 ++++++--- 10 files changed, 69 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bdbaa40fb4..f043ab07c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -173,6 +173,30 @@ jobs: RUST_VERSION: stable WASM: wasm_unknown + wasm_wasi: + strategy: + matrix: + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-wasi + override: true + default: true + + - name: Build and Test + run: bash ci/github.sh + env: + RUST_VERSION: stable + WASM: wasm_wasi + cross-targets: strategy: matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index 768011d77d..51fd48e902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add the `and_local_timezone` method to `NaiveDateTime` * Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos * Add compatibility with rfc2822 comments (#733) +* Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery ## 0.4.19 diff --git a/Cargo.toml b/Cargo.toml index 8913d90565..12b45b0183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ libc = [] std = [] clock = ["std", "winapi"] oldtime = ["time"] -wasmbind = ["wasm-bindgen", "js-sys"] +wasmbind = [] # TODO: empty feature to avoid breaking change in 0.4.20, can be removed later unstable-locales = ["pure-rust-locales", "alloc"] __internal_bench = ["criterion"] __doctest = [] @@ -42,8 +42,8 @@ criterion = { version = "0.3", optional = true } rkyv = {version = "0.7", 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 +wasm-bindgen = { version = "0.2" } +js-sys = { version = "0.3" } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } diff --git a/README.md b/README.md index 59d4eb7139..fa7e289de5 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ UNIX-like operating systems and the Windows API (`winapi`) for Windows. Optional features: -- `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project - [`serde`][]: Enable serialization/deserialization via serde. - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. The implementation and API may change or even be diff --git a/ci/github.sh b/ci/github.sh index 6512f9a7ee..73d4b86042 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -38,6 +38,8 @@ meaningful in the github actions feature matrix UI. test_wasm_emscripten elif [[ ${WASM:-} == wasm_unknown ]]; then test_wasm_unknown + elif [[ ${WASM:-} == wasm_wasi ]]; then + test_wasm_wasi elif [[ ${CORE:-} == no_std ]]; then test_core elif [[ ${EXHAUSTIVE_TZ:-} == all_tzs ]]; then @@ -106,13 +108,13 @@ test_wasm() { } test_wasm_simple() { - if ! runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind ; then + if ! runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node ; then # sometimes on github the initial build takes 8-10 minutes, and we # check that the time makes sense inside the test by approximately # comparing it to the env var, # # so re-run the test in case it took too long - runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind + runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node fi } @@ -124,4 +126,8 @@ test_wasm_unknown() { runt cargo build --target wasm32-unknown-unknown } +test_wasm_wasi() { + runt cargo build --target wasm32-wasi +} + main "$@" diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index f814b23180..28cb16d23a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -994,21 +994,21 @@ impl From> for SystemTime { } } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { DateTime::::from(&date) } } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { Utc.timestamp_millis(date.get_time() as i64) } } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] +#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 diff --git a/src/lib.rs b/src/lib.rs index fb71d10c61..d3f3d673c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,6 @@ //! //! Optional features: //! -//! - `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project //! - [`serde`][]: Enable serialization/deserialization via serde. //! - `unstable-locales`: Enable localization. This adds various methods with a //! `_localized` suffix. The implementation and API may change or even be diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index cae9278bb4..42a3b3ff86 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -11,7 +11,13 @@ use super::{LocalResult, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{Date, DateTime}; -#[cfg(all(not(unix), not(windows)))] +// we don't want `stub.rs` when the target_os is not wasi or emscripten +// as we use js-sys to get the date instead +#[cfg(all( + not(unix), + not(windows), + not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))) +))] #[path = "stub.rs"] mod inner; @@ -51,13 +57,16 @@ impl Local { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")) + )))] pub fn now() -> DateTime { inner::now() } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] pub fn now() -> DateTime { use super::Utc; let now: DateTime = super::Utc::now(); @@ -101,7 +110,7 @@ impl TimeZone for Local { midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) } - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { let mut local = local.clone(); // Get the offset from the js runtime @@ -110,7 +119,10 @@ impl TimeZone for Local { LocalResult::Single(DateTime::from_utc(local, offset)) } - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")) + )))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { inner::naive_to_local(local, true) } @@ -120,14 +132,17 @@ impl TimeZone for Local { Date::from_utc(*utc, *midnight.offset()) } - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { // Get the offset from the js runtime let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); DateTime::from_utc(*utc, offset) } - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")) + )))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { // this is OK to unwrap as getting local time from a UTC // timestamp is never ambiguous diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index d4d76c5a9b..57e491d5c6 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -18,7 +18,7 @@ pub(super) fn now() -> DateTime { } /// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] +#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))] pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { let tm = Tm { tm_sec: d.second() as i32, @@ -54,7 +54,7 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult DateTime { if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 730c03e95f..457006116d 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -6,7 +6,7 @@ use core::fmt; #[cfg(all( feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) + not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))) ))] use std::time::{SystemTime, UNIX_EPOCH}; @@ -47,7 +47,10 @@ impl Utc { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + #[cfg(not(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", target_os = "wasi")) + )))] pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); @@ -56,7 +59,7 @@ impl Utc { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] pub fn now() -> DateTime { let now = js_sys::Date::new_0(); DateTime::::from(now) From 782f904375c145173f63c125a5fe1af10c189b7a Mon Sep 17 00:00:00 2001 From: Kevin Cox Date: Fri, 29 Jul 2022 09:40:11 -0400 Subject: [PATCH 094/999] Clarify docs for `DateTime::with_timezone` The previous docs don't really make sense, why am I calling a method if it doesn't change the value? The new wording clarifies what is held the same and what changes. --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 28cb16d23a..1e0050bd19 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -313,7 +313,7 @@ impl DateTime { } /// Changes the associated time zone. - /// This does not change the actual `DateTime` (but will change the string representation). + /// The returned `DateTime` references the same instant of time from the perspective of the provided time zone. #[inline] pub fn with_timezone(&self, tz: &Tz2) -> DateTime { tz.from_utc_datetime(&self.datetime) From ab688c384f7c797466fca69d69bfd5ee3c2cba96 Mon Sep 17 00:00:00 2001 From: Brent Gardner Date: Sat, 30 Jul 2022 06:39:13 -0600 Subject: [PATCH 095/999] Implement `Add` and `Sub` for `NaiveDate` (#731) * Add add_months() * Months struct with Add and Sub impls * Update changelog --- .gitignore | 3 ++ CHANGELOG.md | 1 + src/lib.rs | 2 +- src/month.rs | 4 ++ src/naive/date.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9fac28caba..f989181e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target Cargo.lock .tool-versions + +# for jetbrains users +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fd48e902..be98bd3e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Versions with only mechanical changes will be omitted from the following list. * Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos * Add compatibility with rfc2822 comments (#733) * Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery +* Add the `Months` struct and associated `Add` and `Sub` impls ## 0.4.19 diff --git a/src/lib.rs b/src/lib.rs index d3f3d673c3..32e2887329 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -507,7 +507,7 @@ mod weekday; pub use weekday::{ParseWeekdayError, Weekday}; mod month; -pub use month::{Month, ParseMonthError}; +pub use month::{Month, Months, ParseMonthError}; mod traits; pub use traits::{Datelike, Timelike}; diff --git a/src/month.rs b/src/month.rs index 7b642f9ed9..ce0f17280c 100644 --- a/src/month.rs +++ b/src/month.rs @@ -189,6 +189,10 @@ impl num_traits::FromPrimitive for Month { } } +/// A duration in calendar months +#[derive(Clone, Debug, PartialEq)] +pub struct Months(pub usize); + /// An error resulting from reading `` value with `FromStr`. #[derive(Clone, PartialEq)] pub struct ParseMonthError { diff --git a/src/naive/date.rs b/src/naive/date.rs index 1fc895d11a..31487fd50e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -17,6 +17,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; +use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Duration, Weekday}; @@ -596,6 +597,33 @@ impl NaiveDate { parsed.to_naive_date() } + /// Private function to calculate necessary primitives for `Add` + /// + /// # Arguments + /// + /// * `delta` - Number of months (+/-) to add + /// + /// # Returns + /// + /// A new NaiveDate on the first day of the resulting year & month + fn add_months_get_first_day(&self, delta: i32) -> NaiveDate { + let zeroed_months = self.month() as i32 - 1; // zero-based for modulo operations + let res_months = zeroed_months + delta; + let delta_years = if res_months < 0 { + if (-res_months) % 12 > 0 { + res_months / 12 - 1 + } else { + res_months / 12 + } + } else { + res_months / 12 + }; + let res_years = self.year() + delta_years; + let res_months = res_months % 12; + let res_months = if res_months < 0 { res_months + 12 } else { res_months }; + NaiveDate::from_ymd(res_years, res_months as u32 + 1, 1) + } + /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. /// /// # Example @@ -1561,6 +1589,59 @@ impl AddAssign for NaiveDate { } } +impl Add for NaiveDate { + type Output = NaiveDate; + + /// An addition of months to `NaiveDate` clamped to valid days in resulting month. + /// + /// # Example + /// + /// ``` + /// use chrono::{Duration, NaiveDate, Months}; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// assert_eq!(from_ymd(2014, 1, 1) + Months(1), from_ymd(2014, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months(11), from_ymd(2014, 12, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months(12), from_ymd(2015, 1, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months(13), from_ymd(2015, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 31) + Months(1), from_ymd(2014, 2, 28)); + /// assert_eq!(from_ymd(2020, 1, 31) + Months(1), from_ymd(2020, 2, 29)); + /// ``` + fn add(self, months: Months) -> Self::Output { + let target = self.add_months_get_first_day(months.0 as i32); + let target_plus = target.add_months_get_first_day(1); + let last_day = target_plus.sub(Duration::days(1)); + let day = core::cmp::min(self.day(), last_day.day()); + NaiveDate::from_ymd(target.year(), target.month(), day) + } +} + +impl Sub for NaiveDate { + type Output = NaiveDate; + + /// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. + /// + /// # Example + /// + /// ``` + /// use chrono::{Duration, NaiveDate, Months}; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// assert_eq!(from_ymd(2014, 1, 1) - Months(11), from_ymd(2013, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) - Months(12), from_ymd(2013, 1, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) - Months(13), from_ymd(2012, 12, 1)); + /// ``` + fn sub(self, months: Months) -> Self::Output { + let target = self.add_months_get_first_day(-(months.0 as i32)); + let target_plus = target.add_months_get_first_day(1); + let last_day = target_plus.sub(Duration::days(1)); + let day = core::cmp::min(self.day(), last_day.day()); + NaiveDate::from_ymd(target.year(), target.month(), day) + } +} + /// A subtraction of `Duration` from `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. @@ -2002,6 +2083,46 @@ mod tests { use crate::{Datelike, Weekday}; use std::{i32, u32}; + #[test] + fn test_add_months_get_first_day() { + assert_eq!( + NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(1), + NaiveDate::from_ymd(2014, 2, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(1), + NaiveDate::from_ymd(2014, 2, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(1), + NaiveDate::from_ymd(2020, 2, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(-1), + NaiveDate::from_ymd(2013, 12, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(-1), + NaiveDate::from_ymd(2013, 12, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(-1), + NaiveDate::from_ymd(2019, 12, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-11), + NaiveDate::from_ymd(2013, 2, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-12), + NaiveDate::from_ymd(2013, 1, 1) + ); + assert_eq!( + NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-13), + NaiveDate::from_ymd(2012, 12, 1) + ); + } + #[test] fn test_readme_doomsday() { use num_iter::range_inclusive; From bd3b48d1a940b92ca6c6a88682b8d41f34ff94c5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 3 Aug 2022 11:44:09 +0200 Subject: [PATCH 096/999] Revise Months API * Provide `checked_add_months()` and `checked_sub_months()` for callers that would like to avoid panics * Document panic potential in `Add` and `Sub` implementations * Implement additional traits for `Months` per API guidelines * Hide inner type for `Months` and add constructor * Use lower-level APIs to clamp day --- src/month.rs | 11 ++- src/naive/date.rs | 227 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 178 insertions(+), 60 deletions(-) diff --git a/src/month.rs b/src/month.rs index ce0f17280c..6490abfd8b 100644 --- a/src/month.rs +++ b/src/month.rs @@ -190,8 +190,15 @@ impl num_traits::FromPrimitive for Month { } /// A duration in calendar months -#[derive(Clone, Debug, PartialEq)] -pub struct Months(pub usize); +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct Months(pub(crate) u32); + +impl Months { + /// Construct a new `Months` from a number of months + pub fn new(num: u32) -> Self { + Self(num) + } +} /// An error resulting from reading `` value with `FromStr`. #[derive(Clone, PartialEq)] diff --git a/src/naive/date.rs b/src/naive/date.rs index 31487fd50e..13d8b02a5e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -597,31 +597,99 @@ impl NaiveDate { parsed.to_naive_date() } - /// Private function to calculate necessary primitives for `Add` + /// Add a duration in [`Months`] to the date /// - /// # Arguments + /// If the day would be out of range for the resulting month, use the last day for that month. /// - /// * `delta` - Number of months (+/-) to add + /// Returns `None` if the resulting date would be out of range. /// - /// # Returns + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 2, 20).checked_add_months(Months::new(6)), + /// Some(NaiveDate::from_ymd(2022, 8, 20)) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 7, 31).checked_add_months(Months::new(2)), + /// Some(NaiveDate::from_ymd(2022, 9, 30)) + /// ); + /// ``` + pub fn checked_add_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + match months.0 <= core::i32::MAX as u32 { + true => self.diff_months(months.0 as i32), + false => None, + } + } + + /// Subtract a duration in [`Months`] from the date /// - /// A new NaiveDate on the first day of the resulting year & month - fn add_months_get_first_day(&self, delta: i32) -> NaiveDate { - let zeroed_months = self.month() as i32 - 1; // zero-based for modulo operations - let res_months = zeroed_months + delta; - let delta_years = if res_months < 0 { - if (-res_months) % 12 > 0 { - res_months / 12 - 1 - } else { - res_months / 12 + /// If the day would be out of range for the resulting month, use the last day for that month. + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 2, 20).checked_sub_months(Months::new(6)), + /// Some(NaiveDate::from_ymd(2021, 8, 20)) + /// ); + /// ``` + pub fn checked_sub_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + // Copy `i32::MIN` here so we don't have to do a complicated cast + match months.0 <= 2_147_483_648 { + true => self.diff_months(-(months.0 as i32)), + false => None, + } + } + + fn diff_months(self, months: i32) -> Option { + let (years, left) = ((months / 12), (months % 12)); + + // Determine new year (without taking months into account for now + + let year = if (years > 0 && years > (MAX_YEAR - self.year())) + || (years < 0 && years < (MIN_YEAR - self.year())) + { + return None; + } else { + self.year() + years + }; + + // Determine new month + + let month = self.month() as i32 + left; + let (year, month) = if month <= 0 { + if year == MIN_YEAR { + return None; + } + + (year - 1, month + 12) + } else if month > 12 { + if year == MAX_YEAR { + return None; } + + (year + 1, month - 12) } else { - res_months / 12 + (year, month) }; - let res_years = self.year() + delta_years; - let res_months = res_months % 12; - let res_months = if res_months < 0 { res_months + 12 } else { res_months }; - NaiveDate::from_ymd(res_years, res_months as u32 + 1, 1) + + // Clamp original day in case new month is shorter + + let flags = YearFlags::from_year(year); + let feb_days = if flags.ndays() == 366 { 29 } else { 28 }; + let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let day = Ord::min(self.day(), days[(month - 1) as usize]); + + NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags)) } /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. @@ -1594,6 +1662,10 @@ impl Add for NaiveDate { /// An addition of months to `NaiveDate` clamped to valid days in resulting month. /// + /// # Panics + /// + /// Panics if the resulting date would be out of range. + /// /// # Example /// /// ``` @@ -1601,19 +1673,15 @@ impl Add for NaiveDate { /// /// let from_ymd = NaiveDate::from_ymd; /// - /// assert_eq!(from_ymd(2014, 1, 1) + Months(1), from_ymd(2014, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months(11), from_ymd(2014, 12, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months(12), from_ymd(2015, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months(13), from_ymd(2015, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 31) + Months(1), from_ymd(2014, 2, 28)); - /// assert_eq!(from_ymd(2020, 1, 31) + Months(1), from_ymd(2020, 2, 29)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); + /// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); /// ``` fn add(self, months: Months) -> Self::Output { - let target = self.add_months_get_first_day(months.0 as i32); - let target_plus = target.add_months_get_first_day(1); - let last_day = target_plus.sub(Duration::days(1)); - let day = core::cmp::min(self.day(), last_day.day()); - NaiveDate::from_ymd(target.year(), target.month(), day) + self.checked_add_months(months).unwrap() } } @@ -1622,6 +1690,10 @@ impl Sub for NaiveDate { /// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. /// + /// # Panics + /// + /// Panics if the resulting date would be out of range. + /// /// # Example /// /// ``` @@ -1629,16 +1701,12 @@ impl Sub for NaiveDate { /// /// let from_ymd = NaiveDate::from_ymd; /// - /// assert_eq!(from_ymd(2014, 1, 1) - Months(11), from_ymd(2013, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months(12), from_ymd(2013, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months(13), from_ymd(2012, 12, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); + /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); /// ``` fn sub(self, months: Months) -> Self::Output { - let target = self.add_months_get_first_day(-(months.0 as i32)); - let target_plus = target.add_months_get_first_day(1); - let last_day = target_plus.sub(Duration::days(1)); - let day = core::cmp::min(self.day(), last_day.day()); - NaiveDate::from_ymd(target.year(), target.month(), day) + self.checked_sub_months(months).unwrap() } } @@ -2077,49 +2145,92 @@ mod serde { #[cfg(test)] mod tests { - use super::NaiveDate; - use super::{MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; + use super::{ + Months, NaiveDate, MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR, + }; use crate::oldtime::Duration; use crate::{Datelike, Weekday}; use std::{i32, u32}; #[test] - fn test_add_months_get_first_day() { + fn diff_months() { + // identity + assert_eq!( + NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(0)), + Some(NaiveDate::from_ymd(2022, 8, 3)) + ); + + // add with months exceeding `i32::MAX` + assert_eq!( + NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(i32::MAX as u32 + 1)), + None + ); + + // sub with months exceeindg `i32::MIN` assert_eq!( - NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(1), - NaiveDate::from_ymd(2014, 2, 1) + NaiveDate::from_ymd(2022, 8, 3) + .checked_sub_months(Months::new((i32::MIN as i64).abs() as u32 + 1)), + None ); + + // add overflowing year + assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); + + // add underflowing year + assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); + + // sub crossing year 0 boundary assert_eq!( - NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(1), - NaiveDate::from_ymd(2014, 2, 1) + NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(2050 * 12)), + Some(NaiveDate::from_ymd(-28, 8, 3)) ); + + // add crossing year boundary assert_eq!( - NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(1), - NaiveDate::from_ymd(2020, 2, 1) + NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(6)), + Some(NaiveDate::from_ymd(2023, 2, 3)) ); + + // sub crossing year boundary assert_eq!( - NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(-1), - NaiveDate::from_ymd(2013, 12, 1) + NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd(2021, 10, 3)) ); + + // add clamping day, non-leap year assert_eq!( - NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(-1), - NaiveDate::from_ymd(2013, 12, 1) + NaiveDate::from_ymd(2022, 1, 29).checked_add_months(Months::new(1)), + Some(NaiveDate::from_ymd(2022, 2, 28)) ); + + // add to leap day assert_eq!( - NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(-1), - NaiveDate::from_ymd(2019, 12, 1) + NaiveDate::from_ymd(2022, 10, 29).checked_add_months(Months::new(16)), + Some(NaiveDate::from_ymd(2024, 2, 29)) ); + + // add into december assert_eq!( - NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-11), - NaiveDate::from_ymd(2013, 2, 1) + NaiveDate::from_ymd(2022, 10, 31).checked_add_months(Months::new(2)), + Some(NaiveDate::from_ymd(2022, 12, 31)) ); + + // sub into december assert_eq!( - NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-12), - NaiveDate::from_ymd(2013, 1, 1) + NaiveDate::from_ymd(2022, 10, 31).checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd(2021, 12, 31)) ); + + // add into january + assert_eq!( + NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(5)), + Some(NaiveDate::from_ymd(2023, 1, 3)) + ); + + // sub into january assert_eq!( - NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-13), - NaiveDate::from_ymd(2012, 12, 1) + NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(7)), + Some(NaiveDate::from_ymd(2022, 1, 3)) ); } From 65f1f2e588ac6e717b36fd78a87e029c882b8d65 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 4 Aug 2022 16:08:00 +0200 Subject: [PATCH 097/999] Bump version to 0.4.20 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 12b45b0183..e2cbaf4599 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.20-rc.1" +version = "0.4.20" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 0b7feacb5482076b4efe1b6bcf720abfc82eb476 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 4 Aug 2022 16:27:22 +0200 Subject: [PATCH 098/999] Clean up some unreachable_pub warnings --- src/oldtime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oldtime.rs b/src/oldtime.rs index 53ad546725..d348b1d228 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -58,13 +58,13 @@ pub struct Duration { } /// The minimum possible `Duration`: `i64::MIN` milliseconds. -pub const MIN: Duration = Duration { +pub(crate) const MIN: Duration = Duration { secs: i64::MIN / MILLIS_PER_SEC - 1, nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; /// The maximum possible `Duration`: `i64::MAX` milliseconds. -pub const MAX: Duration = Duration { +pub(crate) const MAX: Duration = Duration { secs: i64::MAX / MILLIS_PER_SEC, nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; From a185d3bdfac0fca5fc0de4f392d7f22bfee8d486 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Aug 2022 15:31:09 +0200 Subject: [PATCH 099/999] Remove unnecessary try_opt!() macro --- src/date.rs | 4 ++-- src/datetime/mod.rs | 4 ++-- src/lib.rs | 10 ---------- src/naive/date.rs | 4 ++-- src/naive/datetime/mod.rs | 4 ++-- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/date.rs b/src/date.rs index a8c8033b11..ef409ef7cd 100644 --- a/src/date.rs +++ b/src/date.rs @@ -241,7 +241,7 @@ impl Date { /// Returns `None` when it will result in overflow. #[inline] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_add_signed(rhs)); + let date = self.date.checked_add_signed(rhs)?; Some(Date { date, offset: self.offset }) } @@ -250,7 +250,7 @@ impl Date { /// Returns `None` when it will result in overflow. #[inline] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_sub_signed(rhs)); + let date = self.date.checked_sub_signed(rhs)?; Some(Date { date, offset: self.offset }) } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1e0050bd19..a705f23b02 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -324,7 +324,7 @@ impl DateTime { /// Returns `None` when it will result in overflow. #[inline] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_add_signed(rhs)); + let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } @@ -334,7 +334,7 @@ impl DateTime { /// Returns `None` when it will result in overflow. #[inline] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_sub_signed(rhs)); + let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } diff --git a/src/lib.rs b/src/lib.rs index 32e2887329..aa4342f454 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -463,16 +463,6 @@ pub mod prelude { pub use crate::{Offset, TimeZone}; } -// useful throughout the codebase -macro_rules! try_opt { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - mod date; #[allow(deprecated)] pub use date::{Date, MAX_DATE, MIN_DATE}; diff --git a/src/naive/date.rs b/src/naive/date.rs index 13d8b02a5e..134b0506bd 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1053,7 +1053,7 @@ impl NaiveDate { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_add(try_opt!(rhs.num_days().to_i32()))); + let cycle = (cycle as i32).checked_add(rhs.num_days().to_i32()?)?; let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; @@ -1084,7 +1084,7 @@ impl NaiveDate { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_sub(try_opt!(rhs.num_days().to_i32()))); + let cycle = (cycle as i32).checked_sub(rhs.num_days().to_i32()?)?; let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 1984c286a6..7c60c41e30 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -529,7 +529,7 @@ impl NaiveDateTime { return None; } - let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); + let date = self.date.checked_add_signed(OldDuration::seconds(rhs))?; Some(NaiveDateTime { date, time }) } @@ -602,7 +602,7 @@ impl NaiveDateTime { return None; } - let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); + let date = self.date.checked_sub_signed(OldDuration::seconds(rhs))?; Some(NaiveDateTime { date, time }) } From 5edf4d0cc77165e1d1926563579d138d9e04241a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 9 Aug 2022 08:15:17 -0400 Subject: [PATCH 100/999] fixes #758 -- correctly document that %Y parses negative years (#760) --- src/format/strftime.rs | 57 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 58169d125f..651dca84f5 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -11,9 +11,9 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] | -| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] | -| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. Allows negative years. | +| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | +| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | @@ -28,12 +28,12 @@ The following specifiers are available both to formatting and parsing. | `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. | | `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) | | | | | -| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] | +| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2] | | `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.| | | | | -| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] | -| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] | -| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] | +| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^3] | +| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^3] | +| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] | | | | | | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. | | | | | @@ -52,15 +52,15 @@ The following specifiers are available both to formatting and parsing. | `%p` | `AM` | `AM` or `PM` in 12-hour clocks. | | | | | | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | -| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] | -| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] | -| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] | -| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] | -| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] | -| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] | -| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] | -| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] | -| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] | +| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | +| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] | +| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] | +| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] | +| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] | +| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] | +| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] | +| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] | +| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | @@ -68,16 +68,16 @@ The following specifiers are available both to formatting and parsing. | `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | | | | | | | | **TIME ZONE SPECIFIERS:** | -| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] | +| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^8] | | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | | `%:z` | `+09:30` | Same as `%z` but with a colon. | | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. | | | | | | | | **DATE & TIME SPECIFIERS:** | |`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). | -| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] | +| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5] | | | | | -| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]| +| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]| | | | | | | | **SPECIAL SPECIFIERS:** | | `%t` | | Literal tab (`\t`). | @@ -95,24 +95,21 @@ Modifier | Description Notes: -[^1]: `%Y`: - Negative years are allowed in formatting but not in parsing. - -[^2]: `%C`, `%y`: +[^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. -[^3]: `%U`: +[^2]: `%U`: Week 1 starts with the first Sunday in that year. It is possible to have week 0 for days before the first Sunday. -[^4]: `%G`, `%g`, `%V`: +[^3]: `%G`, `%g`, `%V`: Week 1 is the first week with at least 4 days in that year. Week 0 does not exist, so this should be used with `%G` or `%g`. -[^5]: `%S`: +[^4]: `%S`: It accounts for leap seconds, so `60` is possible. -[^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional +[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional digits for seconds and colons in the time zone offset.

@@ -121,12 +118,12 @@ Notes: stable, it is best to avoid this specifier if you want to control the exact output. -[^7]: `%s`: +[^6]: `%s`: This is not padded and can be negative. For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -[^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: +[^7]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
The default `%f` is right-aligned and always zero-padded to 9 digits for the compatibility with glibc and others, @@ -157,7 +154,7 @@ Notes: and parsing `07`, `070000` etc. will yield the same. Note that they can read nothing if the fractional part is zero. -[^9]: `%Z`: +[^8]: `%Z`: Offset will not be populated from the parsed data, nor will it be validated. Timezone is completely ignored. Similar to the glibc `strptime` treatment of this format code. From 557bcd5f44417aaa77b9e31018b0b103f8396ef1 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 9 Aug 2022 23:43:30 +1000 Subject: [PATCH 101/999] handle missing /etc/localtime on some unix platforms (#756) * msrv -> 1.38 * default to UTC when iana-time-zone errors and /etc/localtime is missing, support android fix function name --- .github/workflows/test-release.yml | 6 ++-- .github/workflows/test.yml | 6 ++-- Cargo.toml | 5 +++- ci/github.sh | 4 +-- clippy.toml | 2 +- src/format/mod.rs | 2 +- src/format/parsed.rs | 2 +- src/format/scan.rs | 4 +-- src/naive/date.rs | 2 +- src/naive/internals.rs | 2 +- src/naive/isoweek.rs | 2 +- src/naive/time/mod.rs | 4 +-- src/offset/local/tz_info/rule.rs | 22 +++++++-------- src/offset/local/tz_info/timezone.rs | 15 ++-------- src/offset/local/unix.rs | 41 +++++++++++++++++++++++----- 15 files changed, 70 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index d2d29cb350..67de1c1a18 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -31,11 +31,11 @@ jobs: - os: ubuntu-latest rust_version: nightly - os: ubuntu-20.04 - rust_version: 1.32.0 + rust_version: 1.38.0 - os: macos-latest - rust_version: 1.32.0 + rust_version: 1.38.0 - os: windows-latest - rust_version: 1.32.0 + rust_version: 1.38.0 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f043ab07c5..7c66f3a607 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,11 +39,11 @@ jobs: - os: ubuntu-latest rust_version: nightly - os: ubuntu-20.04 - rust_version: 1.32.0 + rust_version: 1.38.0 - os: macos-latest - rust_version: 1.32.0 + rust_version: 1.38.0 - os: windows-latest - rust_version: 1.32.0 + rust_version: 1.38.0 runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index e2cbaf4599..391f26f68b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ default = ["clock", "std", "oldtime"] alloc = [] libc = [] std = [] -clock = ["std", "winapi"] +clock = ["std", "winapi", "iana-time-zone"] oldtime = ["time"] wasmbind = [] # TODO: empty feature to avoid breaking change in 0.4.20, can be removed later unstable-locales = ["pure-rust-locales", "alloc"] @@ -45,6 +45,9 @@ rkyv = {version = "0.7", optional = true} wasm-bindgen = { version = "0.2" } js-sys = { version = "0.3" } # contains FFI bindings for the JS Date API +[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))'.dependencies] +iana-time-zone = { version = "0.1.41", optional = true } + [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } diff --git a/ci/github.sh b/ci/github.sh index 73d4b86042..31dbf5d53d 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -29,7 +29,7 @@ meaningful in the github actions feature matrix UI. runv cargo --version - if [[ ${RUST_VERSION:-} != 1.32.0 ]]; then + if [[ ${RUST_VERSION:-} != 1.38.0 ]]; then if [[ ${WASM:-} == yes_wasm ]]; then test_wasm elif [[ ${WASM:-} == wasm_simple ]]; then @@ -49,7 +49,7 @@ meaningful in the github actions feature matrix UI. else test_regular UTC0 fi - elif [[ ${RUST_VERSION:-} == 1.32.0 ]]; then + elif [[ ${RUST_VERSION:-} == 1.38.0 ]]; then test_132 else echo "ERROR: didn't run any tests" diff --git a/clippy.toml b/clippy.toml index 22d09a5a01..749c3b58a5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.32" +msrv = "1.38" diff --git a/src/format/mod.rs b/src/format/mod.rs index 2089c4c06a..695ede79ce 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -529,7 +529,7 @@ fn format_inner<'a>( }; if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { // non-four-digit years require an explicit sign as per ISO 8601 match *pad { Pad::None => write!(result, "{:+}", v), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 3a72cb3d59..6fcdd60e82 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -232,7 +232,7 @@ impl Parsed { /// given hour number in 12-hour clocks. #[inline] pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { - if value < 1 || value > 12 { + if !(1..=12).contains(&value) { return Err(OUT_OF_RANGE); } set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) diff --git a/src/format/scan.rs b/src/format/scan.rs index 9a40903436..7334a3b2ed 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -48,7 +48,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if c < b'0' || b'9' < c { + if !(b'0'..=b'9').contains(&c) { if i < min { return Err(INVALID); } else { @@ -79,7 +79,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9'); + let s = s.trim_left_matches(|c: char| ('0'..='9').contains(&c)); Ok((s, v)) } diff --git a/src/naive/date.rs b/src/naive/date.rs index 134b0506bd..dfe3598319 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1887,7 +1887,7 @@ impl fmt::Debug for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let year = self.year(); let mdf = self.mdf(); - if 0 <= year && year <= 9999 { + if (0..=9999).contains(&year) { write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day()) } else { // ISO 8601 requires the explicit sign for out-of-range years diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 43b769df39..f5f0bc9f50 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -297,7 +297,7 @@ impl Of { pub(super) fn valid(&self) -> bool { let Of(of) = *self; let ol = of >> 3; - MIN_OL <= ol && ol <= MAX_OL + (MIN_OL..=MAX_OL).contains(&ol) } #[inline] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 31d44cb602..b3ccecdedd 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -135,7 +135,7 @@ impl fmt::Debug for IsoWeek { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let year = self.year(); let week = self.week(); - if 0 <= year && year <= 9999 { + if (0..=9999).contains(&year) { write!(f, "{:04}-W{:02}", year, week) } else { // ISO 8601 requires the explicit sign for out-of-range years diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index efb0ae61c0..a810c727b8 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -586,7 +586,7 @@ impl NaiveTime { secs += 1; } debug_assert!(-86_400 <= secs && secs < 2 * 86_400); - debug_assert!(0 <= frac && frac < 1_000_000_000); + debug_assert!((0..1_000_000_000).contains(&frac)); if secs < 0 { secs += 86_400; @@ -595,7 +595,7 @@ impl NaiveTime { secs -= 86_400; morerhssecs += 86_400; } - debug_assert!(0 <= secs && secs < 86_400); + debug_assert!((0..86_400).contains(&secs)); (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) } diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 7e9ed7e98d..69f6264e7a 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -365,13 +365,13 @@ fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> { fn parse_offset(cursor: &mut Cursor) -> Result { let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; - if hour < 0 || hour > 24 { + if !(0..=24).contains(&hour) { return Err(Error::InvalidTzString("invalid offset hour")); } - if minute < 0 || minute > 59 { + if !(0..=59).contains(&minute) { return Err(Error::InvalidTzString("invalid offset minute")); } - if second < 0 || second > 59 { + if !(0..=59).contains(&second) { return Err(Error::InvalidTzString("invalid offset second")); } @@ -382,13 +382,13 @@ fn parse_offset(cursor: &mut Cursor) -> Result { fn parse_rule_time(cursor: &mut Cursor) -> Result { let (hour, minute, second) = parse_hhmmss(cursor)?; - if hour < 0 || hour > 24 { + if !(0..=24).contains(&hour) { return Err(Error::InvalidTzString("invalid day time hour")); } - if minute < 0 || minute > 59 { + if !(0..=59).contains(&minute) { return Err(Error::InvalidTzString("invalid day time minute")); } - if second < 0 || second > 59 { + if !(0..=59).contains(&second) { return Err(Error::InvalidTzString("invalid day time second")); } @@ -402,10 +402,10 @@ fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { if hour < -167 || hour > 167 { return Err(Error::InvalidTzString("invalid day time hour")); } - if minute < 0 || minute > 59 { + if !(0..=59).contains(&minute) { return Err(Error::InvalidTzString("invalid day time minute")); } - if second < 0 || second > 59 { + if !(0..=59).contains(&second) { return Err(Error::InvalidTzString("invalid day time second")); } @@ -496,7 +496,7 @@ impl RuleDay { /// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable fn julian_1(julian_day_1: u16) -> Result { - if julian_day_1 < 1 || julian_day_1 > 365 { + if !(1..=365).contains(&julian_day_1) { return Err(Error::TransitionRule("invalid rule day julian day")); } @@ -514,11 +514,11 @@ impl RuleDay { /// Construct a transition rule day represented by a month, a month week and a week day fn month_weekday(month: u8, week: u8, week_day: u8) -> Result { - if month < 1 || month > 12 { + if !(1..=12).contains(&month) { return Err(Error::TransitionRule("invalid rule day month")); } - if week < 1 || week > 5 { + if !(1..=5).contains(&week) { return Err(Error::TransitionRule("invalid rule day week")); } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 6401fdec33..085b25c117 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -89,7 +89,7 @@ impl TimeZone { /// Construct a time zone from the contents of a time zone file /// /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). - pub(super) fn from_tz_data(bytes: &[u8]) -> Result { + pub(crate) fn from_tz_data(bytes: &[u8]) -> Result { parser::parse(bytes) } @@ -104,7 +104,7 @@ impl TimeZone { } /// Construct the time zone associated to UTC - fn utc() -> Self { + pub(crate) fn utc() -> Self { Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::UTC], @@ -482,7 +482,7 @@ impl TimeZoneName { fn new(input: &[u8]) -> Result { let len = input.len(); - if len < 3 || len > 7 { + if !(3..=7).contains(&len) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); @@ -816,15 +816,6 @@ mod tests { let time_zone_local = TimeZone::local()?; let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; assert_eq!(time_zone_local, time_zone_local_1); - } else { - let time_zone_local = TimeZone::local()?; - let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?; - let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?; - let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?; - - assert_eq!(time_zone_local, time_zone_local_1); - assert_eq!(time_zone_local, time_zone_local_2); - assert_eq!(time_zone_local, time_zone_local_3); } let time_zone_utc = TimeZone::from_posix_tz("UTC")?; diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 6cec6b751c..ab91dfde38 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -47,12 +47,19 @@ impl Default for Source { // to that in `naive_to_local` match env::var_os("TZ") { Some(ref s) if s.to_str().is_some() => Source::Environment, - Some(_) | None => Source::LocalTime { - mtime: fs::symlink_metadata("/etc/localtime") - .expect("localtime should exist") - .modified() - .unwrap(), - last_checked: SystemTime::now(), + Some(_) | None => match fs::symlink_metadata("/etc/localtime") { + Ok(data) => Source::LocalTime { + // we have to pick a sensible default when the mtime fails + // by picking SystemTime::now() we raise the probability of + // the cache being invalidated if/when the mtime starts working + mtime: data.modified().unwrap_or_else(|_| SystemTime::now()), + last_checked: SystemTime::now(), + }, + Err(_) => { + // as above, now() should be a better default than some constant + // TODO: see if we can improve caching in the case where the fallback is a valid timezone + Source::LocalTime { mtime: SystemTime::now(), last_checked: SystemTime::now() } + } }, } } @@ -89,10 +96,30 @@ struct Cache { source: Source, } +#[cfg(target_os = "android")] +const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo"; + +#[allow(dead_code)] // keeps the cfg simpler +#[cfg(not(target_os = "android"))] +const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; + +#[cfg(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris"))] +fn fallback_timezone() -> Option { + Some(TimeZone::utc()) +} + +#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))] +fn fallback_timezone() -> Option { + let tz_name = iana_time_zone::get_timezone().ok()?; + let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?; + TimeZone::from_tz_data(&bytes).ok() +} + impl Default for Cache { fn default() -> Cache { + // default to UTC if no local timezone can be found Cache { - zone: TimeZone::local().expect("unable to parse localtime info"), + zone: TimeZone::local().ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc), source: Source::default(), } } From 2d22e57b461e2dbb129fdb53f848be5b970fa49c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Aug 2022 15:50:00 +0200 Subject: [PATCH 102/999] Bump version number to 0.4.21 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 391f26f68b..94adca3e72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.20" +version = "0.4.21" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 95223ee267ca46fd4fc39c42afe8e0b8a2725b8c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Aug 2022 18:00:27 +0200 Subject: [PATCH 103/999] format: be specific about support year range --- src/format/strftime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 651dca84f5..bc26475ccb 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -11,7 +11,7 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. Allows negative years. | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. | | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | From fe4bd220d24496267c9e07807327e50fb3800fce Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 11 Aug 2022 22:42:37 +1000 Subject: [PATCH 104/999] allow optional wasmbindgen use wasmbind feature --- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ Cargo.toml | 8 ++++---- ci/github.sh | 6 ++++++ src/datetime/mod.rs | 18 +++++++++++++++--- src/offset/local/mod.rs | 27 +++++++++++++++++++++++---- src/offset/local/stub.rs | 12 ++++++++++-- src/offset/utc.rs | 13 +++++++++++-- tests/wasm.rs | 6 +++++- 8 files changed, 98 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c66f3a607..ec1f7cf813 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -173,6 +173,30 @@ jobs: RUST_VERSION: stable WASM: wasm_unknown + wasm_unknown_no_wasmbind: + strategy: + matrix: + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + override: true + default: true + + - name: Build and Test + run: bash ci/github.sh + env: + RUST_VERSION: stable + WASM: wasm_unknown_no_wasmbind + wasm_wasi: strategy: matrix: diff --git a/Cargo.toml b/Cargo.toml index 94adca3e72..e9e8a6ffe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,13 @@ appveyor = { repository = "chronotope/chrono" } name = "chrono" [features] -default = ["clock", "std", "oldtime"] +default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] std = [] clock = ["std", "winapi", "iana-time-zone"] oldtime = ["time"] -wasmbind = [] # TODO: empty feature to avoid breaking change in 0.4.20, can be removed later +wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] __internal_bench = ["criterion"] __doctest = [] @@ -42,8 +42,8 @@ criterion = { version = "0.3", optional = true } rkyv = {version = "0.7", optional = true} [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] -wasm-bindgen = { version = "0.2" } -js-sys = { version = "0.3" } # contains FFI bindings for the JS Date API +wasm-bindgen = { version = "0.2", optional = true } +js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))'.dependencies] iana-time-zone = { version = "0.1.41", optional = true } diff --git a/ci/github.sh b/ci/github.sh index 31dbf5d53d..3c9ffb17a6 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -38,6 +38,8 @@ meaningful in the github actions feature matrix UI. test_wasm_emscripten elif [[ ${WASM:-} == wasm_unknown ]]; then test_wasm_unknown + elif [[ ${WASM:-} == wasm_unknown_no_wasmbind ]]; then + test_wasm_unknown_no_wasmbind elif [[ ${WASM:-} == wasm_wasi ]]; then test_wasm_wasi elif [[ ${CORE:-} == no_std ]]; then @@ -126,6 +128,10 @@ test_wasm_unknown() { runt cargo build --target wasm32-unknown-unknown } +test_wasm_unknown_no_wasmbind() { + runt cargo build --target wasm32-unknown-unknown --no-default-features --features clock,std +} + test_wasm_wasi() { runt cargo build --target wasm32-wasi } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index a705f23b02..0907b2a6c0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -994,21 +994,33 @@ impl From> for SystemTime { } } -#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { DateTime::::from(&date) } } -#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { Utc.timestamp_millis(date.get_time() as i64) } } -#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 42a3b3ff86..5a546de99e 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -16,7 +16,11 @@ use crate::{Date, DateTime}; #[cfg(all( not(unix), not(windows), - not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))) + not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )) ))] #[path = "stub.rs"] mod inner; @@ -59,6 +63,7 @@ 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")) )))] pub fn now() -> DateTime { @@ -66,7 +71,11 @@ impl Local { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] pub fn now() -> DateTime { use super::Utc; let now: DateTime = super::Utc::now(); @@ -110,7 +119,11 @@ impl TimeZone for Local { midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) } - #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { let mut local = local.clone(); // Get the offset from the js runtime @@ -121,6 +134,7 @@ impl TimeZone for Local { #[cfg(not(all( target_arch = "wasm32", + feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { @@ -132,7 +146,11 @@ impl TimeZone for Local { Date::from_utc(*utc, *midnight.offset()) } - #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { // Get the offset from the js runtime let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); @@ -141,6 +159,7 @@ impl TimeZone for Local { #[cfg(not(all( target_arch = "wasm32", + feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 57e491d5c6..21b362e5c4 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -18,7 +18,11 @@ pub(super) fn now() -> DateTime { } /// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))] +#[cfg(not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +)))] pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { let tm = Tm { tm_sec: d.second() as i32, @@ -54,7 +58,11 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult DateTime { if tm.tm_sec >= 60 { tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 457006116d..047243d759 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -6,7 +6,11 @@ use core::fmt; #[cfg(all( feature = "clock", - not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))) + not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )) ))] use std::time::{SystemTime, UNIX_EPOCH}; @@ -49,6 +53,7 @@ impl Utc { /// 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")) )))] pub fn now() -> DateTime { @@ -59,7 +64,11 @@ impl Utc { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] pub fn now() -> DateTime { let now = js_sys::Date::new_0(); DateTime::::from(now) diff --git a/tests/wasm.rs b/tests/wasm.rs index 7e7433adbe..bcec43fb9f 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,4 +1,8 @@ -#![cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] +#![cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] use self::chrono::prelude::*; use self::wasm_bindgen_test::*; From bee614a3050d89a8f1ede2e84aa29cf9d904fa48 Mon Sep 17 00:00:00 2001 From: Trevor Arjeski Date: Wed, 10 Aug 2022 10:55:09 +0300 Subject: [PATCH 105/999] Do not include iana-time-zone for sgx In previous version chrono with default features built successfully on the sgx platform. Since `iana-time-zone` is not implemented for this platform, do not include it when `target_env = "sgx"`. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e9e8a6ffe3..983a9dc200 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ rkyv = {version = "0.7", optional = true} wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API -[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))'.dependencies] +[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris", target_env = "sgx")))'.dependencies] iana-time-zone = { version = "0.1.41", optional = true } [target.'cfg(windows)'.dependencies] From 511d36899037dd9efb0df41cba947d17914883fc Mon Sep 17 00:00:00 2001 From: Trevor Arjeski Date: Wed, 10 Aug 2022 14:58:05 +0300 Subject: [PATCH 106/999] Add sgx build to CI --- .github/workflows/test.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec1f7cf813..daa30272f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -86,6 +86,28 @@ jobs: run: cargo build --target thumbv6m-none-eabi --color=always working-directory: ./ci/core-test + intel_sgx: + strategy: + matrix: + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Install rust with fortanix sgx toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: x86_64-fortanix-unknown-sgx + override: true + - uses: Swatinem/rust-cache@v1 + + - name: Build sgx lib + run: cargo build --target x86_64-fortanix-unknown-sgx --color=always + working-directory: ./ci/core-test + wasm: strategy: matrix: From 84f98e01c7bc33323a0d3736b91b4e8bdeefacd3 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Wed, 10 Aug 2022 22:51:43 +1000 Subject: [PATCH 107/999] set fallback feature in iana-time-zone depedency --- Cargo.toml | 3 +-- src/offset/local/unix.rs | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 983a9dc200..ed67d5e881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,13 +40,12 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.3", optional = true } rkyv = {version = "0.7", optional = true} +iana-time-zone = { version = "0.1.41", optional = true, features = ["fallback"] } [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 -[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris", target_env = "sgx")))'.dependencies] -iana-time-zone = { version = "0.1.41", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index ab91dfde38..46845b69d4 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -103,12 +103,6 @@ const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo"; #[cfg(not(target_os = "android"))] const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; -#[cfg(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris"))] -fn fallback_timezone() -> Option { - Some(TimeZone::utc()) -} - -#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))] fn fallback_timezone() -> Option { let tz_name = iana_time_zone::get_timezone().ok()?; let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?; From af69b903f66f41788b8fd5b0f62ae032f2214722 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 12 Aug 2022 17:53:24 +0200 Subject: [PATCH 108/999] Require iana-time-zone 0.1.44 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ed67d5e881..065126eec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.3", optional = true } rkyv = {version = "0.7", optional = true} -iana-time-zone = { version = "0.1.41", optional = true, features = ["fallback"] } +iana-time-zone = { version = "0.1.44", optional = true, features = ["fallback"] } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } From f6b575ceee6e87dbbd5311cd262df7cc7ef13997 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 12 Aug 2022 17:53:35 +0200 Subject: [PATCH 109/999] Bump version number to 0.4.22 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 065126eec8..59abf99af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.21" +version = "0.4.22" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From db8bde3acf5f39ce8c49f86569cbda36a63665bd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 12 Aug 2022 17:56:55 +0200 Subject: [PATCH 110/999] Apply clippy suggestions --- src/format/parsed.rs | 2 +- src/month.rs | 2 +- src/naive/date.rs | 2 +- src/naive/time/mod.rs | 3 +-- src/offset/local/tz_info/parser.rs | 2 +- src/offset/local/tz_info/rule.rs | 2 +- src/weekday.rs | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 6fcdd60e82..32c6c7afbf 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -22,7 +22,7 @@ use crate::{Datelike, Timelike}; /// /// - `to_*` methods try to make a concrete date and time value out of set fields. /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[derive(Clone, PartialEq, Debug, Default)] +#[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct Parsed { /// Year. /// diff --git a/src/month.rs b/src/month.rs index 6490abfd8b..e8a54bf879 100644 --- a/src/month.rs +++ b/src/month.rs @@ -201,7 +201,7 @@ impl Months { } /// An error resulting from reading `` value with `FromStr`. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct ParseMonthError { pub(crate) _dummy: (), } diff --git a/src/naive/date.rs b/src/naive/date.rs index dfe3598319..0c17a79b4e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -207,7 +207,7 @@ fn test_date_bounds() { impl NaiveDate { /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. fn from_of(year: i32, of: Of) -> Option { - if year >= MIN_YEAR && year <= MAX_YEAR && of.valid() { + if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() { let Of(of) = of; Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) }) } else { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index a810c727b8..f882ccd454 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -536,7 +536,6 @@ impl NaiveTime { /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), /// (from_hms(20, 4, 5), -86_400)); /// ``` - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { let mut secs = self.secs; let mut frac = self.frac; @@ -585,7 +584,7 @@ impl NaiveTime { frac -= 1_000_000_000; secs += 1; } - debug_assert!(-86_400 <= secs && secs < 2 * 86_400); + debug_assert!((-86_400..2 * 86_400).contains(&secs)); debug_assert!((0..1_000_000_000).contains(&frac)); if secs < 0 { diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index dfbcf7c2df..77f8e481b5 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -229,7 +229,7 @@ impl<'a> Cursor<'a> { } pub(crate) fn peek(&self) -> Option<&u8> { - self.remaining().get(0) + self.remaining().first() } /// Returns remaining data diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 69f6264e7a..7befddb5cf 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -399,7 +399,7 @@ fn parse_rule_time(cursor: &mut Cursor) -> Result { fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; - if hour < -167 || hour > 167 { + if !(-167..=167).contains(&hour) { return Err(Error::InvalidTzString("invalid day time hour")); } if !(0..=59).contains(&minute) { diff --git a/src/weekday.rs b/src/weekday.rs index 03e06570ab..6d2603f5d1 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -186,7 +186,7 @@ impl num_traits::FromPrimitive for Weekday { } /// An error resulting from reading `Weekday` value with `FromStr`. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct ParseWeekdayError { pub(crate) _dummy: (), } From 45142764ab6e1f2957437aa187970b94d3aaeadc Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sat, 13 Aug 2022 21:11:04 +1000 Subject: [PATCH 111/999] update changelog remove extra ] --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be98bd3e33..f9fd981eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,19 @@ breakage if you use `no-default-features`. There were/are numerous minor versions before 1.0 due to the language changes. Versions with only mechanical changes will be omitted from the following list. -## 0.4.20 (unreleased) +## 0.4.22 + +* Allow wasmbindgen to be optional on `wasm32-unknown-unknown` target [(#771)](https://github.com/chronotope/chrono/pull/771) +* Fix compile error for `x86_64-fortanix-unknown-sgx` [(#767)](https://github.com/chronotope/chrono/pull/767) +* Update `iana-time-zone` version to 1.44 [(#773)](https://github.com/chronotope/chrono/pull/773) + +## 0.4.21 + +* Fall back to UTC timezone in cases where no timezone is found [(#756)](https://github.com/chronotope/chrono/pull/756) +* Correctly detect timezone on Android [(#756)](https://github.com/chronotope/chrono/pull/756) +* Improve documentation for strftime `%Y` specifier [(#760)](https://github.com/chronotope/chrono/pull/760) + +## 0.4.20 * Add more formatting documentation and examples. * Add support for microseconds timestamps serde serialization/deserialization (#304) From 8275b13941241d4bdd48fc658f5724610e628bb2 Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Sun, 14 Aug 2022 14:56:27 +0300 Subject: [PATCH 112/999] strftime: Improve "%+" documentation (#769) --- src/format/strftime.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index bc26475ccb..b5504cfc32 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -113,6 +113,13 @@ Notes: digits for seconds and colons in the time zone offset.

+ This format also supports having a `Z` or `UTC` in place of `%:z`. They + are equivalent to `+00:00`. +
+
+ Note that all `T`, `Z`, and `UTC` are parsed case-insensitively. +
+
The typical `strftime` implementations have different (and locale-dependent) formats for this specifier. While Chrono's format for `%+` is far more stable, it is best to avoid this specifier if you want to control the exact @@ -530,7 +537,7 @@ fn test_strftime_items() { #[cfg(test)] #[test] fn test_strftime_docs() { - use crate::{FixedOffset, TimeZone, Timelike}; + use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); @@ -593,6 +600,24 @@ fn test_strftime_docs() { // 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() + ); + 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() + ); + assert_eq!( dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), "2001-07-08T00:34:60.026490+09:30" From a383abf30e2e2a21ec0bb32554fc295d1d7c5297 Mon Sep 17 00:00:00 2001 From: Alexander Tesfamichael Date: Mon, 15 Aug 2022 10:01:42 +0200 Subject: [PATCH 113/999] Clarify serde module contains defaults too in docs (#777) Makes it more clear the serde module isn't *only* for alternatives it is also required for the default implementations. --- src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aa4342f454..eed9b259ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,14 +506,15 @@ pub use traits::{Datelike, Timelike}; #[doc(hidden)] pub use naive::__BenchYearFlags; -/// Serialization/Deserialization in alternate formats +/// Serialization/Deserialization with serde. /// -/// The various modules in here are intended to be used with serde's [`with` -/// annotation][1] to serialize as something other than the default [RFC -/// 3339][2] format. +/// This module provides default implementations for `DateTime` using the [RFC 3339][1] format and various +/// alternatives for use with serde's [`with` annotation][1]. /// -/// [1]: https://serde.rs/attributes.html#field-attributes -/// [2]: https://tools.ietf.org/html/rfc3339 +/// *Available on crate feature 'serde' only.* +/// +/// [1]: https://tools.ietf.org/html/rfc3339 +/// [2]: https://serde.rs/attributes.html#field-attributes #[cfg(feature = "serde")] pub mod serde { pub use super::datetime::serde::*; From 5f5410163b6655cd0098530f2c6c44bbcc87d102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Konik?= Date: Wed, 17 Aug 2022 18:41:48 +0200 Subject: [PATCH 114/999] GNU coreutils `date`-like time zone formatting (#759) Co-authored-by: Eric Sheppard --- CHANGELOG.md | 2 +- src/format/mod.rs | 70 +++++++++++++++++++++++++++++++++--------- src/format/parse.rs | 5 ++- src/format/strftime.rs | 22 ++++++++++--- 4 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9fd981eea..a60d88c1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Versions with only mechanical changes will be omitted from the following list. * Add compatibility with rfc2822 comments (#733) * Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery * Add the `Months` struct and associated `Add` and `Sub` impls +* Add `GNU` `coreutils` `date`-like time zone formatting ## 0.4.19 @@ -774,4 +775,3 @@ and replaced by 0.2.25 very shortly. Duh.) ## 0.1.0 (2014-11-20) The initial version that was available to `crates.io`. - diff --git a/src/format/mod.rs b/src/format/mod.rs index 695ede79ce..b88ae4b632 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -224,6 +224,18 @@ pub enum Fixed { /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, + /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24:00:00` to `+24:00:00`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetDoubleColon, + /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24` to `+24`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetTripleColon, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). /// /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, @@ -274,6 +286,15 @@ enum InternalInternal { Nanosecond9NoDot, } +#[cfg(any(feature = "alloc", feature = "std", test))] +#[derive(Debug, Clone, PartialEq, Eq)] +enum Colons { + None, + Single, + Double, + Triple, +} + /// A single formatting item. This is used for both formatting and parsing. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Item<'a> { @@ -557,15 +578,32 @@ fn format_inner<'a>( result: &mut String, off: FixedOffset, allow_zulu: bool, - use_colon: bool, + colon_type: Colons, ) -> fmt::Result { let off = off.local_minus_utc(); if !allow_zulu || off != 0 { let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - if use_colon { - write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) - } else { - write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) + + match colon_type { + Colons::None => { + write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) + } + Colons::Single => { + write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) + } + Colons::Double => { + write!( + result, + "{}{:02}:{:02}:{:02}", + sign, + off / 3600, + off / 60 % 60, + off % 60 + ) + } + Colons::Triple => { + write!(result, "{}{:02}", sign, off / 3600) + } } } else { result.push('Z'); @@ -650,17 +688,19 @@ fn format_inner<'a>( result.push_str(name); Ok(()) }), - TimezoneOffsetColon => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, true)) - } - TimezoneOffsetColonZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, true)) - } + TimezoneOffsetColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), + TimezoneOffsetDoubleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), + TimezoneOffsetTripleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), + TimezoneOffsetColonZ => off + .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, false)) + off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) } TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, false)) + off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) } Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { panic!("Do not try to write %#z it is undefined") @@ -681,7 +721,7 @@ fn format_inner<'a>( t.minute(), sec )?; - Some(write_local_minus_utc(result, off, false, false)) + Some(write_local_minus_utc(result, off, false, Colons::None)) } else { None } @@ -693,7 +733,7 @@ fn format_inner<'a>( // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. write!(result, "{:?}T{:?}", d, t)?; - Some(write_local_minus_utc(result, off, false, true)) + Some(write_local_minus_utc(result, off, false, Colons::Single)) } else { None } diff --git a/src/format/parse.rs b/src/format/parse.rs index 0e2db8fae9..02771b2e42 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -420,7 +420,10 @@ where try_consume!(scan::timezone_name_skip(s)); } - &TimezoneOffsetColon | &TimezoneOffset => { + &TimezoneOffsetColon + | &TimezoneOffsetDoubleColon + | &TimezoneOffsetTripleColon + | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( s.trim_left(), scan::colon_or_space diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b5504cfc32..19e401ade5 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -71,6 +71,8 @@ The following specifiers are available both to formatting and parsing. | `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^8] | | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | | `%:z` | `+09:30` | Same as `%z` but with a colon. | +|`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds. | +|`%:::z`| `+09` | Offset from the local time to UTC without minutes. | | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. | | | | | | | | **DATE & TIME SPECIFIERS:** | @@ -404,10 +406,20 @@ impl<'a> Iterator for StrftimeItems<'a> { } } '+' => fix!(RFC3339), - ':' => match next!() { - 'z' => fix!(TimezoneOffsetColon), - _ => Item::Error, - }, + ':' => { + if self.remainder.starts_with("::z") { + self.remainder = &self.remainder[3..]; + fix!(TimezoneOffsetTripleColon) + } else if self.remainder.starts_with(":z") { + self.remainder = &self.remainder[2..]; + fix!(TimezoneOffsetDoubleColon) + } else if self.remainder.starts_with('z') { + self.remainder = &self.remainder[1..]; + fix!(TimezoneOffsetColon) + } else { + Item::Error + } + } '.' => match next!() { '3' => match next!() { 'f' => fix!(Nanosecond3), @@ -596,6 +608,8 @@ fn test_strftime_docs() { //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"); From cd6d42bafd654acfc1328d00cbd15e1c616a1660 Mon Sep 17 00:00:00 2001 From: Brent Gardner Date: Thu, 18 Aug 2022 08:40:34 -0700 Subject: [PATCH 115/999] Impl Add + Sub for NaiveDateTime (#778) Co-authored-by: Eric Sheppard --- CHANGELOG.md | 3 + src/naive/date.rs | 10 ++- src/naive/datetime/mod.rs | 136 +++++++++++++++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60d88c1b6..be2dbfbf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ breakage if you use `no-default-features`. There were/are numerous minor versions before 1.0 due to the language changes. Versions with only mechanical changes will be omitted from the following list. +## 0.4.23 +* Impl Add + Sub for NaiveDateTime + ## 0.4.22 * Allow wasmbindgen to be optional on `wasm32-unknown-unknown` target [(#771)](https://github.com/chronotope/chrono/pull/771) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0c17a79b4e..f54432f26f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -637,14 +637,20 @@ impl NaiveDate { /// NaiveDate::from_ymd(2022, 2, 20).checked_sub_months(Months::new(6)), /// Some(NaiveDate::from_ymd(2021, 8, 20)) /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1) + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); /// ``` pub fn checked_sub_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); } - // Copy `i32::MIN` here so we don't have to do a complicated cast - match months.0 <= 2_147_483_648 { + // Copy `i32::MAX` here so we don't have to do a complicated cast + match months.0 <= 2_147_483_647 { true => self.diff_months(-(months.0 as i32)), false => None, } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 7c60c41e30..64a7921a21 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -19,7 +19,7 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Weekday}; +use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -533,6 +533,34 @@ impl NaiveDateTime { Some(NaiveDateTime { date, time }) } + /// Adds given `Months` to the current date and time. + /// + /// Returns `None` when it will result in overflow. + /// + /// Overflow returns `None`. + /// + /// # Example + /// + /// ``` + /// use std::str::FromStr; + /// use chrono::{Months, NaiveDate, NaiveDateTime}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// .checked_add_months(Months::new(1)), + /// Some(NaiveDate::from_ymd(2014, 2, 1).and_hms(1, 0, 0)) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + pub fn checked_add_months(self, rhs: Months) -> Option { + Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) + } + /// Subtracts given `Duration` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), @@ -606,6 +634,34 @@ impl NaiveDateTime { Some(NaiveDateTime { date, time }) } + /// Subtracts given `Months` from the current date and time. + /// + /// Returns `None` when it will result in overflow. + /// + /// Overflow returns `None`. + /// + /// # Example + /// + /// ``` + /// use std::str::FromStr; + /// use chrono::{Months, NaiveDate, NaiveDateTime}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// .checked_sub_months(Months::new(1)), + /// Some(NaiveDate::from_ymd(2013, 12, 1).and_hms(1, 0, 0)) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + pub fn checked_sub_months(self, rhs: Months) -> Option { + Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.time }) + } + /// Subtracts another `NaiveDateTime` from the current date and time. /// This does not overflow or underflow at all. /// @@ -1289,6 +1345,51 @@ impl AddAssign for NaiveDateTime { } } +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + /// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. + /// + /// # Panics + /// + /// Panics if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Duration, NaiveDateTime, Months, NaiveDate}; + /// use std::str::FromStr; + /// + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + Months::new(1), + /// NaiveDate::from_ymd(2014, 2, 1).and_hms(1, 0, 0) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 2, 0) + Months::new(11), + /// NaiveDate::from_ymd(2014, 12, 1).and_hms(0, 2, 0) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 0, 3) + Months::new(12), + /// NaiveDate::from_ymd(2015, 1, 1).and_hms(0, 0, 3) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 0, 4) + Months::new(13), + /// NaiveDate::from_ymd(2015, 2, 1).and_hms(0, 0, 4) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2014, 1, 31).and_hms(0, 5, 0) + Months::new(1), + /// NaiveDate::from_ymd(2014, 2, 28).and_hms(0, 5, 0) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2020, 1, 31).and_hms(6, 0, 0) + Months::new(1), + /// NaiveDate::from_ymd(2020, 2, 29).and_hms(6, 0, 0) + /// ); + /// ``` + fn add(self, rhs: Months) -> Self::Output { + Self { date: self.date.checked_add_months(rhs).unwrap(), time: self.time } + } +} + /// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. /// It is the same as the addition with a negated `Duration`. /// @@ -1353,6 +1454,39 @@ impl SubAssign for NaiveDateTime { } } +/// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{Duration, NaiveDateTime, Months, NaiveDate}; +/// use std::str::FromStr; +/// +/// assert_eq!( +/// NaiveDate::from_ymd(2014, 01, 01).and_hms(01, 00, 00) - Months::new(11), +/// NaiveDate::from_ymd(2013, 02, 01).and_hms(01, 00, 00) +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd(2014, 01, 01).and_hms(00, 02, 00) - Months::new(12), +/// NaiveDate::from_ymd(2013, 01, 01).and_hms(00, 02, 00) +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd(2014, 01, 01).and_hms(00, 00, 03) - Months::new(13), +/// NaiveDate::from_ymd(2012, 12, 01).and_hms(00, 00, 03) +/// ); +/// ``` +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + fn sub(self, rhs: Months) -> Self::Output { + Self { date: self.date.checked_sub_months(rhs).unwrap(), time: self.time } + } +} + /// Subtracts another `NaiveDateTime` from the current date and time. /// This does not overflow or underflow at all. /// From 1b877c389d2d8805885a5956d410c1c1d718c2cf Mon Sep 17 00:00:00 2001 From: Lachezar Lechev <8925621+elpiel@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:49:29 +0300 Subject: [PATCH 116/999] Re-export Duration's OutOfRangeError (#541) --- CHANGELOG.md | 1 + src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be2dbfbf0f..c702151a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Versions with only mechanical changes will be omitted from the following list. ## 0.4.23 * Impl Add + Sub for NaiveDateTime +* Export `oldtime::OutOfRangeError` to the public API ## 0.4.22 diff --git a/src/lib.rs b/src/lib.rs index eed9b259ac..2064864d47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -429,7 +429,7 @@ extern crate time as oldtime; #[cfg(not(feature = "oldtime"))] mod oldtime; // this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::Duration; +pub use oldtime::{Duration, OutOfRangeError}; #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] From 3ee9bfd1827327aa08b582baf31a0b716f9f405b Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 21 Aug 2022 21:51:29 +1000 Subject: [PATCH 117/999] checked+regular add/sub months impls add test fix spelling :D --- src/datetime/mod.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 0907b2a6c0..0550608654 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -30,6 +30,7 @@ use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; use crate::Date; +use crate::Months; use crate::{Datelike, Timelike, Weekday}; #[cfg(feature = "rkyv")] @@ -329,6 +330,19 @@ impl DateTime { Some(tz.from_utc_datetime(&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. + /// + /// See [`NaiveDate::checked_add_months`] for more details on behavior + pub fn checked_add_months(self, rhs: Months) -> Option> { + self.naive_local() + .checked_add_months(rhs)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + /// Subtracts given `Duration` from the current date and time. /// /// Returns `None` when it will result in overflow. @@ -339,6 +353,19 @@ impl DateTime { Some(tz.from_utc_datetime(&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. + /// + /// See [`NaiveDate::checked_sub_months`] for more details on behavior + pub fn checked_sub_months(self, rhs: Months) -> Option> { + self.naive_local() + .checked_sub_months(rhs)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + /// Subtracts another `DateTime` from the current date and time. /// This does not overflow or underflow at all. #[inline] @@ -869,6 +896,14 @@ impl AddAssign for DateTime { } } +impl Add for DateTime { + type Output = DateTime; + + fn add(self, rhs: Months) -> Self::Output { + self.checked_add_months(rhs).unwrap() + } +} + impl Sub for DateTime { type Output = DateTime; @@ -888,6 +923,14 @@ impl SubAssign for DateTime { } } +impl Sub for DateTime { + type Output = DateTime; + + fn sub(self, rhs: Months) -> Self::Output { + self.checked_sub_months(rhs).unwrap() + } +} + impl Sub> for DateTime { type Output = OldDuration; @@ -1031,6 +1074,23 @@ impl From> for js_sys::Date { } } +#[test] +fn test_add_sub_months() { + let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); + assert_eq!(utc_dt + Months::new(15), Utc.ymd(2019, 12, 5).and_hms(23, 58, 0)); + + let utc_dt = Utc.ymd(2020, 1, 31).and_hms(23, 58, 0); + assert_eq!(utc_dt + Months::new(1), Utc.ymd(2020, 2, 29).and_hms(23, 58, 0)); + assert_eq!(utc_dt + Months::new(2), Utc.ymd(2020, 3, 31).and_hms(23, 58, 0)); + + let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); + assert_eq!(utc_dt - Months::new(15), Utc.ymd(2017, 6, 5).and_hms(23, 58, 0)); + + let utc_dt = Utc.ymd(2020, 3, 31).and_hms(23, 58, 0); + assert_eq!(utc_dt - Months::new(1), Utc.ymd(2020, 2, 29).and_hms(23, 58, 0)); + assert_eq!(utc_dt - Months::new(2), Utc.ymd(2020, 1, 31).and_hms(23, 58, 0)); +} + #[test] fn test_auto_conversion() { let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); From 8ebee9b9771a74fe34f5ee01f495ad8a1c5aea84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Tue, 16 Aug 2022 06:58:36 +0200 Subject: [PATCH 118/999] Fix broken link in documentation --- src/date.rs | 2 +- src/datetime/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/date.rs b/src/date.rs index ef409ef7cd..33bff0b629 100644 --- a/src/date.rs +++ b/src/date.rs @@ -362,7 +362,7 @@ where } /// Formats the date with the specified format string and locale. - /// See the [`::format::strftime`] module + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(feature = "unstable-locales")] #[inline] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 0550608654..2acb3f9206 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -706,7 +706,7 @@ where /// Formats the combined date and time with the specified format string and /// locale. /// - /// See the [`::format::strftime`] module on the supported escape + /// See the [`crate::format::strftime`] module on the supported escape /// sequences. #[cfg(feature = "unstable-locales")] #[inline] From 5b1b812a740f286352793b5a9fe96032337187a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Tue, 16 Aug 2022 06:59:49 +0200 Subject: [PATCH 119/999] Feature is already implied --- src/naive/datetime/serde.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index bad948ad13..6c017dbf97 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1097,19 +1097,16 @@ where } } -#[cfg(feature = "serde")] enum SerdeError { NonExistent { timestamp: V }, Ambiguous { timestamp: V, min: D, max: D }, } /// Construct a [`SerdeError::NonExistent`] -#[cfg(feature = "serde")] fn ne_timestamp(ts: T) -> SerdeError { SerdeError::NonExistent:: { timestamp: ts } } -#[cfg(feature = "serde")] impl fmt::Debug for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ChronoSerdeError({})", self) @@ -1117,7 +1114,6 @@ impl fmt::Debug for SerdeError { } // impl core::error::Error for SerdeError {} -#[cfg(feature = "serde")] impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { From 16b2ae4156952cba9f480860eed4b83a9601b147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Tue, 16 Aug 2022 07:00:23 +0200 Subject: [PATCH 120/999] Add feature documentation for docs.rs This way the documentation will thell user which feature(s) are needed to use a function, trait, etc. docs.rs display the information like > Available on crate feature `serde` only. --- Cargo.toml | 1 + src/date.rs | 4 +++ src/datetime/mod.rs | 40 +++++++++++++++++++++++++++ src/datetime/rustc_serialize.rs | 4 +++ src/datetime/serde.rs | 3 ++ src/format/mod.rs | 10 +++++++ src/format/strftime.rs | 1 + src/lib.rs | 8 ++++++ src/month.rs | 1 + src/naive/date.rs | 4 +++ src/naive/datetime/mod.rs | 2 ++ src/naive/datetime/rustc_serialize.rs | 2 ++ src/naive/datetime/serde.rs | 2 ++ src/naive/mod.rs | 1 + src/naive/time/mod.rs | 2 ++ src/naive/time/rustc_serialize.rs | 2 ++ src/naive/time/serde.rs | 2 ++ src/offset/utc.rs | 1 + src/oldtime.rs | 3 ++ src/round.rs | 1 + src/weekday.rs | 2 ++ 21 files changed, 96 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 59abf99af3..2e42224f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ wasm-bindgen-test = "0.3" [package.metadata.docs.rs] features = ["serde"] +rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] features = ["serde"] diff --git a/src/date.rs b/src/date.rs index 33bff0b629..5d30d887dc 100644 --- a/src/date.rs +++ b/src/date.rs @@ -313,6 +313,7 @@ where { /// Formats the date with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where @@ -335,6 +336,7 @@ where /// assert_eq!(formatted, "02/04/2017"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -342,6 +344,7 @@ where /// Formats the date with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] pub fn format_localized_with_items<'a, I, B>( &self, @@ -365,6 +368,7 @@ where /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] pub fn format_localized<'a>( &self, diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2acb3f9206..8705c2b76d 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -415,6 +415,7 @@ impl Default for DateTime { } #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Default for DateTime { fn default() -> Self { Local.from_utc_datetime(&NaiveDateTime::default()) @@ -440,6 +441,7 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -462,6 +464,7 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -474,6 +477,7 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -486,6 +490,7 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -572,6 +577,7 @@ where { /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn to_rfc2822(&self) -> String { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; self.format_with_items(ITEMS.iter()).to_string() @@ -579,6 +585,7 @@ where /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn to_rfc3339(&self) -> String { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; self.format_with_items(ITEMS.iter()).to_string() @@ -609,6 +616,7 @@ where /// "2018-01-26T10:30:09+08:00"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { use crate::format::Numeric::*; use crate::format::Pad::Zero; @@ -653,6 +661,7 @@ where /// Formats the combined date and time with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where @@ -676,6 +685,7 @@ where /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -683,6 +693,7 @@ where /// Formats the combined date and time with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] pub fn format_localized_with_items<'a, I, B>( &self, @@ -709,6 +720,7 @@ where /// See the [`crate::format::strftime`] module on the supported escape /// sequences. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] pub fn format_localized<'a>( &self, @@ -986,6 +998,7 @@ impl str::FromStr for DateTime { /// "2012- 12-12T12: 12:12Z".parse::>(); /// ``` #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl str::FromStr for DateTime { type Err = ParseError; @@ -995,6 +1008,7 @@ impl str::FromStr for DateTime { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { @@ -1015,6 +1029,7 @@ impl From for DateTime { } #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { DateTime::::from(t).with_timezone(&Local) @@ -1022,6 +1037,7 @@ impl From for DateTime { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { use std::time::Duration; @@ -1042,6 +1058,14 @@ impl From> for SystemTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] +#[cfg_attr( + docsrs, + doc(cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))) +)] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { DateTime::::from(&date) @@ -1053,6 +1077,14 @@ impl From for DateTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] +#[cfg_attr( + docsrs, + doc(cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))) +)] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { Utc.timestamp_millis(date.get_time() as i64) @@ -1064,6 +1096,14 @@ impl From<&js_sys::Date> for DateTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] +#[cfg_attr( + docsrs, + doc(cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))) +)] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 5ff43cfdcc..e06517404d 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] + use super::DateTime; #[cfg(feature = "clock")] use crate::offset::Local; @@ -80,6 +82,7 @@ impl Decodable for TsSeconds { } #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Decodable for DateTime { fn decode(d: &mut D) -> Result, D::Error> { match d.read_str()?.parse::>() { @@ -90,6 +93,7 @@ impl Decodable for DateTime { } #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[allow(deprecated)] impl Decodable for TsSeconds { #[allow(deprecated)] diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 556c3ac99a..5371edb797 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + use core::fmt; use serde::{de, ser}; @@ -103,6 +105,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// See [the `serde` module](./serde/index.html) for alternate /// serialization formats. #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where diff --git a/src/format/mod.rs b/src/format/mod.rs index b88ae4b632..ff0363bb94 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -302,11 +302,13 @@ pub enum Item<'a> { Literal(&'a str), /// Same as `Literal` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -419,6 +421,7 @@ impl fmt::Display for ParseError { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { @@ -437,6 +440,7 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format_item<'a>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -754,6 +758,7 @@ fn format_inner<'a>( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -783,6 +788,7 @@ pub mod strftime; /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. #[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat { /// The date view, if any. @@ -837,6 +843,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn new_with_locale( date: Option, time: Option, @@ -848,6 +855,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn new_with_offset_and_locale( date: Option, time: Option, @@ -924,6 +932,7 @@ 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>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -940,6 +949,7 @@ pub fn format_item_localized<'a>( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 19e401ade5..31509a0ff4 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -227,6 +227,7 @@ impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); diff --git a/src/lib.rs b/src/lib.rs index 2064864d47..f9a00b161d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -423,8 +423,10 @@ // can remove this if/when rustc-serialize support is removed // keeps clippy happy in the meantime #![cfg_attr(feature = "rustc-serialize", allow(deprecated))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "oldtime")] +#[cfg_attr(docsrs, doc(cfg(feature = "oldtime")))] extern crate time as oldtime; #[cfg(not(feature = "oldtime"))] mod oldtime; @@ -444,9 +446,11 @@ pub mod prelude { #[doc(no_inline)] pub use crate::Date; #[cfg(feature = "clock")] + #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use crate::Local; #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[doc(no_inline)] pub use crate::Locale; #[doc(no_inline)] @@ -469,6 +473,7 @@ pub use date::{Date, MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] +#[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] pub use datetime::rustc_serialize::TsSeconds; #[allow(deprecated)] pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; @@ -476,6 +481,7 @@ pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub use format::Locale; pub use format::{ParseError, ParseResult}; @@ -485,6 +491,7 @@ pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use offset::Local; #[doc(no_inline)] @@ -516,6 +523,7 @@ pub use naive::__BenchYearFlags; /// [1]: https://tools.ietf.org/html/rfc3339 /// [2]: https://serde.rs/attributes.html#field-attributes #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } diff --git a/src/month.rs b/src/month.rs index e8a54bf879..56316b3c4d 100644 --- a/src/month.rs +++ b/src/month.rs @@ -213,6 +213,7 @@ impl fmt::Debug for ParseMonthError { } #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod month_serde { use super::Month; use serde::{de, ser}; diff --git a/src/naive/date.rs b/src/naive/date.rs index f54432f26f..d2fdc6d9d3 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1161,6 +1161,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where @@ -1203,6 +1204,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -2034,6 +2036,7 @@ where } #[cfg(feature = "rustc-serialize")] +#[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] mod rustc_serialize { use super::NaiveDate; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -2065,6 +2068,7 @@ mod rustc_serialize { } #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde { use super::NaiveDate; use core::fmt; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 64a7921a21..577fe87d00 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -732,6 +732,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where @@ -774,6 +775,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs index c65017d2a3..6e33829e0a 100644 --- a/src/naive/datetime/rustc_serialize.rs +++ b/src/naive/datetime/rustc_serialize.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] + use super::NaiveDateTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use std::ops::Deref; diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 6c017dbf97..70369c3888 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + use core::fmt; use serde::{de, ser}; diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 2ddd9a0134..f7cb298b66 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -33,6 +33,7 @@ pub use self::internals::YearFlags as __BenchYearFlags; /// [1]: https://serde.rs/attributes.html#field-attributes /// [2]: https://tools.ietf.org/html/rfc3339 #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f882ccd454..a0345f8fc7 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -744,6 +744,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where @@ -788,6 +789,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs index 42c091a661..9eaf68219e 100644 --- a/src/naive/time/rustc_serialize.rs +++ b/src/naive/time/rustc_serialize.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] + use super::NaiveTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index 6df610ca43..369c7a1c27 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -1,3 +1,5 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + use super::NaiveTime; use core::fmt; use serde::{de, ser}; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 047243d759..d38bfade51 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -44,6 +44,7 @@ use crate::{Date, DateTime}; pub struct Utc; #[cfg(feature = "clock")] +#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Utc { /// Returns a `Date` which corresponds to the current date. pub fn today() -> Date { diff --git a/src/oldtime.rs b/src/oldtime.rs index d348b1d228..970dc90d16 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -382,6 +382,7 @@ impl Div for Duration { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl<'a> std::iter::Sum<&'a Duration> for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + *x) @@ -389,6 +390,7 @@ impl<'a> std::iter::Sum<&'a Duration> for Duration { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::iter::Sum for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + x) @@ -445,6 +447,7 @@ impl fmt::Display for OutOfRangeError { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for OutOfRangeError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/round.rs b/src/round.rs index c10b78c760..fc7b8f99a6 100644 --- a/src/round.rs +++ b/src/round.rs @@ -294,6 +294,7 @@ impl fmt::Display for RoundingError { } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/weekday.rs b/src/weekday.rs index 6d2603f5d1..bebdd116a9 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -192,6 +192,7 @@ pub struct ParseWeekdayError { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for ParseWeekdayError {} impl fmt::Display for ParseWeekdayError { @@ -209,6 +210,7 @@ impl fmt::Debug for ParseWeekdayError { // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod weekday_serde { use super::Weekday; use core::fmt; From e1b2675cbfe2dabd306c52c44928c56c0f39b086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Tue, 16 Aug 2022 07:03:06 +0200 Subject: [PATCH 121/999] Update documentation test on CI --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index daa30272f5..c9f39f947d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -273,5 +273,5 @@ jobs: override: true - name: Check docs build without warnings env: - RUSTDOCFLAGS: "-D warnings" - run: cargo +nightly doc + RUSTDOCFLAGS: "-D warnings --cfg docsrs" + run: cargo +nightly doc --all-features --no-deps From 17d8c2af865f14b83154a0112f372fcf1718b481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Tue, 16 Aug 2022 07:20:46 +0200 Subject: [PATCH 122/999] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c702151a68..5956c34776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Versions with only mechanical changes will be omitted from the following list. ## 0.4.23 * Impl Add + Sub for NaiveDateTime * Export `oldtime::OutOfRangeError` to the public API +* Add feature documentation for docs.rs [(#779)](https://github.com/chronotope/chrono/pull/779) ## 0.4.22 @@ -53,7 +54,6 @@ Versions with only mechanical changes will be omitted from the following list. * Add compatibility with rfc2822 comments (#733) * Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery * Add the `Months` struct and associated `Add` and `Sub` impls -* Add `GNU` `coreutils` `date`-like time zone formatting ## 0.4.19 @@ -779,3 +779,4 @@ and replaced by 0.2.25 very shortly. Duh.) ## 0.1.0 (2014-11-20) The initial version that was available to `crates.io`. + From 9c4604c0f8285c9d40c1e20e6377d22dc7c9fe12 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 25 Aug 2022 15:36:41 +0200 Subject: [PATCH 123/999] Make the changelog historical from now on --- .github/pull_request_template.md | 7 +--- CHANGELOG.md | 57 ++------------------------------ README.md | 5 --- src/lib.rs | 3 -- 4 files changed, 4 insertions(+), 68 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 14286bddee..ba92e741d0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,3 @@ ### Thanks for contributing to chrono! -- [ ] Have you added yourself and the change to the [changelog]? (Don't worry - about adding the PR number) -- [ ] If this pull request fixes a bug, does it add a test that verifies that - we can't reintroduce it? - -[changelog]: ../CHANGELOG.md +Please consider adding a test to ensure your bug fix/feature will not break in the future. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5956c34776..1e6f6f935e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,59 +1,9 @@ ChangeLog for Chrono ==================== -This documents all notable changes to [Chrono](https://github.com/chronotope/chrono). - -Chrono obeys the principle of [Semantic Versioning](http://semver.org/), with one caveat: we may -move previously-existing code behind a feature gate and put it behind a new feature. This new -feature will always be placed in the `previously-default` feature, which you can use to prevent -breakage if you use `no-default-features`. - -There were/are numerous minor versions before 1.0 due to the language changes. -Versions with only mechanical changes will be omitted from the following list. - -## 0.4.23 -* Impl Add + Sub for NaiveDateTime -* Export `oldtime::OutOfRangeError` to the public API -* Add feature documentation for docs.rs [(#779)](https://github.com/chronotope/chrono/pull/779) - -## 0.4.22 - -* Allow wasmbindgen to be optional on `wasm32-unknown-unknown` target [(#771)](https://github.com/chronotope/chrono/pull/771) -* Fix compile error for `x86_64-fortanix-unknown-sgx` [(#767)](https://github.com/chronotope/chrono/pull/767) -* Update `iana-time-zone` version to 1.44 [(#773)](https://github.com/chronotope/chrono/pull/773) - -## 0.4.21 - -* Fall back to UTC timezone in cases where no timezone is found [(#756)](https://github.com/chronotope/chrono/pull/756) -* Correctly detect timezone on Android [(#756)](https://github.com/chronotope/chrono/pull/756) -* Improve documentation for strftime `%Y` specifier [(#760)](https://github.com/chronotope/chrono/pull/760) - -## 0.4.20 - -* Add more formatting documentation and examples. -* Add support for microseconds timestamps serde serialization/deserialization (#304) -* Fix `DurationRound` is not TZ aware (#495) -* Implement `DurationRound` for `NaiveDateTime` -* Implement `std::iter::Sum` for `Duration` -* Add `DateTime::from_local()` to construct from given local date and time (#572) -* Add a function that calculates the number of years elapsed between now and a given `Date` or `DateTime` (#557) -* Correct build for wasm32-unknown-emscripten target (#568) -* Change `Local::now()` and `Utc::now()` documentation from "current date" to "current date and time" (#647) -* Fix `duration_round` panic on rounding by `Duration::zero()` (#658) -* Add optional rkyv support. -* Add support for microseconds timestamps serde serialization for `NaiveDateTime`. -* Add support for optional timestamps serde serialization for `NaiveDateTime`. -* Fix build for wasm32-unknown-emscripten (@yu-re-ka #593) -* Make `ParseErrorKind` public and available through `ParseError::kind()` (#588) -* Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` -* Fix panicking when parsing a `DateTime` (@botahamec) -* Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) -* Remove libc dependency from Cargo.toml. -* Add the `and_local_timezone` method to `NaiveDateTime` -* Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos -* Add compatibility with rfc2822 comments (#733) -* Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery -* Add the `Months` struct and associated `Add` and `Sub` impls +This documents notable changes to [Chrono](https://github.com/chronotope/chrono) +up to and including version 0.4.19. For later releases, please review the +release notes on [GitHub](https://github.com/chronotope/chrono/releases). ## 0.4.19 @@ -779,4 +729,3 @@ and replaced by 0.2.25 very shortly. Duh.) ## 0.1.0 (2014-11-20) The initial version that was available to `crates.io`. - diff --git a/README.md b/README.md index fa7e289de5..2c3bdf5977 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,6 @@ which Chrono builds upon and should acknowledge: * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) -Any significant changes to Chrono are documented in -the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. - - ## Usage Put this in your `Cargo.toml`: @@ -414,4 +410,3 @@ crate ([sources](https://github.com/bcourtine/chrono-ext/)). Advanced time zone handling is not yet supported. For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. - diff --git a/src/lib.rs b/src/lib.rs index f9a00b161d..ef8c506ef9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,6 @@ //! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) //! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) //! -//! Any significant changes to Chrono are documented in -//! the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. -//! //! ## Usage //! //! Put this in your `Cargo.toml`: From 2162dd7ca6f9b18ab04d4d1b42ddddb9db8a12c1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 27 Aug 2022 10:05:54 +0200 Subject: [PATCH 124/999] Remove extensive code examples from README --- Makefile | 3 - README.md | 353 ----------------------------------------------- ci/fix-readme.sh | 32 ----- ci/lint.sh | 1 - src/lib.rs | 12 -- 5 files changed, 401 deletions(-) delete mode 100755 ci/fix-readme.sh diff --git a/Makefile b/Makefile index 46b39dd25f..63aef15ace 100644 --- a/Makefile +++ b/Makefile @@ -17,9 +17,6 @@ authors: .PHONY: readme README.md readme: README.md -README.md: src/lib.rs - ( ./ci/fix-readme.sh $< ) > $@ - .PHONY: test test: CHANNEL=$(CHANNEL) ./ci/travis.sh diff --git a/README.md b/README.md index 2c3bdf5977..d20200d1b6 100644 --- a/README.md +++ b/README.md @@ -31,359 +31,6 @@ which Chrono builds upon and should acknowledge: * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) -## Usage - -Put this in your `Cargo.toml`: - -```toml -[dependencies] -chrono = "0.4" -``` - -### Features - -Chrono supports various runtime environments and operating systems, and has -several features that may be enabled or disabled. - -Default features: - -- `alloc`: Enable features that depend on allocation (primarily string formatting) -- `std`: Enables functionality that depends on the standard library. This - is a superset of `alloc` and adds interoperation with standard library types - and traits. -- `clock`: Enables reading the system time (`now`) that depends on the standard library for -UNIX-like operating systems and the Windows API (`winapi`) for Windows. - -Optional features: - -- [`serde`][]: Enable serialization/deserialization via serde. -- `unstable-locales`: Enable localization. This adds various methods with a - `_localized` suffix. The implementation and API may change or even be - removed in a patch release. Feedback welcome. - -[`serde`]: https://github.com/serde-rs/serde -[wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen - -See the [cargo docs][] for examples of specifying features. - -[cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features - -## Overview - -### Duration - -Chrono currently uses its own [`Duration`] type to represent the magnitude -of a time span. Since this has the same name as the newer, standard type for -duration, the reference will refer this type as `OldDuration`. - -Note that this is an "accurate" duration represented as seconds and -nanoseconds and does not represent "nominal" components such as days or -months. - -When the `oldtime` feature is enabled, [`Duration`] is an alias for the -[`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -type from v0.1 of the time crate. time v0.1 is deprecated, so new code -should disable the `oldtime` feature and use the `chrono::Duration` type -instead. The `oldtime` feature is enabled by default for backwards -compatibility, but future versions of Chrono are likely to remove the -feature entirely. - -Chrono does not yet natively support -the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -but it will be supported in the future. -Meanwhile you can convert between two types with -[`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -and -[`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) -methods. - -### Date and Time - -Chrono provides a -[**`DateTime`**](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) -type to represent a date and a time in a timezone. - -For more abstract moment-in-time tracking such as internal timekeeping -that is unconcerned with timezones, consider -[`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -which tracks your system clock, or -[`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -is an opaque but monotonically-increasing representation of a moment in time. - -`DateTime` is timezone-aware and must be constructed from -the [**`TimeZone`**](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html) object, -which defines how the local date is converted to and back from the UTC date. -There are three well-known `TimeZone` implementations: - -* [**`Utc`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. - -* [**`Local`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html) specifies the system local time zone. - -* [**`FixedOffset`**](https://docs.rs/chrono/0.4/chrono/offset/struct.FixedOffset.html) specifies - an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. - This often results from the parsed textual date and time. - Since it stores the most information and does not depend on the system environment, - you would want to normalize other `TimeZone`s into this type. - -`DateTime`s with different `TimeZone` types are distinct and do not mix, -but can be converted to each other using -the [`DateTime::with_timezone`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.with_timezone) method. - -You can get the current date and time in the UTC time zone -([`Utc::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html#method.now)) -or in the local time zone -([`Local::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html#method.now)). - -```rust -use chrono::prelude::*; - -let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` -let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -``` - -Alternatively, you can create your own date and time. -This is a bit verbose due to Rust's lack of function and method overloading, -but in turn we get a rich combination of initialization methods. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` -// July 8 is 188th day of the year 2014 (`o` for "ordinal") -assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); -// July 8 is Tuesday in ISO week 28 of the year 2014. -assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); - -let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); - -// dynamic verification -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), - LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); - -// other time zone objects can be used to construct a local datetime. -// obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); -assert_eq!(dt, fixed_dt); -``` - -Various properties are available to the date and time, and can be altered individually. -Most of them are defined in the traits [`Datelike`](https://docs.rs/chrono/0.4/chrono/trait.Datelike.html) and -[`Timelike`](https://docs.rs/chrono/0.4/chrono/trait.Timelike.html) which you should `use` before. -Addition and subtraction is also supported. -The following illustrates most supported operations to the date and time: - -```rust -use chrono::prelude::*; -use chrono::Duration; - -// assume this returned `2014-11-28T21:45:59.324310806+09:00`: -let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); - -// property accessors -assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); -assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls -assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59)); -assert_eq!(dt.weekday(), Weekday::Fri); -assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7 -assert_eq!(dt.ordinal(), 332); // the day of year -assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1 - -// time zone accessor and manipulation -assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); - -// a sample of property manipulations (validates dynamically) -assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday -assert_eq!(dt.with_day(32), None); -assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE - -// arithmetic operations -let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); -assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), - Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), - Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -``` - -### Formatting and Parsing - -Formatting is done via the [`format`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.format) method, -which format is equivalent to the familiar `strftime` format. - -See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -The default `to_string` method and `{:?}` specifier also give a reasonable representation. -Chrono also provides [`to_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc2822) and -[`to_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc3339) methods -for well-known formats. - -Chrono now also provides date formatting in almost any language without the -help of an additional C library. This functionality is under the feature -`unstable-locales`: - -```toml -chrono = { version = "0.4", features = ["unstable-locales"] } -``` - -The `unstable-locales` feature requires and implies at least the `alloc` feature. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); - -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); -assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); -assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000"); -assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00"); -assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); - -// Note that milli/nanoseconds are only printed if they are non-zero -let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); -assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); -``` - -Parsing can be done with three methods: - -1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait - (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method - on a string) can be used for parsing `DateTime`, `DateTime` and - `DateTime` values. This parses what the `{:?}` - ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) - format specifier prints, and requires the offset to be present. - -2. [`DateTime::parse_from_str`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_str) parses - a date and time with offsets and returns `DateTime`. - This should be used when the offset is a part of input and the caller cannot guess that. - It *cannot* be used when the offset can be missing. - [`DateTime::parse_from_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc2822) - and - [`DateTime::parse_from_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc3339) - are similar but for well-known formats. - -3. [`Offset::datetime_from_str`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.datetime_from_str) is - similar but returns `DateTime` of given offset. - When the explicit offset is missing from the input, it simply uses given offset. - It issues an error when the input contains an explicit offset different - from the current offset. - -More detailed control over the parsing process is available via -[`format`](https://docs.rs/chrono/0.4/chrono/format/index.html) module. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); - -// method 1 -assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); - -// method 2 -assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); - -// method 3 -assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); - -// oops, the year is missing! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); -// oops, the format string does not include the year at all! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); -// oops, the weekday is incorrect! -assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); -``` - -Again : See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -### Conversion from and to EPOCH timestamps - -Use [`Utc.timestamp(seconds, nanoseconds)`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.timestamp) -to construct a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) from a UNIX timestamp -(seconds, nanoseconds that passed since January 1st 1970). - -Use [`DateTime.timestamp`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -from a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html). Additionally, you can use -[`DateTime.timestamp_subsec_nanos`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp_subsec_nanos) -to get the number of additional number of nanoseconds. - -```rust -// We need the trait in scope to use Utc::timestamp(). -use chrono::{DateTime, TimeZone, Utc}; - -// Construct a datetime from epoch: -let dt = Utc.timestamp(1_500_000_000, 0); -assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); - -// Get epoch value from a datetime: -let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); -assert_eq!(dt.timestamp(), 1_500_000_000); -``` - -### Individual date - -Chrono also provides an individual date type ([**`Date`**](https://docs.rs/chrono/0.4/chrono/struct.Date.html)). -It also has time zones attached, and have to be constructed via time zones. -Most operations available to `DateTime` are also available to `Date` whenever appropriate. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -assert_eq!(Utc::today(), Utc::now().date()); -assert_eq!(Local::today(), Local::now().date()); - -assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); -assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), - "070809"); -``` - -There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. - -`DateTime` has [`date`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.date) method -which returns a `Date` which represents its date component. -There is also a [`time`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.time) method, -which simply returns a naive local time described below. - -### Naive date and time - -Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -as [**`NaiveDate`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html), -[**`NaiveTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html) and -[**`NaiveDateTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDateTime.html) respectively. - -They have almost equivalent interfaces as their timezone-aware twins, -but are not associated to time zones obviously and can be quite low-level. -They are mostly useful for building blocks for higher-level types. - -Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -[`naive_local`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_local) returns -a view to the naive local time, -and [`naive_utc`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_utc) returns -a view to the naive UTC time. - ## Limitations Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. diff --git a/ci/fix-readme.sh b/ci/fix-readme.sh deleted file mode 100755 index c08118f12c..0000000000 --- a/ci/fix-readme.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -VERSION="$( cargo read-manifest | python -c 'import json, sys; print(json.load(sys.stdin)["version"][:3])')" -LIB="$1" - -# Make the Chrono in the header a link to the docs -awk '/^\/\/! # Chrono: / { print "[Chrono][docsrs]:", substr($0, index($0, $4))}' "$LIB" -awk '/^\/\/! # Chrono: / { print "[Chrono][docsrs]:", substr($0, index($0, $4))}' "$LIB" | sed 's/./=/g' -# Add all the badges -echo ' -[![Chrono GitHub Actions][gh-image]][gh-checks] -[![Chrono on crates.io][cratesio-image]][cratesio] -[![Chrono on docs.rs][docsrs-image]][docsrs] -[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] - -[gh-image]: https://github.com/chronotope/chrono/workflows/test/badge.svg -[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest -[cratesio-image]: https://img.shields.io/crates/v/chrono.svg -[cratesio]: https://crates.io/crates/chrono -[docsrs-image]: https://docs.rs/chrono/badge.svg -[docsrs]: https://docs.rs/chrono -[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg -[gitter]: https://gitter.im/chrono-rs/chrono' - -# print the section between the header and the usage -awk '/^\/\/! # Chrono:/,/^\/\/! ## /' "$LIB" | cut -b 5- | grep -v '^#' | \ - sed 's/](\.\//](https:\/\/docs.rs\/chrono\/'"$VERSION"'\/chrono\//g' -echo -# Replace relative doc links with links to this exact version of docs on -# docs.rs -awk '/^\/\/! ## /,!/^\/\/!/' "$LIB" | cut -b 5- | grep -v '^# ' | \ - sed 's/](\.\//](https:\/\/docs.rs\/chrono\/'"$VERSION"'\/chrono\//g' \ diff --git a/ci/lint.sh b/ci/lint.sh index 360d72cd22..f028c2112c 100755 --- a/ci/lint.sh +++ b/ci/lint.sh @@ -9,7 +9,6 @@ main() { # rustflags are present because of: https://github.com/rust-lang/rust-clippy/issues/5749 runt env RUSTFLAGS="-Dwarnings" cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive runt cargo fmt -- --check --color=always - runt make readme runv git diff --exit-code -- README.md } diff --git a/src/lib.rs b/src/lib.rs index ef8c506ef9..123d7ea90f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - //! # Chrono: Date and Time for Rust //! //! It aims to be a feature-complete superset of @@ -19,15 +16,6 @@ //! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) //! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) //! -//! ## Usage -//! -//! Put this in your `Cargo.toml`: -//! -//! ```toml -//! [dependencies] -//! chrono = "0.4" -//! ``` -//! //! ### Features //! //! Chrono supports various runtime environments and operating systems, and has From 181b08364774711994cb6e84befc991d4bb32c88 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Wed, 17 Aug 2022 23:18:15 +1000 Subject: [PATCH 125/999] add/sub Days docs fixes improve checked impls --- src/datetime/mod.rs | 38 ++++++++++++++++++- src/lib.rs | 2 +- src/naive/date.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/naive/datetime/mod.rs | 32 +++++++++++++++- src/naive/mod.rs | 2 +- 5 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8705c2b76d..48eb85001a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -24,7 +24,7 @@ use crate::format::DelayedFormat; use crate::format::Locale; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item}; -use crate::naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; @@ -366,6 +366,26 @@ impl DateTime { .single() } + /// Add a duration in [`Days`] to the date part of the `DateTime` + /// + /// Returns `None` if the resulting date would be out of range. + pub fn checked_add_days(self, days: Days) -> Option { + self.datetime + .checked_add_days(days)? + .and_local_timezone(TimeZone::from_offset(&self.offset)) + .single() + } + + /// Subtract a duration in [`Days`] to the date part of the `DateTime` + /// + /// Returns `None` if the resulting date would be out of range. + pub fn checked_sub_days(self, days: Days) -> Option { + self.datetime + .checked_sub_days(days)? + .and_local_timezone(TimeZone::from_offset(&self.offset)) + .single() + } + /// Subtracts another `DateTime` from the current date and time. /// This does not overflow or underflow at all. #[inline] @@ -952,6 +972,22 @@ impl Sub> for DateTime { } } +impl Add for DateTime { + type Output = DateTime; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).unwrap() + } +} + +impl Sub for DateTime { + type Output = DateTime; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).unwrap() + } +} + impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}{:?}", self.naive_local(), self.offset) diff --git a/src/lib.rs b/src/lib.rs index 123d7ea90f..c16485f7c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -472,7 +472,7 @@ pub use format::{ParseError, ParseResult}; pub mod naive; #[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; +pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] diff --git a/src/naive/date.rs b/src/naive/date.rs index d2fdc6d9d3..cc674c70b8 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::convert::TryFrom; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; @@ -115,6 +116,22 @@ impl NaiveWeek { } } +/// A duration in calendar days. +/// +/// This is useful becuase when using `Duration` it is possible +/// that adding `Duration::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 `Duration::days(n)` and `Days::new(n)` are equivalent. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub struct Days(pub(crate) u64); + +impl Days { + /// Construct a new `Days` from a number of months + pub fn new(num: u64) -> Self { + Self(num) + } +} + /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date](#calendar-date) /// from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -698,6 +715,52 @@ impl NaiveDate { NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags)) } + /// Add a duration in [`Days`] to the date + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 2, 20).checked_add_days(Days::new(9)), + /// Some(NaiveDate::from_ymd(2022, 3, 1)) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 7, 31).checked_add_days(Days::new(2)), + /// Some(NaiveDate::from_ymd(2022, 8, 2)) + /// ); + /// ``` + pub fn checked_add_days(self, days: Days) -> Option { + if days.0 == 0 { + return Some(self); + } + + i64::try_from(days.0).ok().and_then(|d| self.diff_days(d)) + } + + /// Subtract a duration in [`Days`] from the date + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd(2022, 2, 20).checked_sub_days(Days::new(6)), + /// Some(NaiveDate::from_ymd(2022, 2, 14)) + /// ); + /// ``` + pub fn checked_sub_days(self, days: Days) -> Option { + if days.0 == 0 { + return Some(self); + } + + i64::try_from(days.0).ok().and_then(|d| self.diff_days(-d)) + } + + fn diff_days(self, days: i64) -> Option { + self.checked_add_signed(Duration::days(days)) + } + /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. /// /// # Example @@ -1718,6 +1781,22 @@ impl Sub for NaiveDate { } } +impl Add for NaiveDate { + type Output = NaiveDate; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).unwrap() + } +} + +impl Sub for NaiveDate { + type Output = NaiveDate; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).unwrap() + } +} + /// A subtraction of `Duration` from `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 577fe87d00..c0e851e4e3 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -17,7 +17,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; -use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; @@ -662,6 +662,20 @@ impl NaiveDateTime { Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.time }) } + /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + pub fn checked_add_days(self, days: Days) -> Option { + Some(Self { date: self.date.checked_add_days(days)?, ..self }) + } + + /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + pub fn checked_sub_days(self, days: Days) -> Option { + Some(Self { date: self.date.checked_sub_days(days)?, ..self }) + } + /// Subtracts another `NaiveDateTime` from the current date and time. /// This does not overflow or underflow at all. /// @@ -1537,6 +1551,22 @@ impl Sub for NaiveDateTime { } } +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).unwrap() + } +} + +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).unwrap() + } +} + /// The `Debug` output of the naive date and time `dt` is the same as /// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](crate::format::strftime). /// diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f7cb298b66..c41acba8d3 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -11,7 +11,7 @@ mod isoweek; mod time; #[allow(deprecated)] -pub use self::date::{NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; +pub use self::date::{Days, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] #[allow(deprecated)] pub use self::datetime::rustc_serialize::TsSeconds; From 9a2ba953885cf939c3f6a0bfb8e9fd1e13b12882 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 23 Aug 2022 22:33:05 +1000 Subject: [PATCH 126/999] to -> from --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 48eb85001a..7131279bc3 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -376,7 +376,7 @@ impl DateTime { .single() } - /// Subtract a duration in [`Days`] to 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. pub fn checked_sub_days(self, days: Days) -> Option { From 42ca8ca88df0ef205d690170f6b43444483d7ba8 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 26 Aug 2022 21:28:50 +1000 Subject: [PATCH 127/999] duplicate add/sub days tests --- src/naive/date.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index cc674c70b8..27dcafbb6e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2235,11 +2235,14 @@ mod serde { #[cfg(test)] mod tests { use super::{ - Months, NaiveDate, MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR, + Days, Months, NaiveDate, MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR, }; use crate::oldtime::Duration; use crate::{Datelike, Weekday}; - use std::{i32, u32}; + use std::{ + convert::{TryFrom, TryInto}, + i32, u32, + }; #[test] fn diff_months() { @@ -2682,6 +2685,51 @@ mod tests { check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64)); } + #[test] + fn test_date_add_days() { + fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) { + let lhs = NaiveDate::from_ymd(y1, m1, d1); + let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd(y, m, d)); + assert_eq!(lhs.checked_add_days(rhs), sum); + } + + check((2014, 1, 1), Days::new(0), Some((2014, 1, 1))); + // always round towards zero + check((2014, 1, 1), Days::new(1), Some((2014, 1, 2))); + check((2014, 1, 1), Days::new(364), Some((2014, 12, 31))); + check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1))); + check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1))); + + check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1))); + + // overflow check + check( + (0, 1, 1), + Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), + Some((MAX_YEAR, 12, 31)), + ); + check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); + } + + #[test] + fn test_date_sub_days() { + fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) { + let lhs = NaiveDate::from_ymd(y1, m1, d1); + let rhs = NaiveDate::from_ymd(y2, m2, d2); + assert_eq!(lhs - diff, rhs); + } + + check((2014, 1, 1), (2014, 1, 1), Days::new(0)); + check((2014, 1, 2), (2014, 1, 1), Days::new(1)); + check((2014, 12, 31), (2014, 1, 1), Days::new(364)); + check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2)); + check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1)); + check((2414, 1, 1), (2014, 1, 1), Days::new(365 * 400 + 97)); + + check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap())); + check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); + } + #[test] fn test_date_addassignment() { let ymd = NaiveDate::from_ymd; From 926f7bf2885b0097840309022732a116258b6108 Mon Sep 17 00:00:00 2001 From: Pavel Pscheidl Date: Sun, 11 Sep 2022 21:05:51 +0200 Subject: [PATCH 128/999] NaiveDateTime::from_timestamp_millis(_opt) Construct NaiveDateTime from millis since epoch --- src/naive/datetime/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c0e851e4e3..acc25ebdb5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -5,6 +5,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; +use core::convert::TryFrom; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -39,6 +40,11 @@ mod tests; /// touching that call when we are already sure that it WILL overflow... const MAX_SECS_BITS: usize = 44; +/// Number of nanoseconds in a millisecond +const NANOS_IN_MILLISECOND: u32 = 1_000_000; +/// Number of nanoseconds in a second +const NANOS_IN_SECOND: u32 = 1000 * NANOS_IN_MILLISECOND; + /// The minimum possible `NaiveDateTime`. #[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; @@ -131,6 +137,43 @@ impl NaiveDateTime { datetime.expect("invalid or out-of-range datetime") } + /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// Returns `None` on an out-of-range number of milliseconds. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDateTime; + /// let timestamp_millis: i64 = 1662921288; //Sunday, September 11, 2022 6:34:48 PM + /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_millis: i64 = -2208936075; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); + /// ``` + #[inline] + pub fn from_timestamp_millis(millis: i64) -> Option { + let secs = millis / 1000; + + if millis < 0 { + let nsecs = u32::try_from((millis % 1000).abs()).ok()? * NANOS_IN_MILLISECOND; + NaiveDateTime::from_timestamp_opt( + secs.checked_sub(1)?, + NANOS_IN_SECOND.checked_sub(nsecs)?, + ) + } else { + let nsecs = u32::try_from(millis % 1000).ok()? * NANOS_IN_MILLISECOND; + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + } + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, /// from the number of non-leap seconds /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") From f90a0ffb98a128d29031854eea62e76312e2be25 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 19 Sep 2022 11:40:08 +0200 Subject: [PATCH 129/999] Deduplicate logic in from_timestamp_millis() --- src/naive/datetime/mod.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index acc25ebdb5..3e92895a45 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -160,18 +160,18 @@ impl NaiveDateTime { /// ``` #[inline] pub fn from_timestamp_millis(millis: i64) -> Option { - let secs = millis / 1000; - + let mut secs = millis / 1000; if millis < 0 { - let nsecs = u32::try_from((millis % 1000).abs()).ok()? * NANOS_IN_MILLISECOND; - NaiveDateTime::from_timestamp_opt( - secs.checked_sub(1)?, - NANOS_IN_SECOND.checked_sub(nsecs)?, - ) - } else { - let nsecs = u32::try_from(millis % 1000).ok()? * NANOS_IN_MILLISECOND; - NaiveDateTime::from_timestamp_opt(secs, nsecs) + secs = secs.checked_sub(1)?; + } + + let nsecs = (millis % 1000).abs(); + let mut nsecs = u32::try_from(nsecs).ok()? * NANOS_IN_MILLISECOND; + if secs < 0 { + nsecs = NANOS_IN_SECOND.checked_sub(nsecs)?; } + + NaiveDateTime::from_timestamp_opt(secs, nsecs) } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, From 147089b78f1ffde1f3ef2202920cab372bf6daf4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 19 Sep 2022 14:08:11 +0200 Subject: [PATCH 130/999] Run CI on PRs to 0.4.x branch --- .github/workflows/lint.yml | 3 +-- .github/workflows/test-release.yml | 4 +--- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4c1d62bab7..55e6c6ce7f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,9 +2,8 @@ name: lint on: push: - branches: [main, master] + branches: [main, master, 0.4.x] pull_request: - branches: [main, master] jobs: lint: diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 67de1c1a18..f10b3ee5e4 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -2,9 +2,7 @@ name: Release Test on: push: - branches: ["rel*"] - pull_request: - branches: ["rel*"] + branches: ["rel*", "0.4.x"] # From here down this should be exactly the same as test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9f39f947d..dfb40f0c08 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: All Tests and Builds on: push: - branches: [main] + branches: [main, 0.4.x] pull_request: paths: - "**.rs" From cb073a0db75abef50ce4b25f8971189d9e473f2b Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:02:04 +1000 Subject: [PATCH 131/999] move test against date command to integration tests --- src/offset/local/mod.rs | 78 +---------------------------------------- tests/dateutils.rs | 78 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 77 deletions(-) create mode 100644 tests/dateutils.rs diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 5a546de99e..0024cf0c69 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -173,83 +173,7 @@ impl TimeZone for Local { mod tests { use super::Local; use crate::offset::TimeZone; - use crate::{Datelike, Duration, NaiveDate, NaiveDateTime, Timelike}; - - use std::{path, process}; - - #[cfg(unix)] - fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { - let output = process::Command::new(path) - .arg("-d") - .arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour())) - .arg("+%Y-%m-%d %H:%M:%S %:z") - .output() - .unwrap(); - - let date_command_str = String::from_utf8(output.stdout).unwrap(); - - // The below would be preferred. At this stage neither earliest() or latest() - // seems to be consistent with the output of the `date` command, so we simply - // compare both. - // let local = Local - // .from_local_datetime(&NaiveDate::from_ymd(year, month, day).and_hms(hour, 5, 1)) - // // looks like the "date" command always returns a given time when it is ambiguous - // .earliest(); - - // if let Some(local) = local { - // assert_eq!(format!("{}\n", local), date_command_str); - // } else { - // // we are in a "Spring forward gap" due to DST, and so date also returns "" - // assert_eq!("", date_command_str); - // } - - // This is used while a decision is made wheter the `date` output needs to - // be exactly matched, or whether LocalResult::Ambigious should be handled - // differently - match Local.from_local_datetime( - &NaiveDate::from_ymd(dt.year(), dt.month(), dt.day()).and_hms(dt.hour(), 5, 1), - ) { - crate::LocalResult::Ambiguous(a, b) => { - assert!( - format!("{}\n", a) == date_command_str - || format!("{}\n", b) == date_command_str - ) - } - crate::LocalResult::Single(a) => { - assert_eq!(format!("{}\n", a), date_command_str); - } - crate::LocalResult::None => { - assert_eq!("", date_command_str); - } - } - } - - #[test] - #[cfg(unix)] - fn try_verify_against_date_command() { - let date_path = "/usr/bin/date"; - - if !path::Path::new(date_path).exists() { - // date command not found, skipping - // avoid running this on macOS, which has path /bin/date - // as the required CLI arguments are not present in the - // macOS build. - return; - } - - let mut date = NaiveDate::from_ymd(1975, 1, 1).and_hms(0, 0, 0); - - while date.year() < 2078 { - if (1975..=1977).contains(&date.year()) - || (2020..=2022).contains(&date.year()) - || (2073..=2077).contains(&date.year()) - { - verify_against_date_command_local(date_path, date); - } - - date += crate::Duration::hours(1); - } - } + use crate::{Datelike, Duration}; #[test] fn verify_correct_offsets() { diff --git a/tests/dateutils.rs b/tests/dateutils.rs new file mode 100644 index 0000000000..d2d3aaf5c8 --- /dev/null +++ b/tests/dateutils.rs @@ -0,0 +1,78 @@ +use chrono::offset::TimeZone; +use chrono::Local; +use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; + +use std::{path, process}; + +#[cfg(unix)] +fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { + let output = process::Command::new(path) + .arg("-d") + .arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour())) + .arg("+%Y-%m-%d %H:%M:%S %:z") + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + + // The below would be preferred. At this stage neither earliest() or latest() + // seems to be consistent with the output of the `date` command, so we simply + // compare both. + // let local = Local + // .from_local_datetime(&NaiveDate::from_ymd(year, month, day).and_hms(hour, 5, 1)) + // // looks like the "date" command always returns a given time when it is ambiguous + // .earliest(); + + // if let Some(local) = local { + // assert_eq!(format!("{}\n", local), date_command_str); + // } else { + // // we are in a "Spring forward gap" due to DST, and so date also returns "" + // assert_eq!("", date_command_str); + // } + + // This is used while a decision is made wheter the `date` output needs to + // be exactly matched, or whether LocalResult::Ambigious should be handled + // differently + match Local.from_local_datetime(&NaiveDate::from_ymd(dt.year(), dt.month(), dt.day()).and_hms( + dt.hour(), + 5, + 1, + )) { + chrono::LocalResult::Ambiguous(a, b) => assert!( + format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str + ), + chrono::LocalResult::Single(a) => { + assert_eq!(format!("{}\n", a), date_command_str); + } + chrono::LocalResult::None => { + assert_eq!("", date_command_str); + } + } +} + +#[test] +#[cfg(unix)] +fn try_verify_against_date_command() { + let date_path = "/usr/bin/date"; + + if !path::Path::new(date_path).exists() { + // date command not found, skipping + // avoid running this on macOS, which has path /bin/date + // as the required CLI arguments are not present in the + // macOS build. + return; + } + + let mut date = NaiveDate::from_ymd(1975, 1, 1).and_hms(0, 0, 0); + + while date.year() < 2078 { + if (1975..=1977).contains(&date.year()) + || (2020..=2022).contains(&date.year()) + || (2073..=2077).contains(&date.year()) + { + verify_against_date_command_local(date_path, date); + } + + date += chrono::Duration::hours(1); + } +} From eb82ae603d41eb6e5893b2be41c3b6a843643aff Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:03:36 +1000 Subject: [PATCH 132/999] adjust lint workflow --- .github/workflows/lint.yml | 23 +++++++++++------------ ci/lint.sh | 15 --------------- 2 files changed, 11 insertions(+), 27 deletions(-) delete mode 100755 ci/lint.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 55e6c6ce7f..5ef8d54a77 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,8 +2,13 @@ name: lint on: push: - branches: [main, master, 0.4.x] + branches: [main, 0.4.x] pull_request: + paths: + - "**.rs" + - .github/** + - .ci/** + - Cargo.toml jobs: lint: @@ -11,15 +16,9 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v1 - - name: Lint - run: bash ci/lint.sh - - name: Run ShellCheck - uses: ludeeus/action-shellcheck@0.3.0 - with: - check_together: "y" + - run: cargo fmt -- --check --color=always + - run: cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + env: + RUSTFLAGS: "-Dwarnings" diff --git a/ci/lint.sh b/ci/lint.sh deleted file mode 100755 index f028c2112c..0000000000 --- a/ci/lint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# shellcheck source=ci/_shlib.sh -source "${BASH_SOURCE[0]%/*}/_shlib.sh" - -main() { - # rustflags are present because of: https://github.com/rust-lang/rust-clippy/issues/5749 - runt env RUSTFLAGS="-Dwarnings" cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive - runt cargo fmt -- --check --color=always - runv git diff --exit-code -- README.md -} - -main From 2827c22762f9fd881f38c041cdd1626020263de4 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:08:58 +1000 Subject: [PATCH 133/999] split test job into timezones, timezones linux, rust versions and powerset features check --- .github/workflows/test.yml | 81 +++++++++++++++++++------------------- ci/github.sh | 41 ------------------- 2 files changed, 40 insertions(+), 82 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfb40f0c08..88d776a4f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,58 +11,57 @@ on: - Cargo.toml jobs: - test: + timezones_linux: strategy: matrix: - os: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] - rust_version: [stable] - exhaustive_tz: [onetz] - check_combinatoric: [no_combinatoric] - include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # compilation check - - os: ubuntu-latest - rust_version: stable - check_combinatoric: "combinatoric" - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-20.04 - rust_version: 1.38.0 - - os: macos-latest - rust_version: 1.38.0 - - os: windows-latest - rust_version: 1.38.0 - + os: [ubuntu-latest] + tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v1 + - run: cargo test --all-features --color=always -- --color=always + timezones_other: + strategy: + matrix: + os: [macos-latest, windows-latest] + tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v1 + - run: cargo test --lib --all-features --color=always -- --color=always + - run: cargo test --doc --all-features --color=always -- --color=always - - name: Install rust - uses: actions-rs/toolchain@v1 + rust_versions: + strategy: + matrix: + os: [ubuntu-latest] + rust_version: ["1.38.0", "stable", "beta", "nightly"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} - override: true + - uses: Swatinem/rust-cache@v1 + - run: cargo test --lib --features serde --color=always -- --color=always + - run: cargo test --doc --features serde --color=always -- --color=always + features_check: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v1 - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: ${{ matrix.rust_version }} - EXHAUSTIVE_TZ: ${{ matrix.exhaustive_tz }} - CHECK_COMBINATORIC: ${{ matrix.check_combinatoric }} + - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales no_std: strategy: diff --git a/ci/github.sh b/ci/github.sh index 3c9ffb17a6..b53c100166 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -5,11 +5,6 @@ set -euo pipefail # shellcheck source=ci/_shlib.sh source "${BASH_SOURCE[0]%/*}/_shlib.sh" -TEST_TZS=(ACST-9:30 EST4 UTC0 Asia/Katmandu) -FEATURES=(std serde clock "alloc serde" unstable-locales) -CHECK_FEATURES=(alloc "std unstable-locales" "serde clock" "clock unstable-locales") -RUST_132_FEATURES=(rustc-serialize serde) - main() { if [[ "$*" =~ "-h" ]]; then echo -n "usage: ENV_VARS... $0 @@ -44,51 +39,15 @@ meaningful in the github actions feature matrix UI. test_wasm_wasi elif [[ ${CORE:-} == no_std ]]; then test_core - elif [[ ${EXHAUSTIVE_TZ:-} == all_tzs ]]; then - test_all_tzs - elif [[ ${CHECK_COMBINATORIC:-} == combinatoric ]]; then - check_combinatoric else test_regular UTC0 fi - elif [[ ${RUST_VERSION:-} == 1.38.0 ]]; then - test_132 else echo "ERROR: didn't run any tests" exit 1 fi } -test_all_tzs() { - for tz in "${TEST_TZS[@]}"; do - test_regular "$tz" - done -} - -test_regular() { - tz="$1" && shift - - runt env TZ="$tz" cargo test --features __doctest,unstable-locales --color=always -- --color=always - for feature in "${FEATURES[@]}"; do - runt env TZ="$tz" cargo test --no-default-features --features "$feature" --lib --color=always -- --color=always - done -} - -check_combinatoric() { - runt cargo check --no-default-features - runt cargo check --all-features - for feature in "${CHECK_FEATURES[@]}"; do - runt cargo check --no-default-features --features "$feature" --lib --color=always - done -} - -test_132() { - runv cargo build --color=always - for feature in "${RUST_132_FEATURES[@]}"; do - runt cargo build --features "$feature" --color=always - done -} - test_core() { ( cd ci/core-test From bbf5168f133a2b211e4fe8426192bda85ff3ea31 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:09:16 +1000 Subject: [PATCH 134/999] consolidate no_std --- .github/workflows/test.yml | 37 ++++--------------------------------- ci/github.sh | 9 --------- 2 files changed, 4 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 88d776a4f3..e76d6008b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,47 +64,18 @@ jobs: - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales no_std: - strategy: - matrix: - os: [macos-latest, ubuntu-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust with no_std toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv6m-none-eabi - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build no_std lib - run: cargo build --target thumbv6m-none-eabi --color=always - working-directory: ./ci/core-test - - intel_sgx: strategy: matrix: os: [ubuntu-latest] - + target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} - steps: - uses: actions/checkout@v2 - - - name: Install rust with fortanix sgx toolchain - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly - target: x86_64-fortanix-unknown-sgx - override: true + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v1 - - - name: Build sgx lib - run: cargo build --target x86_64-fortanix-unknown-sgx --color=always + - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test wasm: diff --git a/ci/github.sh b/ci/github.sh index b53c100166..f5b1fe3147 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -37,8 +37,6 @@ meaningful in the github actions feature matrix UI. test_wasm_unknown_no_wasmbind elif [[ ${WASM:-} == wasm_wasi ]]; then test_wasm_wasi - elif [[ ${CORE:-} == no_std ]]; then - test_core else test_regular UTC0 fi @@ -48,13 +46,6 @@ meaningful in the github actions feature matrix UI. fi } -test_core() { - ( - cd ci/core-test - runt cargo build --target thumbv6m-none-eabi --color=always - ) -} - test_wasm() { if ! command -v node; then echo "node is not installed, can't run wasm-pack tests" From e2dca90c19a1d990309012a0d738af1fd67f823d Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:10:08 +1000 Subject: [PATCH 135/999] consolidate wasm, add wasm features check --- .github/workflows/test.yml | 130 +++++-------------------------------- ci/_shlib.sh | 19 ------ ci/github.sh | 89 ------------------------- 3 files changed, 15 insertions(+), 223 deletions(-) delete mode 100644 ci/_shlib.sh delete mode 100755 ci/github.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e76d6008b4..756452745b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,140 +78,40 @@ jobs: - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test - wasm: + alternative_targets: strategy: matrix: - os: [macos-latest] - + os: [ubuntu-latest] + target: [wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android] runs-on: ${{ matrix.os }} - steps: - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - target: wasm32-unknown-unknown - override: true + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v1 - - - name: Install node - uses: actions/setup-node@v1 + - uses: actions/setup-node@v1 with: node-version: "12" - - - name: Install wasm-pack - run: | + - run: | export RUST_BACKTRACE=1 curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh wasm-pack --version - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_simple - - wasm_emscripten: - strategy: - matrix: - os: [macos-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-emscripten - override: true - - - name: Install node - uses: actions/setup-node@v1 - with: - node-version: "12" - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_emscripten - - wasm_unknown: + - run: cargo build --target ${{ matrix.target }} --color=always + + features_check_wasm: strategy: matrix: os: [ubuntu-latest] - runs-on: ${{ matrix.os }} - steps: - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - default: true - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_unknown - - wasm_unknown_no_wasmbind: - strategy: - matrix: - os: [ubuntu-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - default: true - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_unknown_no_wasmbind - - wasm_wasi: - strategy: - matrix: - os: [ubuntu-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - target: wasm32-wasi - override: true - default: true - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_wasi + targets: wasm32-unknown-unknown + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v1 + - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales cross-targets: strategy: diff --git a/ci/_shlib.sh b/ci/_shlib.sh deleted file mode 100644 index 74f5c383b1..0000000000 --- a/ci/_shlib.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -# Use github workflow commands to make sections of the tests more obvious -# https://help.github.com/en/actions/reference/workflow-commands-for-github-actions - - -# Run a test as a "group" -- output will be folded and hidden by default -runt() { - echo "::group::$*" - # stdout is occasionally swallowed by gh actions - "$@" >&2 - echo "::endgroup::$*" -} - -runv() { - echo "🚀>$ $*" >&2 - # stdout is occasionally swallowed by gh actions - "$@" >&2 -} diff --git a/ci/github.sh b/ci/github.sh deleted file mode 100755 index f5b1fe3147..0000000000 --- a/ci/github.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# shellcheck source=ci/_shlib.sh -source "${BASH_SOURCE[0]%/*}/_shlib.sh" - -main() { - if [[ "$*" =~ "-h" ]]; then - echo -n "usage: ENV_VARS... $0 - -Recognized environment variables. Their values are as they are so that they are -meaningful in the github actions feature matrix UI. - - RUST_VERSION The rust version currently being tested - This doesn't set the version, it is just used to test - WASM Empty or 'yes_wasm' - CORE 'std' or 'no_std' - EXHAUSTIVE_TZ Emptly or 'all_tzs' - CHECK_COMBINATORIC Combine various features and verify that we compile -" - exit - fi - - runv cargo --version - - if [[ ${RUST_VERSION:-} != 1.38.0 ]]; then - if [[ ${WASM:-} == yes_wasm ]]; then - test_wasm - elif [[ ${WASM:-} == wasm_simple ]]; then - test_wasm_simple - elif [[ ${WASM:-} == wasm_emscripten ]]; then - test_wasm_emscripten - elif [[ ${WASM:-} == wasm_unknown ]]; then - test_wasm_unknown - elif [[ ${WASM:-} == wasm_unknown_no_wasmbind ]]; then - test_wasm_unknown_no_wasmbind - elif [[ ${WASM:-} == wasm_wasi ]]; then - test_wasm_wasi - else - test_regular UTC0 - fi - else - echo "ERROR: didn't run any tests" - exit 1 - fi -} - -test_wasm() { - if ! command -v node; then - echo "node is not installed, can't run wasm-pack tests" - exit 1 - fi - if ! command -v wasm-pack >/dev/null; then - echo "::group::curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh" - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - runv wasm-pack --version - fi - test_wasm_simple -} - -test_wasm_simple() { - if ! runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node ; then - # sometimes on github the initial build takes 8-10 minutes, and we - # check that the time makes sense inside the test by approximately - # comparing it to the env var, - # - # so re-run the test in case it took too long - runt env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node - fi -} - -test_wasm_emscripten() { - runt cargo build --target wasm32-unknown-emscripten -} - -test_wasm_unknown() { - runt cargo build --target wasm32-unknown-unknown -} - -test_wasm_unknown_no_wasmbind() { - runt cargo build --target wasm32-unknown-unknown --no-default-features --features clock,std -} - -test_wasm_wasi() { - runt cargo build --target wasm32-wasi -} - -main "$@" From c59235aca20e096d4d0f9acad148473159bc5a71 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:10:28 +1000 Subject: [PATCH 136/999] update cross-targets --- .github/workflows/test.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 756452745b..56347c62a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -118,18 +118,12 @@ jobs: matrix: target: - x86_64-sun-solaris - runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - - name: Install cross - run: cargo install cross - + - run: cargo install cross - uses: Swatinem/rust-cache@v1 - - - name: Build static library - run: cross check --target ${{ matrix.target }} + - run: cross check --target ${{ matrix.target }} check-docs: runs-on: ubuntu-latest From 2474b02198d1fab577e73f2db01840211aee149b Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:10:46 +1000 Subject: [PATCH 137/999] update check-docs --- .github/workflows/test.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 56347c62a4..4a8eca55cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -127,15 +127,9 @@ jobs: check-docs: runs-on: ubuntu-latest - name: Check doc links steps: - uses: actions/checkout@v2 - - name: Install rust nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - - name: Check docs build without warnings + - uses: dtolnay/rust-toolchain@nightly + - run: cargo +nightly doc --all-features --no-deps env: RUSTDOCFLAGS: "-D warnings --cfg docsrs" - run: cargo +nightly doc --all-features --no-deps From 3421cbdeeb89cb40c1d8e72d86910c506ea5edb8 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:11:06 +1000 Subject: [PATCH 138/999] extra imports due to powerset features check --- src/format/mod.rs | 1 + src/format/scan.rs | 4 ++-- src/format/strftime.rs | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index ff0363bb94..e12f3efb01 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -63,6 +63,7 @@ pub use pure_rust_locales::Locale; pub use strftime::StrftimeItems; #[cfg(not(feature = "unstable-locales"))] +#[allow(dead_code)] #[derive(Debug)] struct Locale; diff --git a/src/format/scan.rs b/src/format/scan.rs index 7334a3b2ed..263fec556d 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -404,10 +404,10 @@ fn test_rfc2822_comments() { ("( x ( x ) x ( x ) x )", Ok("")), ]; - for (test_in, expected) in testdata { + for (test_in, expected) in testdata.iter() { let actual = comment_2822(test_in).map(|(s, _)| s); assert_eq!( - expected, actual, + *expected, actual, "{:?} expected to produce {:?}, but produced {:?}.", test_in, expected, actual ); diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 31509a0ff4..b41ab2e1c7 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -174,6 +174,12 @@ Notes: China Daylight Time. */ +#[cfg(feature = "unstable-locales")] +extern crate alloc; + +#[cfg(feature = "unstable-locales")] +use alloc::vec::Vec; + #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; From 9e1836cd494741bc0e2e5d4e572db18da6c15121 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Fri, 2 Sep 2022 22:11:24 +1000 Subject: [PATCH 139/999] remove old ci references and supporting files --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e42224f75..e28c60a8d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,9 @@ keywords = ["date", "time", "calendar"] categories = ["date-and-time"] readme = "README.md" license = "MIT/Apache-2.0" -exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"] +exclude = ["/ci/*"] edition = "2018" -[badges] -travis-ci = { repository = "chronotope/chrono" } -appveyor = { repository = "chronotope/chrono" } - [lib] name = "chrono" From 7d1060e141f2a8e425525459fd5117e80fd4b507 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 18 Sep 2022 16:06:52 +1000 Subject: [PATCH 140/999] use --all-features when testing other timezones and rust_versions, test 1.38.0 by itself with less features fix 1.38.0 tests add back default features in rkyv --- .github/workflows/test.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a8eca55cb..63e7c81c5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,11 +36,30 @@ jobs: - run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always + # later this may be able to be included with the below + # kept seperate for now as the following don't compile on 1.38.0 + # * rkyv + # * criterion + rust_msrv: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.38.0 + - uses: Swatinem/rust-cache@v1 + # run --lib and --doc to avoid the long running integration tests which are run elsewhere + - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always + - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always + rust_versions: strategy: matrix: os: [ubuntu-latest] - rust_version: ["1.38.0", "stable", "beta", "nightly"] + rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -48,8 +67,9 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v1 - - run: cargo test --lib --features serde --color=always -- --color=always - - run: cargo test --doc --features serde --color=always -- --color=always + # run --lib and --doc to avoid the long running integration tests which are run elsewhere + - run: cargo test --lib --all-features --color=always -- --color=always + - run: cargo test --doc --all-features --color=always -- --color=always features_check: strategy: From 2068695b55945086f2bab1580f8115f79d292f76 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 26 Sep 2022 12:56:19 +0200 Subject: [PATCH 141/999] Remove test-release workflow --- .github/workflows/test-release.yml | 111 ----------------------------- 1 file changed, 111 deletions(-) delete mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml deleted file mode 100644 index f10b3ee5e4..0000000000 --- a/.github/workflows/test-release.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Release Test - -on: - push: - branches: ["rel*", "0.4.x"] - -# From here down this should be exactly the same as test.yml - -jobs: - test: - strategy: - matrix: - os: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] - rust_version: [stable] - include: - # check all tzs on most-recent OS's - - os: ubuntu-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: windows-latest - rust_version: stable - exhaustive_tz: all_tzs - - os: macos-latest - rust_version: stable - exhaustive_tz: all_tzs - # test other rust versions - - os: ubuntu-latest - rust_version: beta - - os: ubuntu-latest - rust_version: nightly - - os: ubuntu-20.04 - rust_version: 1.38.0 - - os: macos-latest - rust_version: 1.38.0 - - os: windows-latest - rust_version: 1.38.0 - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust_version }} - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: ${{ matrix.rust_version }} - EXHAUSTIVE_TZ: ${{ matrix.exhaustive_tz }} - - no_std: - strategy: - matrix: - os: [macos-latest, ubuntu-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust with no_std toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv6m-none-eabi - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Build no_std lib - run: cargo build --target thumbv6m-none-eabi --color=always - working-directory: ./ci/core-test - - wasm: - strategy: - matrix: - os: [macos-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - - uses: Swatinem/rust-cache@v1 - - - name: Install node - uses: actions/setup-node@v1 - with: - node-version: "12" - - - name: Install wasm-pack - run: | - export RUST_BACKTRACE=1 - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - wasm-pack --version - - - name: Build and Test - run: bash ci/github.sh - env: - RUST_VERSION: stable - WASM: wasm_simple From 001b19c26239982b78b77bee4ba7402f21e7dcff Mon Sep 17 00:00:00 2001 From: Qiu Chaofan Date: Tue, 30 Aug 2022 14:30:42 +0800 Subject: [PATCH 142/999] Support AIX's TZDB location --- src/offset/local/unix.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 46845b69d4..3ca2f4a6a5 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -99,8 +99,11 @@ struct Cache { #[cfg(target_os = "android")] const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo"; +#[cfg(target_os = "aix")] +const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo"; + #[allow(dead_code)] // keeps the cfg simpler -#[cfg(not(target_os = "android"))] +#[cfg(not(any(target_os = "android", target_os = "aix")))] const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; fn fallback_timezone() -> Option { From a9b250764fe8502116389738fc50012d6e70a3f4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 28 Sep 2022 14:19:06 +0200 Subject: [PATCH 143/999] Deprecate methods that have an _opt() alternative --- benches/chrono.rs | 10 +-- src/date.rs | 6 ++ src/naive/date.rs | 129 ++++---------------------------------- src/naive/datetime/mod.rs | 13 +--- src/naive/time/mod.rs | 65 ++----------------- src/offset/fixed.rs | 34 +++++----- src/offset/mod.rs | 69 +++++++++----------- tests/dateutils.rs | 13 ++-- 8 files changed, 83 insertions(+), 256 deletions(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index c44a2a15b6..c0704e33cb 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -35,14 +35,14 @@ fn bench_datetime_from_str(c: &mut Criterion) { } fn bench_datetime_to_rfc2822(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); } fn bench_datetime_to_rfc3339(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); } @@ -90,7 +90,7 @@ fn num_days_from_ce_alt(date: &Date) -> i32 { fn bench_num_days_from_ce(c: &mut Criterion) { let mut group = c.benchmark_group("num_days_from_ce"); for year in &[1, 500, 2000, 2019] { - let d = NaiveDate::from_ymd(*year, 1, 1); + let d = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap(); group.bench_with_input(BenchmarkId::new("new", year), &d, |b, y| { b.iter(|| num_days_from_ce_alt(y)) }); diff --git a/src/date.rs b/src/date.rs index 5d30d887dc..2a18110c8c 100644 --- a/src/date.rs +++ b/src/date.rs @@ -93,6 +93,7 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")] #[inline] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") @@ -112,6 +113,7 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")] #[inline] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") @@ -138,6 +140,7 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or microsecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")] #[inline] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") @@ -164,6 +167,7 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")] #[inline] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") @@ -188,6 +192,7 @@ impl Date { /// Makes a new `Date` for the next date. /// /// Panics when `self` is the last representable date. + #[deprecated(since = "0.4.23", note = "Use succ_opt() instead")] #[inline] pub fn succ(&self) -> Date { self.succ_opt().expect("out of bound") @@ -204,6 +209,7 @@ impl Date { /// Makes a new `Date` for the prior date. /// /// Panics when `self` is the first representable date. + #[deprecated(since = "0.4.23", note = "Use pred_opt() instead")] #[inline] pub fn pred(&self) -> Date { self.pred_opt().expect("out of bound") diff --git a/src/naive/date.rs b/src/naive/date.rs index 27dcafbb6e..09a4c23d88 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -241,22 +241,7 @@ impl NaiveDate { /// (year, month and day). /// /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 3, 14); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { NaiveDate::from_ymd_opt(year, month, day).expect("invalid or out-of-range date") } @@ -289,22 +274,7 @@ impl NaiveDate { /// (year and day of the year). /// /// Panics on the out-of-range date and/or invalid day of year. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_yo(2015, 73); - /// assert_eq!(d.ordinal(), 73); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate { NaiveDate::from_yo_opt(year, ordinal).expect("invalid or out-of-range date") } @@ -339,22 +309,7 @@ impl NaiveDate { /// The resulting `NaiveDate` may have a different year from the input year. /// /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_isoywd(2015, 11, Weekday::Sat); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { NaiveDate::from_isoywd_opt(year, week, weekday).expect("invalid or out-of-range date") } @@ -438,45 +393,7 @@ impl NaiveDate { /// January 1, 1 being day 1. /// /// Panics if the date is out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_num_days_from_ce(735671); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// ``` - /// - /// While not directly supported by Chrono, - /// it is easy to convert from the Julian day number - /// (January 1, 4713 BCE in the *Julian* calendar being Day 0) - /// to Gregorian with this method. - /// (Note that this panics when `jd` is out of range.) - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// fn jd_to_date(jd: i32) -> NaiveDate { - /// // keep in mind that the Julian day number is 0-based - /// // while this method requires an 1-based number. - /// NaiveDate::from_num_days_from_ce(jd - 1721425) - /// } - /// - /// // January 1, 4713 BCE in Julian = November 24, 4714 BCE in Gregorian - /// assert_eq!(jd_to_date(0), NaiveDate::from_ymd(-4713, 11, 24)); - /// - /// assert_eq!(jd_to_date(1721426), NaiveDate::from_ymd(1, 1, 1)); - /// assert_eq!(jd_to_date(2450000), NaiveDate::from_ymd(1995, 10, 9)); - /// assert_eq!(jd_to_date(2451545), NaiveDate::from_ymd(2000, 1, 1)); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] #[inline] pub fn from_num_days_from_ce(days: i32) -> NaiveDate { NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") @@ -493,7 +410,7 @@ impl NaiveDate { /// use chrono::NaiveDate; /// /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); @@ -520,21 +437,7 @@ impl NaiveDate { /// 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. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let from_weekday_of_month = NaiveDate::from_weekday_of_month; - /// let from_ymd = NaiveDate::from_ymd; - /// - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Wed, 1), from_ymd(2018, 8, 1)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 1), from_ymd(2018, 8, 3)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Tue, 2), from_ymd(2018, 8, 14)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 4), from_ymd(2018, 8, 24)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 5), from_ymd(2018, 8, 31)); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")] pub fn from_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u8) -> NaiveDate { NaiveDate::from_weekday_of_month_opt(year, month, weekday, n).expect("out-of-range date") } @@ -799,6 +702,7 @@ impl NaiveDate { /// assert_eq!(dt.weekday(), Weekday::Wed); /// assert_eq!(dt.second(), 56); /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") @@ -847,6 +751,7 @@ impl NaiveDate { /// assert_eq!(dt.second(), 56); /// assert_eq!(dt.nanosecond(), 789_000_000); /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") @@ -903,6 +808,7 @@ impl NaiveDate { /// assert_eq!(dt.second(), 56); /// assert_eq!(dt.nanosecond(), 789_012_000); /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] #[inline] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") @@ -945,20 +851,7 @@ impl NaiveDate { /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_nano(12, 34, 56, 789_012_345); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_012_345); - /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] #[inline] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") @@ -1041,6 +934,7 @@ impl NaiveDate { /// assert_eq!(NaiveDate::from_ymd(2015, 6, 30).succ(), NaiveDate::from_ymd(2015, 7, 1)); /// assert_eq!(NaiveDate::from_ymd(2015, 12, 31).succ(), NaiveDate::from_ymd(2016, 1, 1)); /// ``` + #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] pub fn succ(&self) -> NaiveDate { self.succ_opt().expect("out of bound") @@ -1077,6 +971,7 @@ impl NaiveDate { /// assert_eq!(NaiveDate::from_ymd(2015, 6, 1).pred(), NaiveDate::from_ymd(2015, 5, 31)); /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).pred(), NaiveDate::from_ymd(2014, 12, 31)); /// ``` + #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] pub fn pred(&self) -> NaiveDate { self.pred_opt().expect("out of bound") diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 3e92895a45..428341ee1b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -119,18 +119,7 @@ impl NaiveDateTime { /// timestamp" cannot represent a leap second unambiguously.) /// /// Panics on the out-of-range number of seconds and/or invalid nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; - /// - /// let dt = NaiveDateTime::from_timestamp(0, 42_000_000); - /// assert_eq!(dt, NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 0, 42)); - /// - /// let dt = NaiveDateTime::from_timestamp(1_000_000_000, 0); - /// assert_eq!(dt, NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40)); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")] #[inline] pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index a0345f8fc7..3132a0048f 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -201,18 +201,7 @@ impl NaiveTime { /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. /// /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 0); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] #[inline] pub fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { NaiveTime::from_hms_opt(hour, min, sec).expect("invalid time") @@ -249,18 +238,7 @@ impl NaiveTime { /// in order to represent the [leap second](#leap-second-handling). /// /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_milli(23, 56, 4, 12); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_000_000); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] #[inline] pub fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { NaiveTime::from_hms_milli_opt(hour, min, sec, milli).expect("invalid time") @@ -301,18 +279,7 @@ impl NaiveTime { /// in order to represent the [leap second](#leap-second-handling). /// /// Panics on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_micro(23, 56, 4, 12_345); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_000); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] #[inline] pub fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { NaiveTime::from_hms_micro_opt(hour, min, sec, micro).expect("invalid time") @@ -351,18 +318,7 @@ impl NaiveTime { /// in order to represent the [leap second](#leap-second-handling). /// /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] #[inline] pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { NaiveTime::from_hms_nano_opt(hour, min, sec, nano).expect("invalid time") @@ -405,18 +361,7 @@ impl NaiveTime { /// in order to represent the [leap second](#leap-second-handling). /// /// Panics on invalid number of seconds and/or nanosecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_num_seconds_from_midnight(86164, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ``` + #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] #[inline] pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index df1a990bac..0dc047e6dd 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -33,6 +33,15 @@ impl FixedOffset { /// The negative `secs` means the Western Hemisphere. /// /// Panics on the out-of-bound `secs`. + #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")] + pub fn east(secs: i32) -> FixedOffset { + FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") + } + + /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. + /// The negative `secs` means the Western Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. /// /// # Example /// @@ -43,14 +52,6 @@ impl FixedOffset { /// .and_hms(0, 0, 0); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` - pub fn east(secs: i32) -> FixedOffset { - FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") - } - - /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. - /// The negative `secs` means the Western Hemisphere. - /// - /// Returns `None` on the out-of-bound `secs`. pub fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) @@ -63,6 +64,15 @@ impl FixedOffset { /// The negative `secs` means the Eastern Hemisphere. /// /// Panics on the out-of-bound `secs`. + #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")] + pub fn west(secs: i32) -> FixedOffset { + FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") + } + + /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. + /// The negative `secs` means the Eastern Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. /// /// # Example /// @@ -73,14 +83,6 @@ impl FixedOffset { /// .and_hms(0, 0, 0); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` - pub fn west(secs: i32) -> FixedOffset { - FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") - } - - /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. - /// The negative `secs` means the Eastern Hemisphere. - /// - /// Returns `None` on the out-of-bound `secs`. pub fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index eb89680ae9..9bf7480d2a 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -213,14 +213,7 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.ymd(2015, 5, 15).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated(since = "0.4.23", note = "use `ymd_opt()` instead")] fn ymd(&self, year: i32, month: u32, day: u32) -> Date { self.ymd_opt(year, month, day).unwrap() } @@ -255,14 +248,7 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid DOY. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.yo(2015, 135).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated(since = "0.4.23", note = "use `ymd_opt()` instead")] fn yo(&self, year: i32, ordinal: u32) -> Date { self.yo_opt(year, ordinal).unwrap() } @@ -274,6 +260,14 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid DOY. + /// + /// # Example + /// + /// ``` + /// use chrono::{Utc, TimeZone}; + /// + /// assert_eq!(Utc.yo_opt(2015, 135).unwrap().to_string(), "2015-05-15UTC"); + /// ``` fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), @@ -290,14 +284,7 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, Weekday, TimeZone}; - /// - /// assert_eq!(Utc.isoywd(2015, 20, Weekday::Fri).to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated(since = "0.4.23", note = "use `isoywd_opt()` instead")] fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date { self.isoywd_opt(year, week, weekday).unwrap() } @@ -311,6 +298,14 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid week number. + /// + /// # Example + /// + /// ``` + /// use chrono::{Utc, Weekday, TimeZone}; + /// + /// assert_eq!(Utc.isoywd_opt(2015, 20, Weekday::Fri).unwrap().to_string(), "2015-05-15UTC"); + /// ``` fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), @@ -324,14 +319,7 @@ pub trait TimeZone: Sized + Clone { /// /// Panics on the out-of-range number of seconds and/or invalid nanosecond, /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.timestamp(1431648000, 0).to_string(), "2015-05-15 00:00:00 UTC"); - /// ``` + #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime { self.timestamp_opt(secs, nsecs).unwrap() } @@ -342,6 +330,14 @@ pub trait TimeZone: Sized + Clone { /// /// Returns `LocalResult::None` on out-of-range number of seconds and/or /// invalid nanosecond, otherwise always returns `LocalResult::Single`. + /// + /// # Example + /// + /// ``` + /// use chrono::{Utc, TimeZone}; + /// + /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); + /// ``` fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { match NaiveDateTime::from_timestamp_opt(secs, nsecs) { Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), @@ -354,14 +350,7 @@ pub trait TimeZone: Sized + Clone { /// /// Panics on out-of-range number of milliseconds for a non-panicking /// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt). - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.timestamp_millis(1431648000).timestamp(), 1431648); - /// ``` + #[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")] fn timestamp_millis(&self, millis: i64) -> DateTime { self.timestamp_millis_opt(millis).unwrap() } diff --git a/tests/dateutils.rs b/tests/dateutils.rs index d2d3aaf5c8..8d0516dcda 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -33,11 +33,12 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // This is used while a decision is made wheter the `date` output needs to // be exactly matched, or whether LocalResult::Ambigious should be handled // differently - match Local.from_local_datetime(&NaiveDate::from_ymd(dt.year(), dt.month(), dt.day()).and_hms( - dt.hour(), - 5, - 1, - )) { + match Local.from_local_datetime( + &NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()) + .unwrap() + .and_hms_opt(dt.hour(), 5, 1) + .unwrap(), + ) { chrono::LocalResult::Ambiguous(a, b) => assert!( format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str ), @@ -63,7 +64,7 @@ fn try_verify_against_date_command() { return; } - let mut date = NaiveDate::from_ymd(1975, 1, 1).and_hms(0, 0, 0); + let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); while date.year() < 2078 { if (1975..=1977).contains(&date.year()) From 71b58818246dcbe356d1c3a416f847fd009629b4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 28 Sep 2022 14:20:14 +0200 Subject: [PATCH 144/999] Extract date value to avoid multi-line match scrutinee --- tests/dateutils.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 8d0516dcda..1573d8c419 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -33,12 +33,9 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // This is used while a decision is made wheter the `date` output needs to // be exactly matched, or whether LocalResult::Ambigious should be handled // differently - match Local.from_local_datetime( - &NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()) - .unwrap() - .and_hms_opt(dt.hour(), 5, 1) - .unwrap(), - ) { + + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { chrono::LocalResult::Ambiguous(a, b) => assert!( format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str ), From 7cd5e03ee40ea97c706ecf94b9ef91089edfb0bd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 28 Sep 2022 15:25:13 +0200 Subject: [PATCH 145/999] Fix up internal calls to newly deprecated methods --- README.md | 2 +- ci/core-test/src/lib.rs | 2 +- src/date.rs | 20 +- src/datetime/mod.rs | 181 ++++++--- src/datetime/rustc_serialize.rs | 2 +- src/datetime/serde.rs | 34 +- src/datetime/tests.rs | 289 +++++++++----- src/format/mod.rs | 2 +- src/format/parse.rs | 32 +- src/format/parsed.rs | 57 ++- src/format/strftime.rs | 14 +- src/lib.rs | 50 +-- src/month.rs | 12 +- src/naive/date.rs | 661 +++++++++++++++++--------------- src/naive/datetime/mod.rs | 335 ++++++++-------- src/naive/datetime/serde.rs | 39 +- src/naive/datetime/tests.rs | 50 ++- src/naive/isoweek.rs | 12 +- src/naive/time/mod.rs | 162 ++++---- src/naive/time/serde.rs | 2 +- src/naive/time/tests.rs | 119 ++++-- src/offset/fixed.rs | 32 +- src/offset/local/mod.rs | 22 +- src/offset/local/stub.rs | 2 +- src/offset/local/unix.rs | 24 +- src/offset/local/windows.rs | 5 +- src/offset/mod.rs | 16 +- src/offset/utc.rs | 8 +- src/round.rs | 122 ++++-- src/traits.rs | 10 +- tests/dateutils.rs | 2 +- tests/wasm.rs | 12 +- 32 files changed, 1380 insertions(+), 952 deletions(-) diff --git a/README.md b/README.md index d20200d1b6..7296a76fc3 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ if you want. Chrono inherently does not support an inaccurate or partial date and time representation. Any operation that can be ambiguous will return `None` in such cases. For example, "a month later" of 2014-01-30 is not well-defined -and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. +and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. Non ISO week handling is not yet supported. For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) diff --git a/ci/core-test/src/lib.rs b/ci/core-test/src/lib.rs index e311edb5bc..c4d7e9cc1a 100644 --- a/ci/core-test/src/lib.rs +++ b/ci/core-test/src/lib.rs @@ -3,5 +3,5 @@ use chrono::{TimeZone, Utc}; pub fn create_time() { - let _ = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0); + let _ = Utc.ymd_opt(2019, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); } diff --git a/src/date.rs b/src/date.rs index 2a18110c8c..d8ac15cd77 100644 --- a/src/date.rs +++ b/src/date.rs @@ -48,7 +48,7 @@ use crate::{Datelike, Weekday}; /// /// - Once constructed as a full `DateTime`, [`DateTime::date`] and other associated /// methods should return those for the original `Date`. For example, if `dt = -/// tz.ymd(y,m,d).hms(h,n,s)` were valid, `dt.date() == tz.ymd(y,m,d)`. +/// tz.ymd_opt(y,m,d).unwrap().hms(h,n,s)` were valid, `dt.date() == tz.ymd_opt(y,m,d).unwrap()`. /// /// - The date is timezone-agnostic up to one day (i.e. practically always), /// so the local date and UTC date should be equal for most cases @@ -337,7 +337,7 @@ where /// ```rust /// use chrono::prelude::*; /// - /// let date_time: Date = Utc.ymd(2017, 04, 02); + /// let date_time: Date = Utc.ymd_opt(2017, 04, 02).unwrap(); /// let formatted = format!("{}", date_time.format("%d/%m/%Y")); /// assert_eq!(formatted, "02/04/2017"); /// ``` @@ -575,20 +575,20 @@ mod tests { #[test] fn test_date_add_assign() { - let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Date::::from_utc(naivedate, Utc); let mut date_add = date; date_add += Duration::days(5); assert_eq!(date_add, date + Duration::days(5)); - let timezone = FixedOffset::east(60 * 60); + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_add = date_add.with_timezone(&timezone); assert_eq!(date_add, date + Duration::days(5)); - let timezone = FixedOffset::west(2 * 60 * 60); + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_add = date_add.with_timezone(&timezone); @@ -598,7 +598,7 @@ mod tests { #[test] #[cfg(feature = "clock")] fn test_date_add_assign_local() { - let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Local.from_utc_date(&naivedate); let mut date_add = date; @@ -609,20 +609,20 @@ mod tests { #[test] fn test_date_sub_assign() { - let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Date::::from_utc(naivedate, Utc); let mut date_sub = date; date_sub -= Duration::days(5); assert_eq!(date_sub, date - Duration::days(5)); - let timezone = FixedOffset::east(60 * 60); + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_sub = date_sub.with_timezone(&timezone); assert_eq!(date_sub, date - Duration::days(5)); - let timezone = FixedOffset::west(2 * 60 * 60); + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_sub = date_sub.with_timezone(&timezone); @@ -632,7 +632,7 @@ mod tests { #[test] #[cfg(feature = "clock")] fn test_date_sub_assign_local() { - let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); let date = Local.from_utc_date(&naivedate); let mut date_sub = date; diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 7131279bc3..28b3bd64d2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -125,15 +125,15 @@ impl DateTime { /// use chrono::naive::NaiveDate; /// use chrono::offset::{Utc, FixedOffset}; /// - /// let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); + /// let naivedatetime_utc = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); /// let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); /// - /// let timezone_east = FixedOffset::east(8 * 60 * 60); - /// let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); + /// let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let naivedatetime_east = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); /// let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); /// - /// let timezone_west = FixedOffset::west(7 * 60 * 60); - /// let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); + /// let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); + /// let naivedatetime_west = NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); /// let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); /// assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); @@ -155,12 +155,12 @@ impl DateTime { /// ``` /// use chrono::prelude::*; /// - /// let date: Date = Utc.ymd(2020, 1, 1); - /// let dt: DateTime = date.and_hms(0, 0, 0); + /// let date: Date = Utc.ymd_opt(2020, 1, 1).unwrap(); + /// let dt: DateTime = date.and_hms_opt(0, 0, 0).unwrap(); /// /// assert_eq!(dt.date(), date); /// - /// assert_eq!(dt.date().and_hms(1, 1, 1), date.and_hms(1, 1, 1)); + /// assert_eq!(dt.date().and_hms_opt(1, 1, 1).unwrap(), date.and_hms_opt(1, 1, 1).unwrap()); /// ``` #[inline] pub fn date(&self) -> Date { @@ -175,14 +175,14 @@ impl DateTime { /// ``` /// use chrono::prelude::*; /// - /// let date: DateTime = Utc.ymd(2020, 1, 1).and_hms(0, 0, 0); - /// let other: DateTime = FixedOffset::east(23).ymd(2020, 1, 1).and_hms(0, 0, 0); + /// let date: DateTime = Utc.ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// let other: DateTime = FixedOffset::east_opt(23).unwrap().ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(date.date_naive(), other.date_naive()); /// ``` #[inline] pub fn date_naive(&self) -> NaiveDate { let local = self.naive_local(); - NaiveDate::from_ymd(local.year(), local.month(), local.day()) + NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap() } /// Retrieves a time component. @@ -212,10 +212,10 @@ impl DateTime { /// use chrono::Utc; /// use chrono::TimeZone; /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); + /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); + /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// ``` #[inline] @@ -236,10 +236,10 @@ impl DateTime { /// use chrono::Utc; /// use chrono::TimeZone; /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); + /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); + /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] @@ -260,10 +260,10 @@ impl DateTime { /// use chrono::Utc; /// use chrono::TimeZone; /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); + /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); + /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); /// ``` #[inline] @@ -444,7 +444,7 @@ impl Default for DateTime { impl Default for DateTime { fn default() -> Self { - FixedOffset::west(0).from_utc_datetime(&NaiveDateTime::default()) + FixedOffset::west_opt(0).unwrap().from_utc_datetime(&NaiveDateTime::default()) } } @@ -455,7 +455,7 @@ impl From> for DateTime { /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by /// this will be created with a fixed timezone offset of 0. fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) + src.with_timezone(&FixedOffset::east_opt(0).unwrap()) } } @@ -517,7 +517,7 @@ impl From> for DateTime { /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned /// by this will be created with a fixed timezone offset of 0. fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) + src.with_timezone(&FixedOffset::east_opt(0).unwrap()) } } @@ -540,7 +540,7 @@ impl DateTime { /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), - /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) + /// FixedOffset::east_opt(0).unwrap().ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() /// ); /// ``` pub fn parse_from_rfc2822(s: &str) -> ParseResult> { @@ -582,7 +582,7 @@ impl DateTime { /// /// let dt = DateTime::parse_from_str( /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); + /// assert_eq!(dt, Ok(FixedOffset::east_opt(0).unwrap().ymd_opt(1983, 4, 13).unwrap().and_hms_milli_opt(12, 9, 14, 274).unwrap())); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); @@ -622,7 +622,7 @@ where /// /// ```rust /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_829); + /// let dt = Utc.ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), /// "2018-01-26T18:30:09.453+00:00"); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), @@ -630,8 +630,8 @@ where /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T18:30:09Z"); /// - /// let pst = FixedOffset::east(8 * 60 * 60); - /// let dt = pst.ymd(2018, 1, 26).and_hms_micro(10, 30, 9, 453_829); + /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let dt = pst.ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` @@ -700,7 +700,7 @@ where /// ```rust /// use chrono::prelude::*; /// - /// let date_time: DateTime = Utc.ymd(2017, 04, 02).and_hms(12, 50, 32); + /// let date_time: DateTime = Utc.ymd_opt(2017, 04, 02).unwrap().and_hms_opt(12, 50, 32).unwrap(); /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` @@ -884,8 +884,8 @@ impl PartialOrd> for DateTime { /// ``` /// use chrono::prelude::*; /// - /// let earlier = Utc.ymd(2015, 5, 15).and_hms(2, 0, 0).with_timezone(&FixedOffset::west(1 * 3600)); - /// let later = Utc.ymd(2015, 5, 15).and_hms(3, 0, 0).with_timezone(&FixedOffset::west(5 * 3600)); + /// let earlier = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(2, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); + /// let later = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(3, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); /// /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); @@ -1060,7 +1060,7 @@ impl From for DateTime { } } }; - Utc.timestamp(sec, nsec) + Utc.timestamp_opt(sec, nsec).unwrap() } } @@ -1123,7 +1123,7 @@ impl From for DateTime { )] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { - Utc.timestamp_millis(date.get_time() as i64) + Utc.timestamp_millis_opt(date.get_time() as i64).unwrap() } } @@ -1152,25 +1152,48 @@ impl From> for js_sys::Date { #[test] fn test_add_sub_months() { - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - assert_eq!(utc_dt + Months::new(15), Utc.ymd(2019, 12, 5).and_hms(23, 58, 0)); + let utc_dt = Utc.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); + assert_eq!( + utc_dt + Months::new(15), + Utc.ymd_opt(2019, 12, 5).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); - let utc_dt = Utc.ymd(2020, 1, 31).and_hms(23, 58, 0); - assert_eq!(utc_dt + Months::new(1), Utc.ymd(2020, 2, 29).and_hms(23, 58, 0)); - assert_eq!(utc_dt + Months::new(2), Utc.ymd(2020, 3, 31).and_hms(23, 58, 0)); + let utc_dt = Utc.ymd_opt(2020, 1, 31).unwrap().and_hms_opt(23, 58, 0).unwrap(); + assert_eq!( + utc_dt + Months::new(1), + Utc.ymd_opt(2020, 2, 29).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); + assert_eq!( + utc_dt + Months::new(2), + Utc.ymd_opt(2020, 3, 31).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - assert_eq!(utc_dt - Months::new(15), Utc.ymd(2017, 6, 5).and_hms(23, 58, 0)); + let utc_dt = Utc.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); + assert_eq!( + utc_dt - Months::new(15), + Utc.ymd_opt(2017, 6, 5).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); - let utc_dt = Utc.ymd(2020, 3, 31).and_hms(23, 58, 0); - assert_eq!(utc_dt - Months::new(1), Utc.ymd(2020, 2, 29).and_hms(23, 58, 0)); - assert_eq!(utc_dt - Months::new(2), Utc.ymd(2020, 1, 31).and_hms(23, 58, 0)); + let utc_dt = Utc.ymd_opt(2020, 3, 31).unwrap().and_hms_opt(23, 58, 0).unwrap(); + assert_eq!( + utc_dt - Months::new(1), + Utc.ymd_opt(2020, 2, 29).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); + assert_eq!( + utc_dt - Months::new(2), + Utc.ymd_opt(2020, 1, 31).unwrap().and_hms_opt(23, 58, 0).unwrap() + ); } #[test] fn test_auto_conversion() { - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0); + let utc_dt = Utc.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); + let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) + .unwrap() + .ymd_opt(2018, 9, 5) + .unwrap() + .and_hms_opt(18, 58, 0) + .unwrap(); let utc_dt2: DateTime = cdt_dt.into(); assert_eq!(utc_dt, utc_dt2); } @@ -1183,16 +1206,32 @@ where E: ::core::fmt::Debug, { assert_eq!( - to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + to_string_utc(&Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()).ok(), Some(r#""2014-07-24T12:34:06Z""#.into()) ); assert_eq!( - to_string_fixed(&FixedOffset::east(3660).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + to_string_fixed( + &FixedOffset::east_opt(3660) + .unwrap() + .ymd_opt(2014, 7, 24) + .unwrap() + .and_hms_opt(12, 34, 6) + .unwrap() + ) + .ok(), Some(r#""2014-07-24T12:34:06+01:01""#.into()) ); assert_eq!( - to_string_fixed(&FixedOffset::east(3650).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + to_string_fixed( + &FixedOffset::east_opt(3650) + .unwrap() + .ymd_opt(2014, 7, 24) + .unwrap() + .and_hms_opt(12, 34, 6) + .unwrap() + ) + .ok(), Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) ); } @@ -1215,31 +1254,45 @@ fn test_decodable_json( assert_eq!( norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) + norm(&Some(Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap())) ); assert_eq!( norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) + norm(&Some(Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap())) ); assert_eq!( norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))) + norm(&Some( + FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2014, 7, 24) + .unwrap() + .and_hms_opt(12, 34, 6) + .unwrap() + )) ); assert_eq!( norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(FixedOffset::east(60 * 60 + 23 * 60).ymd(2014, 7, 24).and_hms(13, 57, 6))) + norm(&Some( + FixedOffset::east_opt(60 * 60 + 23 * 60) + .unwrap() + .ymd_opt(2014, 7, 24) + .unwrap() + .and_hms_opt(13, 57, 6) + .unwrap() + )) ); // we don't know the exact local offset but we can check that // the conversion didn't change the instant itself assert_eq!( local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) + Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() ); assert_eq!( local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) + Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() ); assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); @@ -1263,28 +1316,42 @@ fn test_decodable_json_timestamps( assert_eq!( norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0))) + norm(&Some(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap())) ); assert_eq!( norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1969, 12, 31).and_hms(23, 59, 59))) + norm(&Some(Utc.ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap())) ); assert_eq!( norm(&fixed_from_str("0").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))) + norm(&Some( + FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(1970, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap() + )) ); assert_eq!( norm(&fixed_from_str("-1").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))) + norm(&Some( + FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(1969, 12, 31) + .unwrap() + .and_hms_opt(23, 59, 59) + .unwrap() + )) ); assert_eq!( *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() ); assert_eq!( *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.ymd(1969, 12, 31).and_hms(23, 59, 59) + Utc.ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap() ); } diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index e06517404d..d64777de7c 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -37,7 +37,7 @@ impl Decodable for DateTime { impl Decodable for TsSeconds { #[allow(deprecated)] fn decode(d: &mut D) -> Result, D::Error> { - from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) + from(FixedOffset::east_opt(0).unwrap().timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) } } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 5371edb797..890ec985d5 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -131,7 +131,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// time: DateTime /// } /// -/// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); +/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -168,7 +168,7 @@ pub mod ts_nanoseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), + /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -254,7 +254,7 @@ pub mod ts_nanoseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); +/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -290,7 +290,7 @@ pub mod ts_nanoseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), + /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -383,7 +383,7 @@ pub mod ts_nanoseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); +/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -419,7 +419,7 @@ pub mod ts_microseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), + /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -505,7 +505,7 @@ pub mod ts_microseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); +/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -540,7 +540,7 @@ pub mod ts_microseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), + /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -633,7 +633,7 @@ pub mod ts_microseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); +/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -669,7 +669,7 @@ pub mod ts_milliseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), + /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -752,7 +752,7 @@ pub mod ts_milliseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); +/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -787,7 +787,7 @@ pub mod ts_milliseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), + /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -893,7 +893,7 @@ pub mod ts_milliseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); +/// let time = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -929,7 +929,7 @@ pub mod ts_seconds { /// } /// /// let my_s = S { - /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), + /// time: Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -1009,7 +1009,7 @@ pub mod ts_seconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); +/// let time = Some(Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -1044,7 +1044,7 @@ pub mod ts_seconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), + /// time: Some(Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -1142,7 +1142,7 @@ fn test_serde_bincode() { // it is not self-describing. use bincode::{deserialize, serialize}; - let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); + let dt = Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap(); let encoded = serialize(&dt).unwrap(); let decoded: DateTime = deserialize(&encoded).unwrap(); assert_eq!(dt, decoded); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index d9b11757c8..f92000675f 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -11,68 +11,101 @@ use crate::Datelike; #[test] fn test_datetime_offset() { - let est = FixedOffset::west(5 * 60 * 60); - let edt = FixedOffset::west(4 * 60 * 60); - let kst = FixedOffset::east(9 * 60 * 60); + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); - assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); - assert_eq!(format!("{}", edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 -04:00"); - assert_eq!(format!("{}", kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 +09:00"); - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); - assert_eq!(format!("{:?}", edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09-04:00"); - assert_eq!(format!("{:?}", kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09+09:00"); + assert_eq!( + format!("{}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 UTC" + ); + assert_eq!( + format!("{}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 -04:00" + ); + assert_eq!( + format!("{}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 +09:00" + ); + assert_eq!( + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09-04:00" + ); + assert_eq!( + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09+09:00" + ); // edge cases - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); - assert_eq!(format!("{:?}", edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00-04:00"); - assert_eq!(format!("{:?}", kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00+09:00"); - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), "2014-05-06T23:59:59Z"); assert_eq!( - format!("{:?}", edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00-04:00" + ); + assert_eq!( + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00+09:00" + ); + assert_eq!( + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + "2014-05-06T23:59:59Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), "2014-05-06T23:59:59-04:00" ); assert_eq!( - format!("{:?}", kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), "2014-05-06T23:59:59+09:00" ); - let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(dt, edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); - assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); + let dt = Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap(); + assert_eq!(dt, edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(3, 8, 9).unwrap()); assert_eq!( - dt.signed_duration_since(edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), + dt + Duration::seconds(3600 + 60 + 1), + Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(8, 9, 10).unwrap() + ); + assert_eq!( + dt.signed_duration_since(edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(10, 11, 12).unwrap()), Duration::seconds(-7 * 3600 - 3 * 60 - 3) ); - assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); - assert_eq!(*edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), edt); - assert!(*edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != est); + assert_eq!(*Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset(), Utc); + assert_eq!(*edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset(), edt); + assert!(*edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset() != est); } #[test] fn test_datetime_date_and_time() { - let tz = FixedOffset::east(5 * 60 * 60); - let d = tz.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(d.time(), NaiveTime::from_hms(7, 8, 9)); - assert_eq!(d.date(), tz.ymd(2014, 5, 6)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2014, 5, 6)); + let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let d = tz.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(7, 8, 9).unwrap()); + assert_eq!(d.date(), tz.ymd_opt(2014, 5, 6).unwrap()); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2014, 5, 6).unwrap()); assert_eq!(d.date().and_time(d.time()), Some(d)); - let tz = FixedOffset::east(4 * 60 * 60); - let d = tz.ymd(2016, 5, 4).and_hms(3, 2, 1); - assert_eq!(d.time(), NaiveTime::from_hms(3, 2, 1)); - assert_eq!(d.date(), tz.ymd(2016, 5, 4)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2016, 5, 4)); + let tz = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let d = tz.ymd_opt(2016, 5, 4).unwrap().and_hms_opt(3, 2, 1).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(3, 2, 1).unwrap()); + assert_eq!(d.date(), tz.ymd_opt(2016, 5, 4).unwrap()); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2016, 5, 4).unwrap()); assert_eq!(d.date().and_time(d.time()), Some(d)); - let tz = FixedOffset::west(13 * 60 * 60); - let d = tz.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert_eq!(d.time(), NaiveTime::from_hms(12, 34, 56)); - assert_eq!(d.date(), tz.ymd(2017, 8, 9)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2017, 8, 9)); + let tz = FixedOffset::west_opt(13 * 60 * 60).unwrap(); + let d = tz.ymd_opt(2017, 8, 9).unwrap().and_hms_opt(12, 34, 56).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(12, 34, 56).unwrap()); + assert_eq!(d.date(), tz.ymd_opt(2017, 8, 9).unwrap()); + assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2017, 8, 9).unwrap()); assert_eq!(d.date().and_time(d.time()), Some(d)); - let utc_d = Utc.ymd(2017, 8, 9).and_hms(12, 34, 56); + let utc_d = Utc.ymd_opt(2017, 8, 9).unwrap().and_hms_opt(12, 34, 56).unwrap(); assert!(utc_d < d); } @@ -87,57 +120,83 @@ fn test_datetime_with_timezone() { #[test] fn test_datetime_rfc2822_and_rfc3339() { - let edt = FixedOffset::east(5 * 60 * 60); + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - assert_eq!(Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert_eq!( - edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); assert_eq!( - edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); assert_eq!( - edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), "2015-02-18T23:59:60.234567+05:00" ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) ); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) ); assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) ); } #[test] fn test_rfc3339_opts() { use crate::SecondsFormat::*; - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_000); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); @@ -159,7 +218,7 @@ fn test_rfc3339_opts() { #[should_panic] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; - let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); + let dt = Utc.ymd_opt(1999, 10, 9).unwrap().and_hms_opt(1, 2, 3).unwrap(); dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); } @@ -167,38 +226,53 @@ fn test_rfc3339_opts_nonexhaustive() { fn test_datetime_from_str() { assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -207,7 +281,9 @@ fn test_datetime_from_str() { #[test] fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); + let ymdhms = |y, m, d, h, n, s, off| { + FixedOffset::east_opt(off).unwrap().ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }; assert_eq!( DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) @@ -217,19 +293,19 @@ fn test_datetime_parse_from_str() { .is_err()); assert_eq!( Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), - Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) + Ok(Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap()) ); } #[test] fn test_to_string_round_trip() { - let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0); + let dt = Utc.ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let _dt: DateTime = dt.to_string().parse().unwrap(); - let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600)); + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap()); let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - let ndt_fixed = dt.with_timezone(&FixedOffset::east(0)); + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap()); let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); } @@ -273,7 +349,7 @@ fn test_datetime_is_send() { #[test] fn test_subsecond_part() { - let datetime = Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); + let datetime = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 1234567).unwrap(); assert_eq!(1, datetime.timestamp_subsec_millis()); assert_eq!(1234, datetime.timestamp_subsec_micros()); @@ -285,28 +361,32 @@ fn test_subsecond_part() { fn test_from_system_time() { use std::time::Duration; - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let epoch = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let nanos = 999_999_999; // SystemTime -> DateTime assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); assert_eq!( DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) + Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() ); assert_eq!( DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1) + Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() ); // DateTime -> SystemTime assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), + SystemTime::from( + Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + ), UNIX_EPOCH + Duration::new(999_999_999, nanos) ); assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)), + SystemTime::from( + Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() + ), UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) ); @@ -315,8 +395,14 @@ fn test_from_system_time() { { assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), + UNIX_EPOCH + ); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), + UNIX_EPOCH + ); } #[test] @@ -326,27 +412,31 @@ fn test_from_system_time() { let nanos = 999_999_000; - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); + let epoch = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); // SystemTime -> DateTime assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); assert_eq!( DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) + Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() ); assert_eq!( DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000) + Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1_000).unwrap() ); // DateTime -> SystemTime assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), + SystemTime::from( + Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + ), UNIX_EPOCH + Duration::new(999_999_999, nanos) ); assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)), + SystemTime::from( + Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1_000).unwrap() + ), UNIX_EPOCH - Duration::new(999_999_999, nanos) ); @@ -355,13 +445,19 @@ fn test_from_system_time() { { assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), + UNIX_EPOCH + ); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), + UNIX_EPOCH + ); } #[test] fn test_datetime_format_alignment() { - let datetime = Utc.ymd(2007, 1, 2); + let datetime = Utc.ymd_opt(2007, 1, 2).unwrap(); // Item::Literal let percent = datetime.format("%%"); @@ -392,17 +488,20 @@ fn test_datetime_format_alignment() { #[test] fn test_datetime_from_local() { // 2000-01-12T02:00:00Z - let naivedatetime_utc = NaiveDate::from_ymd(2000, 1, 12).and_hms(2, 0, 0); + let naivedatetime_utc = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); // 2000-01-12T10:00:00+8:00:00 - let timezone_east = FixedOffset::east(8 * 60 * 60); - let naivedatetime_east = NaiveDate::from_ymd(2000, 1, 12).and_hms(10, 0, 0); + let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let naivedatetime_east = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); // 2000-01-11T19:00:00-7:00:00 - let timezone_west = FixedOffset::west(7 * 60 * 60); - let naivedatetime_west = NaiveDate::from_ymd(2000, 1, 11).and_hms(19, 0, 0); + let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); + let naivedatetime_west = + NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); @@ -429,20 +528,20 @@ fn test_years_elapsed() { #[test] fn test_datetime_add_assign() { - let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = DateTime::::from_utc(naivedatetime, Utc); let mut datetime_add = datetime; datetime_add += Duration::seconds(60); assert_eq!(datetime_add, datetime + Duration::seconds(60)); - let timezone = FixedOffset::east(60 * 60); + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); assert_eq!(datetime_add, datetime + Duration::seconds(60)); - let timezone = FixedOffset::west(2 * 60 * 60); + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); @@ -452,7 +551,7 @@ fn test_datetime_add_assign() { #[test] #[cfg(feature = "clock")] fn test_datetime_add_assign_local() { - let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = Local.from_utc_datetime(&naivedatetime); let mut datetime_add = Local.from_utc_datetime(&naivedatetime); @@ -466,20 +565,20 @@ fn test_datetime_add_assign_local() { #[test] fn test_datetime_sub_assign() { - let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(12, 0, 0); + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(); let datetime = DateTime::::from_utc(naivedatetime, Utc); let mut datetime_sub = datetime; datetime_sub -= Duration::minutes(90); assert_eq!(datetime_sub, datetime - Duration::minutes(90)); - let timezone = FixedOffset::east(60 * 60); + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); assert_eq!(datetime_sub, datetime - Duration::minutes(90)); - let timezone = FixedOffset::west(2 * 60 * 60); + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); @@ -489,7 +588,7 @@ fn test_datetime_sub_assign() { #[test] #[cfg(feature = "clock")] fn test_datetime_sub_assign_local() { - let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = Local.from_utc_datetime(&naivedatetime); let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); diff --git a/src/format/mod.rs b/src/format/mod.rs index e12f3efb01..da347531d3 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -20,7 +20,7 @@ //! # use std::error::Error; //! use chrono::prelude::*; //! -//! let date_time = Utc.ymd(2020, 11, 10).and_hms(0, 1, 32); +//! let date_time = Utc.ymd_opt(2020, 11, 10).unwrap().and_hms_opt(0, 1, 32).unwrap(); //! //! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); //! assert_eq!(formatted, "2020-11-10 00:01:32"); diff --git a/src/format/parse.rs b/src/format/parse.rs index 02771b2e42..c6a469062b 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -892,7 +892,7 @@ fn parse_rfc850() { static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37); + let dt = Utc.ymd_opt(1994, 11, 6).unwrap().and_hms_opt(8, 49, 37).unwrap(); // Check that the format is what we expect assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); @@ -903,12 +903,30 @@ fn parse_rfc850() { // Check that the rest of the weekdays parse correctly (this test originally failed because // Sunday parsed incorrectly). let testdates = [ - (Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 9).and_hms(8, 49, 37), "Wednesday, 09-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 10).and_hms(8, 49, 37), "Thursday, 10-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 11).and_hms(8, 49, 37), "Friday, 11-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 12).and_hms(8, 49, 37), "Saturday, 12-Nov-94 08:49:37 GMT"), + ( + Utc.ymd_opt(1994, 11, 7).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Monday, 07-Nov-94 08:49:37 GMT", + ), + ( + Utc.ymd_opt(1994, 11, 8).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Tuesday, 08-Nov-94 08:49:37 GMT", + ), + ( + Utc.ymd_opt(1994, 11, 9).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.ymd_opt(1994, 11, 10).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + ( + Utc.ymd_opt(1994, 11, 11).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Friday, 11-Nov-94 08:49:37 GMT", + ), + ( + Utc.ymd_opt(1994, 11, 12).unwrap().and_hms_opt(8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), ]; for val in &testdates { diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 32c6c7afbf..06b1b2efb3 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -782,7 +782,7 @@ mod tests { ) } - let ymd = |y, m, d| Ok(NaiveDate::from_ymd(y, m, d)); + let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); // ymd: omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -969,8 +969,8 @@ mod tests { ) } - let hms = |h, m, s| Ok(NaiveTime::from_hms(h, m, s)); - let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano(h, m, s, n)); + let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); + let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1025,9 +1025,12 @@ mod tests { ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) } - let ymdhms = |y, m, d, h, n, s| Ok(NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - let ymdhmsn = - |y, m, d, h, n, s, nano| Ok(NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano)); + let ymdhms = |y, m, d, h, n, s| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) + }; + let ymdhmsn = |y, m, d, h, n, s, nano| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) + }; // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1081,11 +1084,12 @@ mod tests { // more timestamps let max_days_from_year_1970 = - NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); - let year_0_from_year_1970 = - NaiveDate::from_ymd(0, 1, 1).signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let min_days_from_year_1970 = - NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); assert_eq!( parse!(timestamp: min_days_from_year_1970.num_seconds()), ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) @@ -1175,7 +1179,12 @@ mod tests { } let ymdhmsn = |y, m, d, h, n, s, nano, off| { - Ok(FixedOffset::east(off).ymd(y, m, d).and_hms_nano(h, n, s, nano)) + Ok(FixedOffset::east_opt(off) + .unwrap() + .ymd_opt(y, m, d) + .unwrap() + .and_hms_nano_opt(h, n, s, nano) + .unwrap()) }; assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); @@ -1219,7 +1228,7 @@ mod tests { parse!(Utc; year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms_nano(4, 26, 40, 12_345_678)) + Ok(Utc.ymd_opt(2014, 12, 31).unwrap().and_hms_nano_opt(4, 26, 40, 12_345_678).unwrap()) ); assert_eq!( parse!(Utc; @@ -1228,31 +1237,41 @@ mod tests { Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms_nano(13, 26, 40, 12_345_678)) + Ok(FixedOffset::east_opt(32400) + .unwrap() + .ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(13, 26, 40, 12_345_678) + .unwrap()) ); // single result from timestamp assert_eq!( parse!(Utc; timestamp: 1_420_000_000, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms(4, 26, 40)) + Ok(Utc.ymd_opt(2014, 12, 31).unwrap().and_hms_opt(4, 26, 40).unwrap()) ); assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 0), + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms(13, 26, 40)) + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), + Ok(FixedOffset::east_opt(32400) + .unwrap() + .ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_opt(13, 26, 40) + .unwrap()) ); // TODO test with a variable time zone (for None and Ambiguous cases) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b41ab2e1c7..d4bf43d614 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -558,7 +558,12 @@ fn test_strftime_items() { fn test_strftime_docs() { use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); + let dt = FixedOffset::east_opt(34200) + .unwrap() + .ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(); // date specifiers assert_eq!(dt.format("%Y").to_string(), "2001"); @@ -656,7 +661,12 @@ fn test_strftime_docs() { fn test_strftime_docs_localized() { use crate::{FixedOffset, TimeZone}; - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); + let dt = FixedOffset::east_opt(34200).unwrap().ymd_opt(2001, 7, 8).unwrap().and_hms_nano( + 0, + 34, + 59, + 1_026_490_708, + ); // date specifiers assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); diff --git a/src/lib.rs b/src/lib.rs index c16485f7c5..b5401a0cd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,26 +126,26 @@ //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! -//! let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` +//! let dt = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") -//! assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); +//! assert_eq!(dt, Utc.yo(2014, 189).and_hms_opt(9, 10, 11).unwrap()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. -//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); +//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms_opt(9, 10, 11).unwrap()); //! -//! let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); +//! let dt = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!(dt, Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap()); +//! assert_eq!(dt, Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap()); //! //! // dynamic verification //! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), -//! LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); +//! LocalResult::Single(Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_opt(21, 15, 33).unwrap())); //! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); //! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); //! //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -//! let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -//! let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); +//! let local_dt = Local.ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(); +//! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; //! ``` @@ -161,7 +161,7 @@ //! use chrono::Duration; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: -//! let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); +//! let dt = FixedOffset::east_opt(9*3600).unwrap().ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap(); //! //! // property accessors //! assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); @@ -174,8 +174,8 @@ //! //! // time zone accessor and manipulation //! assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -//! assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -//! assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); +//! assert_eq!(dt.timezone(), FixedOffset::east_opt(9 * 3600).unwrap()); +//! assert_eq!(dt.with_timezone(&Utc), Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 45, 59, 324310806).unwrap()); //! //! // a sample of property manipulations (validates dynamically) //! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -183,14 +183,14 @@ //! assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE //! //! // arithmetic operations -//! let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -//! let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); +//! let dt1 = Utc.ymd_opt(2014, 11, 14).unwrap().and_hms_opt(8, 9, 10).unwrap(); +//! let dt2 = Utc.ymd_opt(2014, 11, 14).unwrap().and_hms_opt(10, 9, 8).unwrap(); //! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); //! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), -//! Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), -//! Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); +//! assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() + Duration::seconds(1_000_000_000), +//! Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap()); +//! assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() - Duration::seconds(1_000_000_000), +//! Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_opt(22, 13, 20).unwrap()); //! ``` //! //! ### Formatting and Parsing @@ -221,7 +221,7 @@ //! //! # #[cfg(feature = "unstable-locales")] //! # fn test() { -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); +//! let dt = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_opt(12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); //! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); @@ -233,7 +233,7 @@ //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); //! //! // Note that milli/nanoseconds are only printed if they are non-zero -//! let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); +//! let dt_nano = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } //! # #[cfg(not(feature = "unstable-locales"))] @@ -273,8 +273,8 @@ //! ```rust //! use chrono::prelude::*; //! -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -//! let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); +//! let dt = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_opt(12, 0, 9).unwrap(); +//! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9*3600).unwrap()); //! //! // method 1 //! assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); @@ -341,9 +341,9 @@ //! assert_eq!(Utc::today(), Utc::now().date()); //! assert_eq!(Local::today(), Local::now().date()); //! -//! assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); +//! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().weekday(), Weekday::Fri); //! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), +//! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().format("%H%M%S").to_string(), //! "070809"); //! ``` //! @@ -389,7 +389,7 @@ //! Chrono inherently does not support an inaccurate or partial date and time representation. //! Any operation that can be ambiguous will return `None` in such cases. //! For example, "a month later" of 2014-01-30 is not well-defined -//! and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. +//! and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. //! //! Non ISO week handling is not yet supported. //! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) diff --git a/src/month.rs b/src/month.rs index 56316b3c4d..3f308e4c08 100644 --- a/src/month.rs +++ b/src/month.rs @@ -12,7 +12,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// ``` /// use num_traits::FromPrimitive; /// use chrono::prelude::*; -/// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); +/// let date = Utc.ymd_opt(2019, 10, 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// // `2019-10-28T09:10:11Z` /// let month = Month::from_u32(date.month()); /// assert_eq!(month, Some(Month::October)) @@ -21,7 +21,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// ``` /// # use chrono::prelude::*; /// let month = Month::January; -/// let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); +/// let dt = Utc.ymd_opt(2019, month.number_from_month(), 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); /// ``` /// Allows mapping from and to month, from 1-January to 12-December. @@ -335,11 +335,15 @@ mod tests { assert_eq!(dec_opt, Some(Month::December)); assert_eq!(no_month, None); - let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); + let date = Utc.ymd_opt(2019, 10, 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); assert_eq!(Month::from_u32(date.month()), Some(Month::October)); let month = Month::January; - let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); + let dt = Utc + .ymd_opt(2019, month.number_from_month(), 28) + .unwrap() + .and_hms_opt(9, 10, 11) + .unwrap(); assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); } diff --git a/src/naive/date.rs b/src/naive/date.rs index 09a4c23d88..c66e13ea51 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -68,7 +68,7 @@ impl NaiveWeek { /// ``` /// use chrono::{NaiveDate, Weekday}; /// - /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); /// let week = date.week(Weekday::Mon); /// assert!(week.first_day() <= date); /// ``` @@ -87,7 +87,7 @@ impl NaiveWeek { /// ``` /// use chrono::{NaiveDate, Weekday}; /// - /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); /// let week = date.week(Weekday::Mon); /// assert!(week.last_day() >= date); /// ``` @@ -105,7 +105,7 @@ impl NaiveWeek { /// ``` /// use chrono::{NaiveDate, Weekday}; /// - /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); /// let week = date.week(Weekday::Mon); /// let days = week.days(); /// assert!(days.contains(&date)); @@ -197,8 +197,8 @@ pub const MAX_DATE: NaiveDate = NaiveDate::MAX; // we use a separate run-time test. #[test] fn test_date_bounds() { - let calculated_min = NaiveDate::from_ymd(MIN_YEAR, 1, 1); - let calculated_max = NaiveDate::from_ymd(MAX_YEAR, 12, 31); + 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 {:?}", @@ -463,7 +463,7 @@ impl NaiveDate { if n == 0 { return None; } - let first = NaiveDate::from_ymd(year, month, 1).weekday(); + let first = NaiveDate::from_ymd_opt(year, month, 1)?.weekday(); let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; let day = (u32::from(n) - 1) * 7 + first_to_dow + 1; NaiveDate::from_ymd_opt(year, month, day) @@ -481,9 +481,9 @@ impl NaiveDate { /// let parse_from_str = NaiveDate::parse_from_str; /// /// assert_eq!(parse_from_str("2015-09-05", "%Y-%m-%d"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); /// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); /// ``` /// /// Time and offset is ignored for the purpose of parsing. @@ -492,7 +492,7 @@ impl NaiveDate { /// # use chrono::NaiveDate; /// # let parse_from_str = NaiveDate::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17))); + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap())); /// ``` /// /// Out-of-bound dates or insufficient fields are errors. @@ -526,12 +526,12 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Months}; /// assert_eq!( - /// NaiveDate::from_ymd(2022, 2, 20).checked_add_months(Months::new(6)), - /// Some(NaiveDate::from_ymd(2022, 8, 20)) + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 20).unwrap()) /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2022, 7, 31).checked_add_months(Months::new(2)), - /// Some(NaiveDate::from_ymd(2022, 9, 30)) + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_months(Months::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 9, 30).unwrap()) /// ); /// ``` pub fn checked_add_months(self, months: Months) -> Option { @@ -554,12 +554,12 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Months}; /// assert_eq!( - /// NaiveDate::from_ymd(2022, 2, 20).checked_sub_months(Months::new(6)), - /// Some(NaiveDate::from_ymd(2021, 8, 20)) + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2021, 8, 20).unwrap()) /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap() /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -625,12 +625,12 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( - /// NaiveDate::from_ymd(2022, 2, 20).checked_add_days(Days::new(9)), - /// Some(NaiveDate::from_ymd(2022, 3, 1)) + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_days(Days::new(9)), + /// Some(NaiveDate::from_ymd_opt(2022, 3, 1).unwrap()) /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2022, 7, 31).checked_add_days(Days::new(2)), - /// Some(NaiveDate::from_ymd(2022, 8, 2)) + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) /// ); /// ``` pub fn checked_add_days(self, days: Days) -> Option { @@ -648,8 +648,8 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( - /// NaiveDate::from_ymd(2022, 2, 20).checked_sub_days(Days::new(6)), - /// Some(NaiveDate::from_ymd(2022, 2, 14)) + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) /// ); /// ``` pub fn checked_sub_days(self, days: Days) -> Option { @@ -671,8 +671,8 @@ impl NaiveDate { /// ``` /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); /// /// let dt: NaiveDateTime = d.and_time(t); /// assert_eq!(dt.date(), d); @@ -689,19 +689,6 @@ impl NaiveDate { /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. /// /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms(12, 34, 56); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// ``` #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { @@ -720,7 +707,7 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_opt(12, 34, 56).is_some()); /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead /// assert!(d.and_hms_opt(12, 60, 56).is_none()); @@ -737,20 +724,6 @@ impl NaiveDate { /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_000_000); - /// ``` #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { @@ -769,7 +742,7 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); @@ -800,7 +773,7 @@ impl NaiveDate { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// /// let dt: NaiveDateTime = d.and_hms_micro(12, 34, 56, 789_012); /// assert_eq!(dt.year(), 2015); @@ -826,7 +799,7 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); @@ -869,7 +842,7 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); @@ -924,16 +897,6 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the next calendar date. /// /// Panics when `self` is the last representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ(), NaiveDate::from_ymd(2015, 6, 4)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 30).succ(), NaiveDate::from_ymd(2015, 7, 1)); - /// assert_eq!(NaiveDate::from_ymd(2015, 12, 31).succ(), NaiveDate::from_ymd(2016, 1, 1)); - /// ``` #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] pub fn succ(&self) -> NaiveDate { @@ -949,8 +912,8 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 4))); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap())); /// assert_eq!(NaiveDate::MAX.succ_opt(), None); /// ``` #[inline] @@ -961,16 +924,6 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the previous calendar date. /// /// Panics when `self` is the first representable date. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred(), NaiveDate::from_ymd(2015, 6, 2)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 1).pred(), NaiveDate::from_ymd(2015, 5, 31)); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).pred(), NaiveDate::from_ymd(2014, 12, 31)); - /// ``` #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] pub fn pred(&self) -> NaiveDate { @@ -986,8 +939,8 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 2))); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap())); /// assert_eq!(NaiveDate::MIN.pred_opt(), None); /// ``` #[inline] @@ -1004,11 +957,11 @@ impl NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.checked_add_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); /// assert_eq!(d.checked_add_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); @@ -1035,11 +988,11 @@ impl NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.checked_sub_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); /// assert_eq!(d.checked_sub_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); @@ -1104,7 +1057,7 @@ impl NaiveDate { /// use chrono::format::strftime::StrftimeItems; /// /// let fmt = StrftimeItems::new("%Y-%m-%d"); - /// let d = NaiveDate::from_ymd(2015, 9, 5); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); /// ``` @@ -1115,7 +1068,7 @@ impl NaiveDate { /// # use chrono::NaiveDate; /// # use chrono::format::strftime::StrftimeItems; /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); - /// # let d = NaiveDate::from_ymd(2015, 9, 5); + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] @@ -1148,7 +1101,7 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); /// ``` @@ -1157,7 +1110,7 @@ impl NaiveDate { /// /// ``` /// # use chrono::NaiveDate; - /// # let d = NaiveDate::from_ymd(2015, 9, 5); + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` @@ -1176,20 +1129,20 @@ impl NaiveDate { /// # use chrono::NaiveDate; /// /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 2, 28), - /// NaiveDate::from_ymd(2016, 2, 29), - /// NaiveDate::from_ymd(2016, 3, 1), + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 28).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 29).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 1).unwrap(), /// ]; /// /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_days().take(4).enumerate() { + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).enumerate() { /// assert_eq!(d, expected[idx]); /// count += 1; /// } /// assert_eq!(count, 4); /// - /// for d in NaiveDate::from_ymd(2016, 3, 1).iter_days().rev().take(4) { + /// for d in NaiveDate::from_ymd_opt(2016, 3, 1).unwrap().iter_days().rev().take(4) { /// count -= 1; /// assert_eq!(d, expected[count]); /// } @@ -1207,20 +1160,20 @@ impl NaiveDate { /// # use chrono::NaiveDate; /// /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 3, 5), - /// NaiveDate::from_ymd(2016, 3, 12), - /// NaiveDate::from_ymd(2016, 3, 19), + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 5).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 12).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 19).unwrap(), /// ]; /// /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_weeks().take(4).enumerate() { + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).enumerate() { /// assert_eq!(d, expected[idx]); /// count += 1; /// } /// assert_eq!(count, 4); /// - /// for d in NaiveDate::from_ymd(2016, 3, 19).iter_weeks().rev().take(4) { + /// for d in NaiveDate::from_ymd_opt(2016, 3, 19).unwrap().iter_weeks().rev().take(4) { /// count -= 1; /// assert_eq!(d, expected[count]); /// } @@ -1251,8 +1204,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).year(), 2015); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).year(), -308); // 309 BCE + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE /// ``` #[inline] fn year(&self) -> i32 { @@ -1268,8 +1221,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month(), 9); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month(), 3); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); /// ``` #[inline] fn month(&self) -> u32 { @@ -1285,8 +1238,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month0(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month0(), 2); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); /// ``` #[inline] fn month0(&self) -> u32 { @@ -1302,8 +1255,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day(), 14); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); /// ``` /// /// Combined with [`NaiveDate::pred`](#method.pred), @@ -1316,7 +1269,7 @@ impl Datelike for NaiveDate { /// fn ndays_in_month(year: i32, month: u32) -> u32 { /// // the first day of the next month... /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; - /// let d = NaiveDate::from_ymd(y, m, 1); + /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap(); /// /// // ...is preceded by the last day of the original month /// d.pred().day() @@ -1342,8 +1295,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day0(), 7); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day0(), 13); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); /// ``` #[inline] fn day0(&self) -> u32 { @@ -1359,8 +1312,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal(), 251); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal(), 74); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal(), 251); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); /// ``` /// /// Combined with [`NaiveDate::pred`](#method.pred), @@ -1372,7 +1325,7 @@ impl Datelike for NaiveDate { /// /// fn ndays_in_year(year: i32) -> u32 { /// // the first day of the next year... - /// let d = NaiveDate::from_ymd(year + 1, 1, 1); + /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(); /// /// // ...is preceded by the last day of the original year /// d.pred().ordinal() @@ -1398,8 +1351,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal0(), 250); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal0(), 73); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal0(), 250); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal0(), 73); /// ``` #[inline] fn ordinal0(&self) -> u32 { @@ -1413,8 +1366,8 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).weekday(), Weekday::Tue); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).weekday(), Weekday::Fri); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); /// ``` #[inline] fn weekday(&self) -> Weekday { @@ -1435,18 +1388,18 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(2016), - /// Some(NaiveDate::from_ymd(2016, 9, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(-308), - /// Some(NaiveDate::from_ymd(-308, 9, 8))); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 8).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 8).unwrap())); /// ``` /// /// A leap day (February 29) is a good example that this method can return `None`. /// /// ``` /// # use chrono::{NaiveDate, Datelike}; - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2015).is_none()); - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2020).is_some()); + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2015).is_none()); + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2020).is_some()); /// ``` #[inline] fn with_year(&self, year: i32) -> Option { @@ -1469,10 +1422,10 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(10), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(13), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month(2), None); // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // no month 13 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); // no February 30 /// ``` #[inline] fn with_month(&self, month: u32) -> Option { @@ -1488,10 +1441,10 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(9), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(12), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month0(1), None); // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // no month 13 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // no February 30 /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { @@ -1507,9 +1460,9 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(30), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(31), + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), /// None); // no September 31 /// ``` #[inline] @@ -1526,9 +1479,9 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(29), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(30), + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), /// None); // no September 31 /// ``` #[inline] @@ -1545,15 +1498,15 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(366), + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(366), /// None); // 2015 had only 365 days /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { @@ -1569,15 +1522,15 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(365), + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(365), /// None); // 2015 had only 365 days /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { @@ -1780,7 +1733,7 @@ impl Iterator for NaiveDateDaysIterator { // current < NaiveDate::MAX from here on: let current = self.value; // This can't panic because current is < NaiveDate::MAX: - self.value = current.succ(); + self.value = current.succ_opt().unwrap(); Some(current) } @@ -1798,7 +1751,7 @@ impl DoubleEndedIterator for NaiveDateDaysIterator { return None; } let current = self.value; - self.value = current.pred(); + self.value = current.pred_opt().unwrap(); Some(current) } } @@ -1853,17 +1806,17 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { /// ``` /// use chrono::NaiveDate; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::NaiveDate; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Debug for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1888,17 +1841,17 @@ impl fmt::Debug for NaiveDate { /// ``` /// use chrono::NaiveDate; /// -/// assert_eq!(format!("{}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::NaiveDate; -/// assert_eq!(format!("{}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Display for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1914,10 +1867,10 @@ impl fmt::Display for NaiveDate { /// ``` /// use chrono::NaiveDate; /// -/// let d = NaiveDate::from_ymd(2015, 9, 18); +/// let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap(); /// assert_eq!("2015-09-18".parse::(), Ok(d)); /// -/// let d = NaiveDate::from_ymd(12345, 6, 7); +/// let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap(); /// assert_eq!("+12345-6-7".parse::(), Ok(d)); /// /// assert!("foo".parse::().is_err()); @@ -1951,11 +1904,11 @@ impl str::FromStr for NaiveDate { /// use chrono::NaiveDate; /// /// let default_date = NaiveDate::default(); -/// assert_eq!(default_date, NaiveDate::from_ymd(1970, 1, 1)); +/// assert_eq!(default_date, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); /// ``` impl Default for NaiveDate { fn default() -> Self { - NaiveDate::from_ymd(1970, 1, 1) + NaiveDate::from_ymd_opt(1970, 1, 1).unwrap() } } @@ -1965,9 +1918,18 @@ where F: Fn(&NaiveDate) -> Result, E: ::std::fmt::Debug, { - assert_eq!(to_string(&NaiveDate::from_ymd(2014, 7, 24)).ok(), Some(r#""2014-07-24""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(0, 1, 1)).ok(), Some(r#""0000-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(-1, 12, 31)).ok(), Some(r#""-0001-12-31""#.into())); + assert_eq!( + to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(), + Some(r#""2014-07-24""#.into()) + ); + assert_eq!( + to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(), + Some(r#""0000-01-01""#.into()) + ); + assert_eq!( + to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), + Some(r#""-0001-12-31""#.into()) + ); assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262144-01-01""#.into())); assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262143-12-31""#.into())); } @@ -1980,12 +1942,18 @@ where { use std::{i32, i64}; - assert_eq!(from_str(r#""2016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""+002016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31))); + assert_eq!( + from_str(r#""2016-07-08""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap())); + assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8)); + assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!( + from_str(r#""-0001-12-31""#).ok(), + Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) + ); assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(NaiveDate::MIN)); assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(NaiveDate::MAX)); @@ -2120,7 +2088,7 @@ mod serde { // it is not self-describing. use bincode::{deserialize, serialize}; - let d = NaiveDate::from_ymd(2014, 7, 24); + 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); @@ -2143,19 +2111,22 @@ mod tests { fn diff_months() { // identity assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(0)), - Some(NaiveDate::from_ymd(2022, 8, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), + Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) ); // add with months exceeding `i32::MAX` assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(i32::MAX as u32 + 1)), + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_add_months(Months::new(i32::MAX as u32 + 1)), None ); // sub with months exceeindg `i32::MIN` assert_eq!( - NaiveDate::from_ymd(2022, 8, 3) + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() .checked_sub_months(Months::new((i32::MIN as i64).abs() as u32 + 1)), None ); @@ -2168,56 +2139,56 @@ mod tests { // sub crossing year 0 boundary assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(2050 * 12)), - Some(NaiveDate::from_ymd(-28, 8, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), + Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) ); // add crossing year boundary assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(6)), - Some(NaiveDate::from_ymd(2023, 2, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), + Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) ); // sub crossing year boundary assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(10)), - Some(NaiveDate::from_ymd(2021, 10, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) ); // add clamping day, non-leap year assert_eq!( - NaiveDate::from_ymd(2022, 1, 29).checked_add_months(Months::new(1)), - Some(NaiveDate::from_ymd(2022, 2, 28)) + NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), + Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) ); // add to leap day assert_eq!( - NaiveDate::from_ymd(2022, 10, 29).checked_add_months(Months::new(16)), - Some(NaiveDate::from_ymd(2024, 2, 29)) + NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), + Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) ); // add into december assert_eq!( - NaiveDate::from_ymd(2022, 10, 31).checked_add_months(Months::new(2)), - Some(NaiveDate::from_ymd(2022, 12, 31)) + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), + Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) ); // sub into december assert_eq!( - NaiveDate::from_ymd(2022, 10, 31).checked_sub_months(Months::new(10)), - Some(NaiveDate::from_ymd(2021, 12, 31)) + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) ); // add into january assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_add_months(Months::new(5)), - Some(NaiveDate::from_ymd(2023, 1, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), + Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) ); // sub into january assert_eq!( - NaiveDate::from_ymd(2022, 8, 3).checked_sub_months(Months::new(7)), - Some(NaiveDate::from_ymd(2022, 1, 3)) + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), + Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) ); } @@ -2227,20 +2198,20 @@ mod tests { for y in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { // even months - let d4 = NaiveDate::from_ymd(y, 4, 4); - let d6 = NaiveDate::from_ymd(y, 6, 6); - let d8 = NaiveDate::from_ymd(y, 8, 8); - let d10 = NaiveDate::from_ymd(y, 10, 10); - let d12 = NaiveDate::from_ymd(y, 12, 12); + let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); + let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); + let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); + let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); + let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); // nine to five, seven-eleven - let d59 = NaiveDate::from_ymd(y, 5, 9); - let d95 = NaiveDate::from_ymd(y, 9, 5); - let d711 = NaiveDate::from_ymd(y, 7, 11); - let d117 = NaiveDate::from_ymd(y, 11, 7); + let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); + let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); + let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); + let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); // "March 0" - let d30 = NaiveDate::from_ymd(y, 3, 1).pred(); + let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); let weekday = d30.weekday(); let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; @@ -2267,7 +2238,7 @@ mod tests { #[test] fn test_date_from_yo() { let yo_opt = NaiveDate::from_yo_opt; - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(yo_opt(2012, 0), None); assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); @@ -2297,7 +2268,7 @@ mod tests { #[test] fn test_date_from_isoywd() { let isoywd_opt = NaiveDate::from_isoywd_opt; - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); @@ -2355,8 +2326,8 @@ mod tests { let d = NaiveDate::from_ymd_opt(year, month, day); if let Some(d) = d { let w = d.iso_week(); - let d_ = NaiveDate::from_isoywd(w.year(), w.week(), d.weekday()); - assert_eq!(d, d_); + let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); + assert_eq!(d, d_.unwrap()); } } } @@ -2366,23 +2337,38 @@ mod tests { #[test] fn test_date_from_num_days_from_ce() { let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; - assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd(1, 1, 1))); - assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd(1, 1, 2))); - assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd(1, 1, 31))); - assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd(1, 2, 1))); - assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd(1, 2, 28))); - assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd(1, 3, 1))); - assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd(1, 12, 31))); - assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd(2, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd(3, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd(4, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd(5, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd(401, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 * 5 + 1), Some(NaiveDate::from_ymd(2001, 1, 1))); - assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd(1970, 1, 1))); - assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd(0, 12, 31))); // 1 BCE - assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd(-1, 12, 31))); // 2 BCE + assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); + assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); + assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); + assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); + assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); + assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); + assert_eq!( + from_ndays_from_ce(365 * 2 + 1), + Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap()) + ); + assert_eq!( + from_ndays_from_ce(365 * 3 + 1), + Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap()) + ); + assert_eq!( + from_ndays_from_ce(365 * 4 + 2), + Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap()) + ); + assert_eq!( + from_ndays_from_ce(146097 + 1), + Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap()) + ); + assert_eq!( + from_ndays_from_ce(146097 * 5 + 1), + Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) + ); + assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE + assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE for days in (-9999..10001).map(|x| x * 100) { assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); @@ -2398,30 +2384,63 @@ mod tests { fn test_date_from_weekday_of_month_opt() { let ymwd = NaiveDate::from_weekday_of_month_opt; assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd(2018, 8, 1))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd(2018, 8, 2))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd(2018, 8, 5))); - assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd(2018, 8, 6))); - assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd(2018, 8, 7))); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd(2018, 8, 8))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd(2018, 8, 12))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd(2018, 8, 16))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd(2018, 8, 23))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd(2018, 8, 30))); - assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd(2018, 8, 31))); + assert_eq!( + ymwd(2018, 8, Weekday::Wed, 1), + Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Thu, 1), + Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Sun, 1), + Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Mon, 1), + Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Tue, 1), + Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Wed, 2), + Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Sun, 2), + Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Thu, 3), + Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Thu, 4), + Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Thu, 5), + Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap()) + ); + assert_eq!( + ymwd(2018, 8, Weekday::Fri, 5), + Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap()) + ); assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); } #[test] fn test_date_fields() { fn check(year: i32, month: u32, day: u32, ordinal: u32) { - let d1 = NaiveDate::from_ymd(year, month, day); + let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); assert_eq!(d1.year(), year); assert_eq!(d1.month(), month); assert_eq!(d1.day(), day); assert_eq!(d1.ordinal(), ordinal); - let d2 = NaiveDate::from_yo(year, ordinal); + let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); assert_eq!(d2.year(), year); assert_eq!(d2.month(), month); assert_eq!(d2.day(), day); @@ -2453,66 +2472,66 @@ mod tests { #[test] fn test_date_weekday() { - assert_eq!(NaiveDate::from_ymd(1582, 10, 15).weekday(), Weekday::Fri); + assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); // May 20, 1875 = ISO 8601 reference date - assert_eq!(NaiveDate::from_ymd(1875, 5, 20).weekday(), Weekday::Thu); - assert_eq!(NaiveDate::from_ymd(2000, 1, 1).weekday(), Weekday::Sat); + assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); + assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); } #[test] fn test_date_with_fields() { - let d = NaiveDate::from_ymd(2000, 2, 29); - assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd(-400, 2, 29))); + let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); + assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); assert_eq!(d.with_year(-100), None); - assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd(1600, 2, 29))); + assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); assert_eq!(d.with_year(1900), None); - assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd(2000, 2, 29))); + assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_year(2001), None); - assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd(2004, 2, 29))); + assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); assert_eq!(d.with_year(i32::MAX), None); - let d = NaiveDate::from_ymd(2000, 4, 30); + let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); assert_eq!(d.with_month(0), None); - assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd(2000, 1, 30))); + assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); assert_eq!(d.with_month(2), None); - assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd(2000, 3, 30))); - assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd(2000, 4, 30))); - assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd(2000, 12, 30))); + assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); + assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); + assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); assert_eq!(d.with_month(13), None); assert_eq!(d.with_month(u32::MAX), None); - let d = NaiveDate::from_ymd(2000, 2, 8); + let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); assert_eq!(d.with_day(0), None); - assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd(2000, 2, 1))); - assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd(2000, 2, 29))); + assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); + assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_day(30), None); assert_eq!(d.with_day(u32::MAX), None); - let d = NaiveDate::from_ymd(2000, 5, 5); + let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); assert_eq!(d.with_ordinal(0), None); - assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd(2000, 1, 1))); - assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd(2000, 3, 1))); - assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd(2000, 12, 31))); + assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); + assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); + assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); assert_eq!(d.with_ordinal(367), None); assert_eq!(d.with_ordinal(u32::MAX), None); } #[test] fn test_date_num_days_from_ce() { - assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); for year in -9999..10001 { assert_eq!( - NaiveDate::from_ymd(year, 1, 1).num_days_from_ce(), - NaiveDate::from_ymd(year - 1, 12, 31).num_days_from_ce() + 1 + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 ); } } #[test] fn test_date_succ() { - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); @@ -2522,7 +2541,7 @@ mod tests { #[test] fn test_date_pred() { - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); @@ -2533,8 +2552,8 @@ mod tests { #[test] fn test_date_add() { fn check((y1, m1, d1): (i32, u32, u32), rhs: Duration, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd(y, m, d)); + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); assert_eq!(lhs.checked_add_signed(rhs), sum); assert_eq!(lhs.checked_sub_signed(-rhs), sum); } @@ -2563,8 +2582,8 @@ mod tests { #[test] fn test_date_sub() { fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Duration) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let rhs = NaiveDate::from_ymd(y2, m2, d2); + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); assert_eq!(lhs.signed_duration_since(rhs), diff); assert_eq!(rhs.signed_duration_since(lhs), -diff); } @@ -2583,8 +2602,8 @@ mod tests { #[test] fn test_date_add_days() { fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd(y, m, d)); + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); assert_eq!(lhs.checked_add_days(rhs), sum); } @@ -2609,8 +2628,8 @@ mod tests { #[test] fn test_date_sub_days() { fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let rhs = NaiveDate::from_ymd(y2, m2, d2); + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); assert_eq!(lhs - diff, rhs); } @@ -2627,7 +2646,7 @@ mod tests { #[test] fn test_date_addassignment() { - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 1); date += Duration::days(10); assert_eq!(date, ymd(2016, 10, 11)); @@ -2637,7 +2656,7 @@ mod tests { #[test] fn test_date_subassignment() { - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 11); date -= Duration::days(10); assert_eq!(date, ymd(2016, 10, 1)); @@ -2647,19 +2666,22 @@ mod tests { #[test] fn test_date_fmt() { - assert_eq!(format!("{:?}", NaiveDate::from_ymd(2012, 3, 4)), "2012-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(0, 3, 4)), "0000-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(-307, 3, 4)), "-0307-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(12345, 3, 4)), "+12345-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); - assert_eq!(NaiveDate::from_ymd(2012, 3, 4).to_string(), "2012-03-04"); - assert_eq!(NaiveDate::from_ymd(0, 3, 4).to_string(), "0000-03-04"); - assert_eq!(NaiveDate::from_ymd(-307, 3, 4).to_string(), "-0307-03-04"); - assert_eq!(NaiveDate::from_ymd(12345, 3, 4).to_string(), "+12345-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:+30?}", NaiveDate::from_ymd(1234, 5, 6)), "1234-05-06"); - assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07"); + assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); + assert_eq!( + format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), + "+12345-06-07" + ); } #[test] @@ -2715,7 +2737,7 @@ mod tests { #[test] fn test_date_parse_from_str() { - let ymd = NaiveDate::from_ymd; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); assert_eq!( NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymd(2014, 5, 7)) @@ -2735,7 +2757,7 @@ mod tests { #[test] fn test_date_format() { - let d = NaiveDate::from_ymd(2012, 3, 4); + let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); assert_eq!(d.format("%d,%e").to_string(), "04, 4"); @@ -2748,44 +2770,59 @@ mod tests { assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); // non-four-digit years - assert_eq!(NaiveDate::from_ymd(12345, 1, 1).format("%Y").to_string(), "+12345"); - assert_eq!(NaiveDate::from_ymd(1234, 1, 1).format("%Y").to_string(), "1234"); - assert_eq!(NaiveDate::from_ymd(123, 1, 1).format("%Y").to_string(), "0123"); - assert_eq!(NaiveDate::from_ymd(12, 1, 1).format("%Y").to_string(), "0012"); - assert_eq!(NaiveDate::from_ymd(1, 1, 1).format("%Y").to_string(), "0001"); - assert_eq!(NaiveDate::from_ymd(0, 1, 1).format("%Y").to_string(), "0000"); - assert_eq!(NaiveDate::from_ymd(-1, 1, 1).format("%Y").to_string(), "-0001"); - assert_eq!(NaiveDate::from_ymd(-12, 1, 1).format("%Y").to_string(), "-0012"); - assert_eq!(NaiveDate::from_ymd(-123, 1, 1).format("%Y").to_string(), "-0123"); - assert_eq!(NaiveDate::from_ymd(-1234, 1, 1).format("%Y").to_string(), "-1234"); - assert_eq!(NaiveDate::from_ymd(-12345, 1, 1).format("%Y").to_string(), "-12345"); + assert_eq!( + NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), + "+12345" + ); + assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); + assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); + assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); + assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); + assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); + assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); + assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); + assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); + assert_eq!( + NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), + "-12345" + ); // corner cases assert_eq!( - NaiveDate::from_ymd(2007, 12, 31).format("%G,%g,%U,%W,%V").to_string(), + NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), "2008,08,53,53,01" ); assert_eq!( - NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(), + NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), "2009,09,01,00,53" ); } #[test] fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).count(), 2); - assert_eq!(NaiveDate::from_ymd(-262144, 1, 3).iter_days().rev().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(-262144, 1, 3).unwrap().iter_days().rev().take(4).count(), + 2 + ); } #[test] fn test_week_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); - assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(262143, 12, 12).unwrap().iter_weeks().take(4).count(), + 2 + ); + assert_eq!( + NaiveDate::from_ymd_opt(-262144, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + 2 + ); } #[test] fn test_naiveweek() { - let date = NaiveDate::from_ymd(2022, 5, 18); + let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); let asserts = vec![ (Weekday::Mon, "2022-05-16", "2022-05-22"), (Weekday::Tue, "2022-05-17", "2022-05-23"), diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 428341ee1b..b67c87338d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -61,7 +61,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ``` /// use chrono::{NaiveDate, NaiveDateTime}; /// -/// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); +/// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// # let _ = dt; /// ``` /// @@ -71,7 +71,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// /// ``` /// # use chrono::{NaiveDate, NaiveDateTime}; -/// # let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); +/// # let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// use chrono::{Datelike, Timelike, Weekday}; /// /// assert_eq!(dt.weekday(), Weekday::Fri); @@ -94,8 +94,8 @@ impl NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); /// /// let dt = NaiveDateTime::new(d, t); /// assert_eq!(dt.date(), d); @@ -214,9 +214,9 @@ impl NaiveDateTime { /// let parse_from_str = NaiveDateTime::parse_from_str; /// /// assert_eq!(parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4))); + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap())); /// assert_eq!(parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms_micro(13, 23, 45, 678_900))); + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_micro_opt(13, 23, 45, 678_900).unwrap())); /// ``` /// /// Offset is ignored for the purpose of parsing. @@ -225,7 +225,7 @@ impl NaiveDateTime { /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17).and_hms(12, 34, 56))); + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// ``` /// /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by @@ -236,7 +236,7 @@ impl NaiveDateTime { /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 7, 1).and_hms_milli(8, 59, 59, 1_123))); + /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_milli_opt(8, 59, 59, 1_123).unwrap())); /// ``` /// /// Missing seconds are assumed to be zero, @@ -246,7 +246,7 @@ impl NaiveDateTime { /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// assert_eq!(parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), - /// Ok(NaiveDate::from_ymd(1994, 9, 4).and_hms(7, 15, 0))); + /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap())); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); @@ -276,8 +276,8 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.date(), NaiveDate::from_ymd(2016, 7, 8)); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); /// ``` #[inline] pub fn date(&self) -> NaiveDate { @@ -291,8 +291,8 @@ impl NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveTime}; /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.time(), NaiveTime::from_hms(9, 10, 11)); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); /// ``` #[inline] pub fn time(&self) -> NaiveTime { @@ -309,16 +309,16 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 980); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 980).unwrap(); /// assert_eq!(dt.timestamp(), 1); /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap(); /// assert_eq!(dt.timestamp(), 1_000_000_000); /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59); + /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(); /// assert_eq!(dt.timestamp(), -1); /// - /// let dt = NaiveDate::from_ymd(-1, 1, 1).and_hms(0, 0, 0); + /// let dt = NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(dt.timestamp(), -62198755200); /// ``` #[inline] @@ -344,13 +344,13 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms_milli(23, 59, 59, 100); + /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_milli_opt(23, 59, 59, 100).unwrap(); /// assert_eq!(dt.timestamp_millis(), -900); /// ``` #[inline] @@ -374,10 +374,10 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_micro(0, 0, 1, 444); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_micro(1, 46, 40, 555); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] @@ -406,10 +406,10 @@ impl NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime}; /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); /// /// const A_BILLION: i64 = 1_000_000_000; /// let nanos = dt.timestamp_nanos(); @@ -435,10 +435,10 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); /// assert_eq!(dt.timestamp_subsec_millis(), 123); /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); /// ``` #[inline] @@ -456,10 +456,10 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); /// assert_eq!(dt.timestamp_subsec_micros(), 123_456); /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); /// ``` #[inline] @@ -477,10 +477,10 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789); /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); /// ``` #[inline] @@ -505,7 +505,7 @@ impl NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::zero()), /// Some(hms(3, 5, 7))); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(1)), @@ -515,9 +515,9 @@ impl NaiveDateTime { /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(3600 + 60)), /// Some(hms(4, 6, 7))); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7))); + /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap())); /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(Duration::milliseconds(450)), /// Some(hmsm(3, 5, 8, 430))); /// ``` @@ -526,7 +526,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); /// ``` /// @@ -536,7 +536,7 @@ impl NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_add_signed(Duration::zero()), /// Some(hmsm(3, 5, 59, 1_300))); @@ -551,7 +551,7 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_add_signed(Duration::seconds(-10)), /// Some(hmsm(3, 5, 50, 300))); /// assert_eq!(leap.checked_add_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); + /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); @@ -578,13 +578,13 @@ impl NaiveDateTime { /// use chrono::{Months, NaiveDate, NaiveDateTime}; /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// .checked_add_months(Months::new(1)), - /// Some(NaiveDate::from_ymd(2014, 2, 1).and_hms(1, 0, 0)) + /// Some(NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -610,7 +610,7 @@ impl NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::zero()), /// Some(hms(3, 5, 7))); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(1)), @@ -620,9 +620,9 @@ impl NaiveDateTime { /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(3600 + 60)), /// Some(hms(2, 4, 7))); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7))); + /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap())); /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(Duration::milliseconds(670)), /// Some(hmsm(3, 5, 6, 780))); /// ``` @@ -631,7 +631,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); /// ``` /// @@ -641,7 +641,7 @@ impl NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_sub_signed(Duration::zero()), /// Some(hmsm(3, 5, 59, 1_300))); @@ -652,7 +652,7 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_sub_signed(Duration::seconds(60)), /// Some(hmsm(3, 5, 0, 300))); /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); + /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); @@ -679,13 +679,13 @@ impl NaiveDateTime { /// use chrono::{Months, NaiveDate, NaiveDateTime}; /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// .checked_sub_months(Months::new(1)), - /// Some(NaiveDate::from_ymd(2013, 12, 1).and_hms(1, 0, 0)) + /// Some(NaiveDate::from_ymd_opt(2013, 12, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -725,12 +725,12 @@ impl NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); - /// assert_eq!(d.and_hms(3, 5, 7).signed_duration_since(d.and_hms(2, 4, 6)), + /// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), /// Duration::seconds(3600 + 60 + 1)); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); - /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).signed_duration_since(d0.and_hms(0, 0, 0)), + /// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap().signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); /// ``` /// @@ -740,10 +740,10 @@ impl NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; - /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); - /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms(23, 0, 0)), + /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); + /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), /// Duration::seconds(3600) + Duration::milliseconds(500)); - /// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0).signed_duration_since(leap), + /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { @@ -763,7 +763,7 @@ impl NaiveDateTime { /// use chrono::format::strftime::StrftimeItems; /// /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); /// ``` @@ -774,7 +774,7 @@ impl NaiveDateTime { /// # use chrono::NaiveDate; /// # use chrono::format::strftime::StrftimeItems; /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] @@ -807,7 +807,7 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); /// ``` @@ -816,7 +816,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::NaiveDate; - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` @@ -842,7 +842,7 @@ impl NaiveDateTime { /// /// ``` /// use chrono::{NaiveDate, Utc}; - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4).and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timezone(), Utc); pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { tz.from_local_datetime(self) @@ -864,7 +864,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.year(), 2015); /// ``` #[inline] @@ -883,7 +883,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.month(), 9); /// ``` #[inline] @@ -902,7 +902,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.month0(), 8); /// ``` #[inline] @@ -921,7 +921,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.day(), 25); /// ``` #[inline] @@ -940,7 +940,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.day0(), 24); /// ``` #[inline] @@ -959,7 +959,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.ordinal(), 268); /// ``` #[inline] @@ -978,7 +978,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.ordinal0(), 267); /// ``` #[inline] @@ -995,7 +995,7 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Weekday}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.weekday(), Weekday::Fri); /// ``` #[inline] @@ -1019,9 +1019,9 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.with_year(2016), Some(NaiveDate::from_ymd(2016, 9, 25).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_year(-308), Some(NaiveDate::from_ymd(-308, 9, 25).and_hms(12, 34, 56))); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.with_year(2016), Some(NaiveDate::from_ymd_opt(2016, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// assert_eq!(dt.with_year(-308), Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// ``` #[inline] fn with_year(&self, year: i32) -> Option { @@ -1039,8 +1039,8 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month(10), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.with_month(10), Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_month(13), None); // no month 13 /// assert_eq!(dt.with_month(2), None); // no February 30 /// ``` @@ -1060,8 +1060,8 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month0(9), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.with_month0(9), Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_month0(12), None); // no month 13 /// assert_eq!(dt.with_month0(1), None); // no February 30 /// ``` @@ -1081,8 +1081,8 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day(30), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.with_day(30), Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_day(31), None); // no September 31 /// ``` #[inline] @@ -1101,8 +1101,8 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day0(29), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.with_day0(29), Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_day0(30), None); // no September 31 /// ``` #[inline] @@ -1121,16 +1121,16 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { @@ -1148,16 +1148,16 @@ impl Datelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// assert_eq!(dt.with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap())); /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { @@ -1175,7 +1175,7 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.hour(), 12); /// ``` #[inline] @@ -1192,7 +1192,7 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.minute(), 34); /// ``` #[inline] @@ -1209,7 +1209,7 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.second(), 56); /// ``` #[inline] @@ -1228,7 +1228,7 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.nanosecond(), 789_000_000); /// ``` #[inline] @@ -1247,9 +1247,9 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_hour(7), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(7, 34, 56, 789))); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap())); /// assert_eq!(dt.with_hour(24), None); /// ``` #[inline] @@ -1269,9 +1269,9 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_minute(45), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 45, 56, 789))); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 45, 56, 789).unwrap())); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] @@ -1292,9 +1292,9 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_second(17), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 17, 789))); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 17, 789).unwrap())); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] @@ -1315,11 +1315,11 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 333_333_333))); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 1_333_333_333))); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 1_333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` #[inline] @@ -1346,17 +1346,17 @@ impl Timelike for NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7) + Duration::zero(), hms(3, 5, 7)); /// assert_eq!(hms(3, 5, 7) + Duration::seconds(1), hms(3, 5, 8)); /// assert_eq!(hms(3, 5, 7) + Duration::seconds(-1), hms(3, 5, 6)); /// assert_eq!(hms(3, 5, 7) + Duration::seconds(3600 + 60), hms(4, 6, 7)); /// assert_eq!(hms(3, 5, 7) + Duration::seconds(86_400), -/// from_ymd(2016, 7, 9).and_hms(3, 5, 7)); +/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()); /// assert_eq!(hms(3, 5, 7) + Duration::days(365), -/// from_ymd(2017, 7, 8).and_hms(3, 5, 7)); +/// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap()); /// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); /// ``` /// @@ -1366,7 +1366,7 @@ impl Timelike for NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap + Duration::milliseconds(-500), hmsm(3, 5, 59, 800)); @@ -1375,7 +1375,7 @@ impl Timelike for NaiveDateTime { /// assert_eq!(leap + Duration::seconds(10), hmsm(3, 6, 9, 300)); /// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); /// assert_eq!(leap + Duration::days(1), -/// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); +/// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1409,28 +1409,28 @@ impl Add for NaiveDateTime { /// use std::str::FromStr; /// /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(1, 0, 0) + Months::new(1), - /// NaiveDate::from_ymd(2014, 2, 1).and_hms(1, 0, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), + /// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 2, 0) + Months::new(11), - /// NaiveDate::from_ymd(2014, 12, 1).and_hms(0, 2, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), + /// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 0, 3) + Months::new(12), - /// NaiveDate::from_ymd(2015, 1, 1).and_hms(0, 0, 3) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), + /// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 1).and_hms(0, 0, 4) + Months::new(13), - /// NaiveDate::from_ymd(2015, 2, 1).and_hms(0, 0, 4) + /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), + /// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2014, 1, 31).and_hms(0, 5, 0) + Months::new(1), - /// NaiveDate::from_ymd(2014, 2, 28).and_hms(0, 5, 0) + /// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), + /// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() /// ); /// assert_eq!( - /// NaiveDate::from_ymd(2020, 1, 31).and_hms(6, 0, 0) + Months::new(1), - /// NaiveDate::from_ymd(2020, 2, 29).and_hms(6, 0, 0) + /// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), + /// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() /// ); /// ``` fn add(self, rhs: Months) -> Self::Output { @@ -1457,17 +1457,17 @@ impl Add for NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7) - Duration::zero(), hms(3, 5, 7)); /// assert_eq!(hms(3, 5, 7) - Duration::seconds(1), hms(3, 5, 6)); /// assert_eq!(hms(3, 5, 7) - Duration::seconds(-1), hms(3, 5, 8)); /// assert_eq!(hms(3, 5, 7) - Duration::seconds(3600 + 60), hms(2, 4, 7)); /// assert_eq!(hms(3, 5, 7) - Duration::seconds(86_400), -/// from_ymd(2016, 7, 7).and_hms(3, 5, 7)); +/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()); /// assert_eq!(hms(3, 5, 7) - Duration::days(365), -/// from_ymd(2015, 7, 9).and_hms(3, 5, 7)); +/// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap()); /// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); /// ``` /// @@ -1477,14 +1477,14 @@ impl Add for NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap - Duration::milliseconds(200), hmsm(3, 5, 59, 1_100)); /// assert_eq!(leap - Duration::milliseconds(500), hmsm(3, 5, 59, 800)); /// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); /// assert_eq!(leap - Duration::days(1), -/// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); +/// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1515,16 +1515,16 @@ impl SubAssign for NaiveDateTime { /// use std::str::FromStr; /// /// assert_eq!( -/// NaiveDate::from_ymd(2014, 01, 01).and_hms(01, 00, 00) - Months::new(11), -/// NaiveDate::from_ymd(2013, 02, 01).and_hms(01, 00, 00) +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() - Months::new(11), +/// NaiveDate::from_ymd_opt(2013, 02, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd(2014, 01, 01).and_hms(00, 02, 00) - Months::new(12), -/// NaiveDate::from_ymd(2013, 01, 01).and_hms(00, 02, 00) +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() - Months::new(12), +/// NaiveDate::from_ymd_opt(2013, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd(2014, 01, 01).and_hms(00, 00, 03) - Months::new(13), -/// NaiveDate::from_ymd(2012, 12, 01).and_hms(00, 00, 03) +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() - Months::new(13), +/// NaiveDate::from_ymd_opt(2012, 12, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() /// ); /// ``` impl Sub for NaiveDateTime { @@ -1554,11 +1554,11 @@ impl Sub for NaiveDateTime { /// let from_ymd = NaiveDate::from_ymd; /// /// let d = from_ymd(2016, 7, 8); -/// assert_eq!(d.and_hms(3, 5, 7) - d.and_hms(2, 4, 6), Duration::seconds(3600 + 60 + 1)); +/// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), Duration::seconds(3600 + 60 + 1)); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); -/// assert_eq!(d.and_hms_milli(0, 7, 6, 500) - d0.and_hms(0, 0, 0), +/// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); /// ``` /// @@ -1568,10 +1568,10 @@ impl Sub for NaiveDateTime { /// ``` /// # use chrono::{Duration, NaiveDate}; /// # let from_ymd = NaiveDate::from_ymd; -/// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms(23, 0, 0), +/// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), /// Duration::seconds(3600) + Duration::milliseconds(500)); -/// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0) - leap, +/// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` impl Sub for NaiveDateTime { @@ -1615,7 +1615,7 @@ impl Sub for NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); /// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); /// ``` /// @@ -1623,7 +1623,7 @@ impl Sub for NaiveDateTime { /// /// ``` /// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); /// ``` impl fmt::Debug for NaiveDateTime { @@ -1646,7 +1646,7 @@ impl fmt::Debug for NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); /// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); /// ``` /// @@ -1654,7 +1654,7 @@ impl fmt::Debug for NaiveDateTime { /// /// ``` /// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); /// ``` impl fmt::Display for NaiveDateTime { @@ -1671,10 +1671,10 @@ impl fmt::Display for NaiveDateTime { /// ``` /// use chrono::{NaiveDateTime, NaiveDate}; /// -/// let dt = NaiveDate::from_ymd(2015, 9, 18).and_hms(23, 56, 4); +/// let dt = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); /// -/// let dt = NaiveDate::from_ymd(12345, 6, 7).and_hms_milli(7, 59, 59, 1_500); // leap second +/// let dt = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap().and_hms_milli_opt(7, 59, 59, 1_500).unwrap(); // leap second /// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); /// /// assert!("foo".parse::().is_err()); @@ -1723,7 +1723,7 @@ impl str::FromStr for NaiveDateTime { /// ``` impl Default for NaiveDateTime { fn default() -> Self { - NaiveDateTime::from_timestamp(0, 0) + NaiveDateTime::from_timestamp_opt(0, 0).unwrap() } } @@ -1734,27 +1734,37 @@ where E: ::std::fmt::Debug, { assert_eq!( - to_string(&NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)).ok(), + to_string( + &NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() + ) + .ok(), Some(r#""2016-07-08T09:10:48.090""#.into()) ); assert_eq!( - to_string(&NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), + to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) + .ok(), Some(r#""2014-07-24T12:34:06""#.into()) ); assert_eq!( - to_string(&NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)).ok(), + to_string( + &NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap() + ) + .ok(), Some(r#""0000-01-01T00:00:60""#.into()) ); assert_eq!( - to_string(&NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)).ok(), + to_string( + &NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap() + ) + .ok(), Some(r#""-0001-12-31T23:59:59.000000007""#.into()) ); assert_eq!( - to_string(&NaiveDate::MIN.and_hms(0, 0, 0)).ok(), + to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), Some(r#""-262144-01-01T00:00:00""#.into()) ); assert_eq!( - to_string(&NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(), + to_string(&NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), Some(r#""+262143-12-31T23:59:60.999999999""#.into()) ); } @@ -1767,36 +1777,43 @@ where { assert_eq!( from_str(r#""2016-07-08T09:10:48.090""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) + Some( + NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() + ) ); assert_eq!( from_str(r#""2016-7-8T9:10:48.09""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) + Some( + NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() + ) ); assert_eq!( from_str(r#""2014-07-24T12:34:06""#).ok(), - Some(NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)) + Some(NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) ); assert_eq!( from_str(r#""0000-01-01T00:00:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap()) ); assert_eq!( from_str(r#""0-1-1T0:0:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap()) ); assert_eq!( from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), - Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)) + Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap()) + ); + assert_eq!( + from_str(r#""-262144-01-01T00:00:00""#).ok(), + Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) ); - assert_eq!(from_str(r#""-262144-01-01T00:00:00""#).ok(), Some(NaiveDate::MIN.and_hms(0, 0, 0))); assert_eq!( from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), - Some(NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); assert_eq!( from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveDate::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); // bad formats @@ -1829,12 +1846,12 @@ where { assert_eq!( *from_str("0").unwrap(), - NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), + NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(), "should parse integers as timestamps" ); assert_eq!( *from_str("-1").unwrap(), - NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59), + NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(), "should parse integers as timestamps" ); } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 70369c3888..629f12f2e3 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -69,7 +69,7 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// time: NaiveDateTime /// } /// -/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -104,7 +104,7 @@ pub mod ts_nanoseconds { /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -189,7 +189,7 @@ pub mod ts_nanoseconds { /// time: Option /// } /// -/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -224,7 +224,7 @@ pub mod ts_nanoseconds_option { /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -315,7 +315,7 @@ pub mod ts_nanoseconds_option { /// time: NaiveDateTime /// } /// -/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -350,7 +350,7 @@ pub mod ts_microseconds { /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -438,7 +438,7 @@ pub mod ts_microseconds { /// time: Option /// } /// -/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -473,7 +473,7 @@ pub mod ts_microseconds_option { /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_micro(02, 04, 59, 918355)), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -564,7 +564,7 @@ pub mod ts_microseconds_option { /// time: NaiveDateTime /// } /// -/// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -599,7 +599,7 @@ pub mod ts_milliseconds { /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -684,7 +684,7 @@ pub mod ts_milliseconds { /// time: Option /// } /// -/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -719,7 +719,7 @@ pub mod ts_milliseconds_option { /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -810,7 +810,7 @@ pub mod ts_milliseconds_option { /// time: NaiveDateTime /// } /// -/// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); +/// let time = NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -845,7 +845,7 @@ pub mod ts_seconds { /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), + /// time: NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -927,7 +927,7 @@ pub mod ts_seconds { /// time: Option /// } /// -/// let time = Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -962,7 +962,7 @@ pub mod ts_seconds_option { /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd(2018, 5, 17).and_hms(02, 04, 59)), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); @@ -1056,7 +1056,7 @@ fn test_serde_bincode() { use crate::naive::NaiveDate; use bincode::{deserialize, serialize}; - let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); + 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); @@ -1076,7 +1076,10 @@ fn test_serde_bincode_optional() { two: Option>, } - let expected = Test { one: Some(1), two: Some(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; + let expected = Test { + one: Some(1), + two: Some(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 1, 1).unwrap()), + }; let bytes: Vec = serialize(&expected).unwrap(); let actual = deserialize::(&(bytes)).unwrap(); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 8efc46b19b..bd070994f9 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -7,7 +7,8 @@ use std::i64; #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); @@ -24,8 +25,10 @@ fn test_datetime_add() { rhs: Duration, result: Option<(i32, u32, u32, u32, u32, u32)>, ) { - let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let sum = result.map(|(y, m, d, h, n, s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); + let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let sum = result.map(|(y, m, d, h, n, s)| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }); assert_eq!(lhs.checked_add_signed(rhs), sum); assert_eq!(lhs.checked_sub_signed(-rhs), sum); } @@ -40,7 +43,8 @@ fn test_datetime_add() { // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + let max_days_from_year_0 = + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), @@ -50,7 +54,8 @@ fn test_datetime_add() { check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); - let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); + let min_days_from_year_0 = + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); @@ -58,7 +63,8 @@ fn test_datetime_add() { #[test] fn test_datetime_sub() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let since = NaiveDateTime::signed_duration_since; assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), Duration::zero()); assert_eq!( @@ -81,7 +87,8 @@ fn test_datetime_sub() { #[test] fn test_datetime_addassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date += Duration::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); @@ -91,7 +98,8 @@ fn test_datetime_addassignment() { #[test] fn test_datetime_subassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date -= Duration::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); @@ -101,7 +109,9 @@ fn test_datetime_subassignment() { #[test] fn test_datetime_timestamp() { - let to_timestamp = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s).timestamp(); + let to_timestamp = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().timestamp() + }; assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); @@ -156,8 +166,11 @@ fn test_datetime_from_str() { #[test] fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let ymdhmsn = |y, m, d, h, n, s, nano| NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano); + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let ymdhmsn = |y, m, d, h, n, s, nano| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap() + }; assert_eq!( NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) @@ -201,13 +214,14 @@ fn test_datetime_parse_from_str() { #[test] fn test_datetime_format() { - let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321); + let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); assert_eq!(dt.format("%s").to_string(), "1283929614"); assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); // a horror of leap second: coming near to you. - let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000); + let dt = + NaiveDate::from_ymd_opt(2012, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. } @@ -215,7 +229,7 @@ fn test_datetime_format() { #[test] fn test_datetime_add_sub_invariant() { // issue #37 - let base = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); + let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let t = -946684799990000; let time = base + Duration::microseconds(t); assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); @@ -229,7 +243,7 @@ fn test_nanosecond_range() { let nanos = parsed.timestamp_nanos(); assert_eq!( parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() ); let minimum = "1677-09-21T00:12:44.000000000"; @@ -237,18 +251,18 @@ fn test_nanosecond_range() { let nanos = parsed.timestamp_nanos(); assert_eq!( parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() ); } #[test] fn test_and_timezone() { - let ndt = NaiveDate::from_ymd(2022, 6, 15).and_hms(18, 59, 36); + let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); let dt_utc = ndt.and_local_timezone(Utc).unwrap(); assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); - let offset_tz = FixedOffset::west(4 * 3600); + let offset_tz = FixedOffset::west_opt(4 * 3600).unwrap(); let dt_offset = ndt.and_local_timezone(offset_tz).unwrap(); assert_eq!(dt_offset.naive_local(), ndt); assert_eq!(dt_offset.timezone(), offset_tz); diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index b3ccecdedd..109535f547 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -68,7 +68,7 @@ impl IsoWeek { /// # use chrono::{NaiveDate, Datelike, Weekday}; /// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); /// assert_eq!(d.year(), 2014); - /// assert_eq!(d, NaiveDate::from_ymd(2014, 12, 29)); + /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` #[inline] pub fn year(&self) -> i32 { @@ -119,17 +119,17 @@ impl IsoWeek { /// ``` /// use chrono::{NaiveDate, Datelike}; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5).iso_week()), "2015-W36"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 3).iso_week()), "0000-W01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31).iso_week()), "9999-W52"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), "2015-W36"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 3).unwrap().iso_week()), "0000-W01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), "9999-W52"); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::{NaiveDate, Datelike}; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 2).iso_week()), "-0001-W52"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31).iso_week()), "+10000-W52"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 2).unwrap().iso_week()), "-0001-W52"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), "+10000-W52"); /// ``` impl fmt::Debug for IsoWeek { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 3132a0048f..44184f86b2 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -76,11 +76,11 @@ mod tests; /// ``` /// use chrono::{NaiveDate, NaiveTime, Utc, TimeZone}; /// -/// let t = NaiveTime::from_hms_milli(8, 59, 59, 1_000); +/// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); /// -/// let dt1 = NaiveDate::from_ymd(2015, 7, 1).and_hms_micro(8, 59, 59, 1_000_000); +/// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_micro_opt(8, 59, 59, 1_000_000).unwrap(); /// -/// let dt2 = Utc.ymd(2015, 6, 30).and_hms_nano(23, 59, 59, 1_000_000_000); +/// let dt2 = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap(); /// # let _ = (t, dt1, dt2); /// ``` /// @@ -163,7 +163,7 @@ mod tests; /// ``` /// use chrono::{Utc, TimeZone}; /// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_000); +/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// @@ -177,10 +177,10 @@ mod tests; /// ``` /// use chrono::{DateTime, Utc, TimeZone}; /// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 56, 4, 1_000); +/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 56, 4, 1_000).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); /// -/// let dt = Utc.ymd(2015, 6, 30).and_hms(23, 56, 5); +/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_opt(23, 56, 5).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); /// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); /// ``` @@ -407,9 +407,9 @@ impl NaiveTime { /// let parse_from_str = NaiveTime::parse_from_str; /// /// assert_eq!(parse_from_str("23:56:04", "%H:%M:%S"), - /// Ok(NaiveTime::from_hms(23, 56, 4))); + /// Ok(NaiveTime::from_hms_opt(23, 56, 4).unwrap())); /// assert_eq!(parse_from_str("pm012345.6789", "%p%I%M%S%.f"), - /// Ok(NaiveTime::from_hms_micro(13, 23, 45, 678_900))); + /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap())); /// ``` /// /// Date and offset is ignored for the purpose of parsing. @@ -418,7 +418,7 @@ impl NaiveTime { /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveTime::from_hms(12, 34, 56))); + /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap())); /// ``` /// /// [Leap seconds](#leap-second-handling) are correctly handled by @@ -429,7 +429,7 @@ impl NaiveTime { /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("08:59:60.123", "%H:%M:%S%.f"), - /// Ok(NaiveTime::from_hms_milli(8, 59, 59, 1_123))); + /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap())); /// ``` /// /// Missing seconds are assumed to be zero, @@ -439,7 +439,7 @@ impl NaiveTime { /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; /// assert_eq!(parse_from_str("7:15", "%H:%M"), - /// Ok(NaiveTime::from_hms(7, 15, 0))); + /// Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("12", "%H").is_err()); @@ -674,7 +674,7 @@ impl NaiveTime { /// use chrono::format::strftime::StrftimeItems; /// /// let fmt = StrftimeItems::new("%H:%M:%S"); - /// let t = NaiveTime::from_hms(23, 56, 4); + /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); /// ``` @@ -685,7 +685,7 @@ impl NaiveTime { /// # use chrono::NaiveTime; /// # use chrono::format::strftime::StrftimeItems; /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); - /// # let t = NaiveTime::from_hms(23, 56, 4); + /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] @@ -718,7 +718,7 @@ impl NaiveTime { /// ``` /// use chrono::NaiveTime; /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); + /// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); @@ -728,7 +728,7 @@ impl NaiveTime { /// /// ``` /// # use chrono::NaiveTime; - /// # let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); + /// # let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); @@ -759,8 +759,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).hour(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).hour(), 23); + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().hour(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().hour(), 23); /// ``` #[inline] fn hour(&self) -> u32 { @@ -774,8 +774,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).minute(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).minute(), 56); + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().minute(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().minute(), 56); /// ``` #[inline] fn minute(&self) -> u32 { @@ -789,8 +789,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).second(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).second(), 4); + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().second(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().second(), 4); /// ``` /// /// This method never returns 60 even when it is a leap second. @@ -799,7 +799,7 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.second(), 59); /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); /// ``` @@ -817,8 +817,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).nanosecond(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).nanosecond(), 12_345_678); + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().nanosecond(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().nanosecond(), 12_345_678); /// ``` /// /// Leap seconds may have seemingly out-of-range return values. @@ -827,7 +827,7 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.nanosecond(), 1_000_000_000); /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); /// ``` @@ -845,8 +845,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano(7, 56, 4, 12_345_678))); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano_opt(7, 56, 4, 12_345_678).unwrap())); /// assert_eq!(dt.with_hour(24), None); /// ``` #[inline] @@ -867,8 +867,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano(23, 45, 4, 12_345_678))); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap())); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] @@ -891,8 +891,8 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano(23, 56, 17, 12_345_678))); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap())); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] @@ -915,9 +915,9 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 333_333_333))); + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` /// @@ -928,9 +928,9 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{NaiveTime, Timelike}; - /// # let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); + /// # let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!(dt.with_nanosecond(1_333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 1_333_333_333))); + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 1_333_333_333).unwrap())); /// ``` #[inline] fn with_nanosecond(&self, nano: u32) -> Option { @@ -947,11 +947,11 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms(1, 2, 3).num_seconds_from_midnight(), + /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), /// 3723); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).num_seconds_from_midnight(), + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), /// 86164); - /// assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).num_seconds_from_midnight(), + /// assert_eq!(NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), /// 86399); /// ``` #[inline] @@ -1156,17 +1156,17 @@ impl Sub for NaiveTime { /// ``` /// use chrono::NaiveTime; /// -/// assert_eq!(format!("{:?}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), "23:56:04.012"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), "23:56:04.001234"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), "23:56:04.000123456"); /// ``` /// /// Leap seconds may also be used. /// /// ``` /// # use chrono::NaiveTime; -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), "06:59:60.500"); /// ``` impl fmt::Debug for NaiveTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1206,17 +1206,17 @@ impl fmt::Debug for NaiveTime { /// ``` /// use chrono::NaiveTime; /// -/// assert_eq!(format!("{}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), "23:56:04.012"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), "23:56:04.001234"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), "23:56:04.000123456"); /// ``` /// /// Leap seconds may also be used. /// /// ``` /// # use chrono::NaiveTime; -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), "06:59:60.500"); /// ``` impl fmt::Display for NaiveTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1232,13 +1232,13 @@ impl fmt::Display for NaiveTime { /// ``` /// use chrono::NaiveTime; /// -/// let t = NaiveTime::from_hms(23, 56, 4); +/// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!("23:56:04".parse::(), Ok(t)); /// -/// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); +/// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); /// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); /// -/// let t = NaiveTime::from_hms_nano(23, 59, 59, 1_234_567_890); // leap second +/// let t = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_234_567_890).unwrap(); // leap second /// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); /// /// assert!("foo".parse::().is_err()); @@ -1273,11 +1273,11 @@ impl str::FromStr for NaiveTime { /// use chrono::NaiveTime; /// /// let default_time = NaiveTime::default(); -/// assert_eq!(default_time, NaiveTime::from_hms(0, 0, 0)); +/// assert_eq!(default_time, NaiveTime::from_hms_opt(0, 0, 0).unwrap()); /// ``` impl Default for NaiveTime { fn default() -> Self { - NaiveTime::from_hms(0, 0, 0) + NaiveTime::from_hms_opt(0, 0, 0).unwrap() } } @@ -1287,27 +1287,36 @@ where F: Fn(&NaiveTime) -> Result, E: ::std::fmt::Debug, { - assert_eq!(to_string(&NaiveTime::from_hms(0, 0, 0)).ok(), Some(r#""00:00:00""#.into())); assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 0, 950)).ok(), + to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""00:00:00""#.into()) + ); + assert_eq!( + to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(), Some(r#""00:00:00.950""#.into()) ); assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 59, 1_000)).ok(), + to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(), Some(r#""00:00:60""#.into()) ); - assert_eq!(to_string(&NaiveTime::from_hms(0, 1, 2)).ok(), Some(r#""00:01:02""#.into())); assert_eq!( - to_string(&NaiveTime::from_hms_nano(3, 5, 7, 98765432)).ok(), + to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(), + Some(r#""00:01:02""#.into()) + ); + assert_eq!( + to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(), Some(r#""03:05:07.098765432""#.into()) ); - assert_eq!(to_string(&NaiveTime::from_hms(7, 8, 9)).ok(), Some(r#""07:08:09""#.into())); assert_eq!( - to_string(&NaiveTime::from_hms_micro(12, 34, 56, 789)).ok(), + to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(), + Some(r#""07:08:09""#.into()) + ); + assert_eq!( + to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(), Some(r#""12:34:56.000789""#.into()) ); assert_eq!( - to_string(&NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)).ok(), + to_string(&NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), Some(r#""23:59:60.999999999""#.into()) ); } @@ -1318,28 +1327,37 @@ where F: Fn(&str) -> Result, E: ::std::fmt::Debug, { - assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""00:00:00.950""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""0:0:0.95""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""00:00:60""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 59, 1_000))); - assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms(0, 1, 2))); + assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!( + from_str(r#""00:00:00.950""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""0:0:0.95""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""00:00:60""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()) + ); + assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap())); assert_eq!( from_str(r#""03:05:07.098765432""#).ok(), - Some(NaiveTime::from_hms_nano(3, 5, 7, 98765432)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()) ); - assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms(7, 8, 9))); + assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap())); assert_eq!( from_str(r#""12:34:56.000789""#).ok(), - Some(NaiveTime::from_hms_micro(12, 34, 56, 789)) + Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()) ); assert_eq!( from_str(r#""23:59:60.999999999""#).ok(), - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); assert_eq!( from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); // bad formats diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index 369c7a1c27..c7394fb575 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -58,7 +58,7 @@ fn test_serde_bincode() { // it is not self-describing. use bincode::{deserialize, serialize}; - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); + 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/naive/time/tests.rs b/src/naive/time/tests.rs index b853c9f22d..62c46a247e 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -7,15 +7,15 @@ use std::u32; fn test_time_from_hms_milli() { assert_eq!( NaiveTime::from_hms_milli_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) ); assert_eq!( NaiveTime::from_hms_milli_opt(3, 5, 7, 777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_000_000)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_000_000).unwrap()) ); assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check @@ -26,19 +26,19 @@ fn test_time_from_hms_milli() { fn test_time_from_hms_micro() { assert_eq!( NaiveTime::from_hms_micro_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro_opt(3, 5, 7, 333), - Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 333_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_999_000)) + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_999_000).unwrap()) ); assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check @@ -47,23 +47,41 @@ fn test_time_from_hms_micro() { #[test] fn test_time_hms() { - assert_eq!(NaiveTime::from_hms(3, 5, 7).hour(), 3); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(0), Some(NaiveTime::from_hms(0, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(23), Some(NaiveTime::from_hms(23, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(24), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(u32::MAX), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().hour(), 3); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(0), + Some(NaiveTime::from_hms_opt(0, 5, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(23), + Some(NaiveTime::from_hms_opt(23, 5, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(24), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(u32::MAX), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).minute(), 5); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(0), Some(NaiveTime::from_hms(3, 0, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(59), Some(NaiveTime::from_hms(3, 59, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(u32::MAX), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().minute(), 5); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(0), + Some(NaiveTime::from_hms_opt(3, 0, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(59), + Some(NaiveTime::from_hms_opt(3, 59, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(u32::MAX), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).second(), 7); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(0), Some(NaiveTime::from_hms(3, 5, 0))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(59), Some(NaiveTime::from_hms(3, 5, 59))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(u32::MAX), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().second(), 7); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(0), + Some(NaiveTime::from_hms_opt(3, 5, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(59), + Some(NaiveTime::from_hms_opt(3, 5, 59).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(u32::MAX), None); } #[test] @@ -75,7 +93,7 @@ fn test_time_add() { }}; } - let hmsm = NaiveTime::from_hms_milli; + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); @@ -98,7 +116,7 @@ fn test_time_add() { #[test] fn test_time_overflowing_add() { - let hmsm = NaiveTime::from_hms_milli; + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); assert_eq!( hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), @@ -126,7 +144,7 @@ fn test_time_overflowing_add() { #[test] fn test_time_addassignment() { - let hms = NaiveTime::from_hms; + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); time += Duration::hours(10); assert_eq!(time, hms(22, 12, 12)); @@ -136,7 +154,7 @@ fn test_time_addassignment() { #[test] fn test_time_subassignment() { - let hms = NaiveTime::from_hms; + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); time -= Duration::hours(10); assert_eq!(time, hms(2, 12, 12)); @@ -154,7 +172,7 @@ fn test_time_sub() { }}; } - let hmsm = NaiveTime::from_hms_milli; + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); @@ -179,14 +197,32 @@ fn test_time_sub() { #[test] fn test_time_fmt() { - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 999)), "23:59:59.999"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_000)), "23:59:60"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_001)), "23:59:60.001"); - assert_eq!(format!("{}", NaiveTime::from_hms_micro(0, 0, 0, 43210)), "00:00:00.043210"); - assert_eq!(format!("{}", NaiveTime::from_hms_nano(0, 0, 0, 6543210)), "00:00:00.006543210"); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 999).unwrap()), + "23:59:59.999" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap()), + "23:59:60" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_001).unwrap()), + "23:59:60.001" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_micro_opt(0, 0, 0, 43210).unwrap()), + "00:00:00.043210" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_nano_opt(0, 0, 0, 6543210).unwrap()), + "00:00:00.006543210" + ); // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); + assert_eq!( + format!("{:30}", NaiveTime::from_hms_milli_opt(3, 5, 7, 9).unwrap()), + "03:05:07.009" + ); } #[test] @@ -239,7 +275,7 @@ fn test_date_from_str() { #[test] fn test_time_parse_from_str() { - let hms = NaiveTime::from_hms; + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); assert_eq!( NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(hms(12, 34, 56)) @@ -250,7 +286,7 @@ fn test_time_parse_from_str() { #[test] fn test_time_format() { - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); assert_eq!(t.format("%M").to_string(), "05"); assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); @@ -260,19 +296,22 @@ fn test_time_format() { assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - let t = NaiveTime::from_hms_micro(3, 5, 7, 432100); + let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); - let t = NaiveTime::from_hms_milli(3, 5, 7, 210); + let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); - let t = NaiveTime::from_hms(3, 5, 7); + let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); // corner cases - assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM"); - assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), "23:59:60"); + assert_eq!(NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), "01:57:09 PM"); + assert_eq!( + NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), + "23:59:60" + ); } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 0dc047e6dd..0c6ccae7b8 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -48,8 +48,8 @@ impl FixedOffset { /// ``` /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::east(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); + /// let datetime = FixedOffset::east_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() + /// .and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` pub fn east_opt(secs: i32) -> Option { @@ -79,8 +79,8 @@ impl FixedOffset { /// ``` /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::west(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); + /// let datetime = FixedOffset::west_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() + /// .and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` pub fn west_opt(secs: i32) -> Option { @@ -231,19 +231,35 @@ mod tests { // starting from 0.3 we don't have an offset exceeding one day. // this makes everything easier! assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)), + format!("{:?}", FixedOffset::east_opt(86399).unwrap().ymd_opt(2012, 2, 29).unwrap()), "2012-02-29+23:59:59".to_string() ); assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)), + format!( + "{:?}", + FixedOffset::east_opt(86399) + .unwrap() + .ymd_opt(2012, 2, 29) + .unwrap() + .and_hms_opt(5, 6, 7) + .unwrap() + ), "2012-02-29T05:06:07+23:59:59".to_string() ); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)), + format!("{:?}", FixedOffset::west_opt(86399).unwrap().ymd_opt(2012, 3, 4).unwrap()), "2012-03-04-23:59:59".to_string() ); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)), + format!( + "{:?}", + FixedOffset::west_opt(86399) + .unwrap() + .ymd_opt(2012, 3, 4) + .unwrap() + .and_hms_opt(5, 6, 7) + .unwrap() + ), "2012-03-04T05:06:07-23:59:59".to_string() ); } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 0024cf0c69..29a3cdd429 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -81,7 +81,9 @@ impl Local { let now: DateTime = super::Utc::now(); // Workaround missing timezone logic in `time` crate - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); + let offset = + FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) + .unwrap(); DateTime::from_utc(now.naive_utc(), offset) } } @@ -115,7 +117,7 @@ impl TimeZone for Local { // this sounds very strange, but required for keeping `TimeZone::ymd` sane. // in the other words, we use the offset at the local midnight // but keep the actual date unaltered (much like `FixedOffset`). - let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0)); + let midnight = self.from_local_datetime(&local.and_hms_opt(0, 0, 0).unwrap()); midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) } @@ -127,7 +129,9 @@ impl TimeZone for Local { fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { let mut local = local.clone(); // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); + let offset = + FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) + .unwrap(); local -= crate::Duration::seconds(offset.local_minus_utc() as i64); LocalResult::Single(DateTime::from_utc(local, offset)) } @@ -142,7 +146,7 @@ impl TimeZone for Local { } fn from_utc_date(&self, utc: &NaiveDate) -> Date { - let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0)); + let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap()); Date::from_utc(*utc, *midnight.offset()) } @@ -153,7 +157,9 @@ impl TimeZone for Local { ))] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); + let offset = + FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) + .unwrap(); DateTime::from_utc(*utc, offset) } @@ -221,7 +227,7 @@ mod tests { #[test] fn test_local_date_sanity_check() { // issue #27 - assert_eq!(Local.ymd(2999, 12, 28).day(), 28); + assert_eq!(Local.ymd_opt(2999, 12, 28).unwrap().day(), 28); } #[test] @@ -229,13 +235,13 @@ mod tests { // issue #123 let today = Local::today(); - let dt = today.and_hms_milli(1, 2, 59, 1000); + let dt = today.and_hms_milli_opt(1, 2, 59, 1000).unwrap(); let timestr = dt.time().to_string(); // the OS API may or may not support the leap second, // but there are only two sensible options. assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr); - let dt = today.and_hms_milli(1, 2, 3, 1234); + let dt = today.and_hms_milli_opt(1, 2, 3, 1234).unwrap(); let timestr = dt.time().to_string(); assert!( timestr == "01:02:03.234" || timestr == "01:02:04.234", diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 21b362e5c4..9ececd3c22 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -77,7 +77,7 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { tm.tm_nsec as u32, ); - let offset = FixedOffset::east(tm.tm_utcoff); + let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); DateTime::from_utc(date.and_time(time) - offset, offset) } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 3ca2f4a6a5..398f07ec2f 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -129,14 +129,16 @@ impl Cache { } if !local { - let offset = FixedOffset::east( - self.zone - .find_local_time_type(d.timestamp()) - .expect("unable to select local time type") - .offset(), - ); - - return LocalResult::Single(DateTime::from_utc(d, offset)); + let offset = self + .zone + .find_local_time_type(d.timestamp()) + .expect("unable to select local time type") + .offset(); + + return match FixedOffset::east_opt(offset) { + Some(offset) => LocalResult::Single(DateTime::from_utc(d, offset)), + None => LocalResult::None, + }; } // we pass through the year as the year of a local point in time must either be valid in that locale, or @@ -148,8 +150,8 @@ impl Cache { { LocalResult::None => LocalResult::None, LocalResult::Ambiguous(early, late) => { - let early_offset = FixedOffset::east(early.offset()); - let late_offset = FixedOffset::east(late.offset()); + let early_offset = FixedOffset::east_opt(early.offset()).unwrap(); + let late_offset = FixedOffset::east_opt(late.offset()).unwrap(); LocalResult::Ambiguous( DateTime::from_utc(d - early_offset, early_offset), @@ -157,7 +159,7 @@ impl Cache { ) } LocalResult::Single(tt) => { - let offset = FixedOffset::east(tt.offset()); + let offset = FixedOffset::east_opt(tt.offset()).unwrap(); LocalResult::Single(DateTime::from_utc(d - offset, offset)) } } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index b4c0ee675f..e6415015c1 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -65,7 +65,8 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { tm.tm_sec = 59; } - let date = NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32); + let date = NaiveDate::from_ymd_opt(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) + .unwrap(); let time = NaiveTime::from_hms_nano( tm.tm_hour as u32, tm.tm_min as u32, @@ -73,7 +74,7 @@ fn tm_to_datetime(mut tm: Tm) -> DateTime { tm.tm_nsec as u32, ); - let offset = FixedOffset::east(tm.tm_utcoff); + let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); DateTime::from_utc(date.and_time(time) - offset, offset) } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 9bf7480d2a..40d5a76f9b 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -472,21 +472,21 @@ mod tests { #[test] fn test_negative_millis() { - let dt = Utc.timestamp_millis(-1000); + let dt = Utc.timestamp_millis_opt(-1000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); - let dt = Utc.timestamp_millis(-7000); + let dt = Utc.timestamp_millis_opt(-7000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC"); - let dt = Utc.timestamp_millis(-7001); + let dt = Utc.timestamp_millis_opt(-7001).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.999 UTC"); - let dt = Utc.timestamp_millis(-7003); + let dt = Utc.timestamp_millis_opt(-7003).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC"); - let dt = Utc.timestamp_millis(-999); + let dt = Utc.timestamp_millis_opt(-999).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC"); - let dt = Utc.timestamp_millis(-1); + let dt = Utc.timestamp_millis_opt(-1).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC"); - let dt = Utc.timestamp_millis(-60000); + let dt = Utc.timestamp_millis_opt(-60000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); - let dt = Utc.timestamp_millis(-3600000); + let dt = Utc.timestamp_millis_opt(-3600000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); for (millis, expected) in &[ diff --git a/src/offset/utc.rs b/src/offset/utc.rs index d38bfade51..76f24a4173 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -37,7 +37,7 @@ use crate::{Date, DateTime}; /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); /// /// assert_eq!(Utc.timestamp(61, 0), dt); -/// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt); +/// assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 1, 1).unwrap(), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] @@ -60,7 +60,9 @@ impl Utc { pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - let naive = NaiveDateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos() as u32); + let naive = + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos() as u32) + .unwrap(); DateTime::from_utc(naive, Utc) } @@ -100,7 +102,7 @@ impl TimeZone for Utc { impl Offset for Utc { fn fix(&self) -> FixedOffset { - FixedOffset::east(0) + FixedOffset::east_opt(0).unwrap() } } diff --git a/src/round.rs b/src/round.rs index fc7b8f99a6..a75cac0b2b 100644 --- a/src/round.rs +++ b/src/round.rs @@ -25,7 +25,7 @@ pub trait SubsecRound { /// # Example /// ``` rust /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -37,7 +37,7 @@ pub trait SubsecRound { /// # Example /// ``` rust /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -112,7 +112,7 @@ pub trait DurationRound: Sized { /// # Example /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); /// assert_eq!( /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -129,7 +129,7 @@ pub trait DurationRound: Sized { /// # Example /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); /// assert_eq!( /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -244,7 +244,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(1970, 12, 12).and_hms(0, 0, 0); + /// let dt = Utc.ymd_opt(1970, 12, 12).unwrap().and_hms_opt(0, 0, 0).unwrap(); /// /// assert_eq!( /// dt.duration_round(Duration::days(365)), @@ -257,7 +257,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2260, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000); + /// let dt = Utc.ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap(); /// /// assert_eq!( /// dt.duration_round(Duration::days(300 * 365)), @@ -270,7 +270,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2300, 12, 12).and_hms(0, 0, 0); + /// let dt = Utc.ymd_opt(2300, 12, 12).unwrap().and_hms_opt(0, 0, 0).unwrap(); /// /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); /// ``` @@ -310,8 +310,8 @@ mod tests { #[test] fn test_round_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); assert_eq!(dt.round_subsecs(10), dt); assert_eq!(dt.round_subsecs(9), dt); @@ -327,7 +327,8 @@ mod tests { assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 13); - let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = + Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); @@ -340,7 +341,8 @@ mod tests { #[test] fn test_round_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); @@ -353,8 +355,8 @@ mod tests { #[test] fn test_trunc_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 84_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); assert_eq!(dt.trunc_subsecs(10), dt); assert_eq!(dt.trunc_subsecs(9), dt); @@ -370,7 +372,8 @@ mod tests { assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).second(), 13); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = + pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); @@ -383,7 +386,8 @@ mod tests { #[test] fn test_trunc_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); @@ -396,7 +400,8 @@ mod tests { #[test] fn test_duration_round() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); assert_eq!( dt.duration_round(Duration::zero()).unwrap().to_string(), @@ -409,13 +414,13 @@ mod tests { ); // round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" @@ -439,7 +444,12 @@ mod tests { ); // timezone east - let dt = FixedOffset::east(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = FixedOffset::east_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); assert_eq!( dt.duration_round(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" @@ -450,7 +460,12 @@ mod tests { ); // timezone west - let dt = FixedOffset::west(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = FixedOffset::west_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); assert_eq!( dt.duration_round(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" @@ -463,7 +478,12 @@ mod tests { #[test] fn test_duration_round_naive() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000).naive_utc(); + let dt = Utc + .ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_round(Duration::zero()).unwrap().to_string(), @@ -476,13 +496,23 @@ mod tests { ); // round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0).naive_utc(); + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00" ); // round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999).naive_utc(); + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" @@ -508,7 +538,7 @@ mod tests { #[test] fn test_duration_round_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); assert_eq!( dt.duration_round(Duration::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" @@ -517,7 +547,8 @@ mod tests { #[test] fn test_duration_trunc() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); assert_eq!( dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), @@ -525,13 +556,13 @@ mod tests { ); // would round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" @@ -554,7 +585,12 @@ mod tests { ); // timezone east - let dt = FixedOffset::east(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = FixedOffset::east_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); assert_eq!( dt.duration_trunc(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" @@ -565,7 +601,12 @@ mod tests { ); // timezone west - let dt = FixedOffset::west(3600).ymd(2020, 10, 27).and_hms(15, 0, 0); + let dt = FixedOffset::west_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); assert_eq!( dt.duration_trunc(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" @@ -578,7 +619,12 @@ mod tests { #[test] fn test_duration_trunc_naive() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000).naive_utc(); + let dt = Utc + .ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), @@ -586,13 +632,23 @@ mod tests { ); // would round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0).naive_utc(); + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); // would round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999).naive_utc(); + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap() + .naive_utc(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" @@ -617,7 +673,7 @@ mod tests { #[test] fn test_duration_trunc_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" diff --git a/src/traits.rs b/src/traits.rs index 163a12852d..1b6af6926b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -95,10 +95,10 @@ pub trait Datelike: Sized { /// ``` /// use chrono::{NaiveDate, Datelike}; /// - /// assert_eq!(NaiveDate::from_ymd(1970, 1, 1).num_days_from_ce(), 719_163); - /// assert_eq!(NaiveDate::from_ymd(2, 1, 1).num_days_from_ce(), 366); - /// assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - /// assert_eq!(NaiveDate::from_ymd(0, 1, 1).num_days_from_ce(), -365); + /// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163); + /// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366); + /// assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + /// assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), -365); /// ``` fn num_days_from_ce(&self) -> i32 { // See test_num_days_from_ce_against_alternative_impl below for a more straightforward @@ -222,7 +222,7 @@ mod tests { use num_iter::range_inclusive; for year in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { - let jan1_year = NaiveDate::from_ymd(year, 1, 1); + let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); assert_eq!( jan1_year.num_days_from_ce(), num_days_from_ce(&jan1_year), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 1573d8c419..a37e854d5c 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -19,7 +19,7 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // seems to be consistent with the output of the `date` command, so we simply // compare both. // let local = Local - // .from_local_datetime(&NaiveDate::from_ymd(year, month, day).and_hms(hour, 5, 1)) + // .from_local_datetime(&NaiveDate::from_ymd_opt(year, month, day).unwrap().and_hms_opt(hour, 5, 1).unwrap()) // // looks like the "date" command always returns a given time when it is ambiguous // .earliest(); diff --git a/tests/wasm.rs b/tests/wasm.rs index bcec43fb9f..f003d4db9d 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -30,11 +30,11 @@ fn now() { // Ensure offset retrieved when getting local time is correct let expected_offset = match tz { - "ACST-9:30" => FixedOffset::east(19 * 30 * 60), - "Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully - "EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60), - "EST" | "-0500" => FixedOffset::east(-5 * 60 * 60), - "UTC0" | "+0000" => FixedOffset::east(0), + "ACST-9:30" => FixedOffset::east_opt(19 * 30 * 60).unwrap(), + "Asia/Katmandu" => FixedOffset::east_opt(23 * 15 * 60).unwrap(), // No DST thankfully + "EDT" | "EST4" | "-0400" => FixedOffset::east_opt(-4 * 60 * 60).unwrap(), + "EST" | "-0500" => FixedOffset::east_opt(-5 * 60 * 60).unwrap(), + "UTC0" | "+0000" => FixedOffset::east_opt(0).unwrap(), tz => panic!("unexpected TZ {}", tz), }; assert_eq!( @@ -52,7 +52,7 @@ fn from_is_exact() { let dt = DateTime::::from(now.clone()); - assert_eq!(now.get_time() as i64, dt.timestamp_millis()); + assert_eq!(now.get_time() as i64, dt.timestamp_millis_opt().unwrap()); } #[wasm_bindgen_test] From f60893a0dce544ac6013038b6def0bddd562b1ca Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Mon, 10 Oct 2022 22:08:42 +1100 Subject: [PATCH 146/999] update criterion version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e28c60a8d3..c823e23e23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } -criterion = { version = "0.3", optional = true } +criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} iana-time-zone = { version = "0.1.44", optional = true, features = ["fallback"] } From 4182d12c903ddc9eba7200057ef26d65235d0f9a Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 16 Oct 2022 22:17:25 +1100 Subject: [PATCH 147/999] add backticks in docs --- src/datetime/rustc_serialize.rs | 2 +- src/month.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index d64777de7c..18a67d6222 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -58,7 +58,7 @@ pub struct TsSeconds(DateTime); #[allow(deprecated)] impl From> for DateTime { - /// Pull the inner DateTime out + /// Pull the inner `DateTime` out #[allow(deprecated)] fn from(obj: TsSeconds) -> DateTime { obj.0 diff --git a/src/month.rs b/src/month.rs index 3f308e4c08..bba21be555 100644 --- a/src/month.rs +++ b/src/month.rs @@ -153,7 +153,7 @@ impl Month { } impl num_traits::FromPrimitive for Month { - /// Returns an Option from a i64, assuming a 1-index, January = 1. + /// Returns an `Option` from a i64, assuming a 1-index, January = 1. /// /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` /// ---------------------------| -------------------- | --------------------- | ... | ----- From bb874851a013962444e11de48bcdb8429ba90be7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 11 Oct 2022 10:48:17 +0200 Subject: [PATCH 148/999] Make Of methods fallible in the face of invalid ordinals --- src/naive/date.rs | 18 +++++----- src/naive/internals.rs | 78 +++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index c66e13ea51..e337d379a1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -301,7 +301,7 @@ impl NaiveDate { /// ``` pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { let flags = YearFlags::from_year(year); - NaiveDate::from_of(year, Of::new(ordinal, flags)) + NaiveDate::from_of(year, Of::new(ordinal, flags)?) } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) @@ -370,18 +370,18 @@ impl NaiveDate { let prevflags = YearFlags::from_year(year - 1); NaiveDate::from_of( year - 1, - Of::new(weekord + prevflags.ndays() - delta, prevflags), + Of::new(weekord + prevflags.ndays() - delta, prevflags)?, ) } else { let ordinal = weekord - delta; let ndays = flags.ndays(); if ordinal <= ndays { // this year - NaiveDate::from_of(year, Of::new(ordinal, flags)) + NaiveDate::from_of(year, Of::new(ordinal, flags)?) } else { // ordinal > ndays, next year let nextflags = YearFlags::from_year(year + 1); - NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)) + NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)?) } } } else { @@ -424,7 +424,7 @@ impl NaiveDate { let (year_div_400, cycle) = div_mod_floor(days, 146_097); let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) + NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week @@ -976,7 +976,7 @@ impl NaiveDate { let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) + NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) } /// Subtracts the `days` part of given `Duration` from the current date. @@ -1007,7 +1007,7 @@ impl NaiveDate { let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) + NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) } /// Subtracts another `NaiveDate` from the current date. @@ -1510,7 +1510,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal)) + self.with_of(self.of().with_ordinal(ordinal)?) } /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. @@ -1534,7 +1534,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal0 + 1)) + self.with_of(self.of().with_ordinal(ordinal0 + 1)?) } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index f5f0bc9f50..b6994347d2 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -270,20 +270,13 @@ pub(super) struct Of(pub(crate) u32); impl Of { #[inline] - fn clamp_ordinal(ordinal: u32) -> u32 { - if ordinal > 366 { - 0 - } else { - ordinal + pub(super) fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { + match ordinal <= 366 { + true => Some(Of((ordinal << 4) | u32::from(flags))), + false => None, } } - #[inline] - pub(super) fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); - Of((ordinal << 4) | u32::from(flags)) - } - #[inline] pub(super) fn from_mdf(Mdf(mdf): Mdf) -> Of { let mdl = mdf >> 3; @@ -307,10 +300,13 @@ impl Of { } #[inline] - pub(super) fn with_ordinal(&self, ordinal: u32) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); + pub(super) fn with_ordinal(&self, ordinal: u32) -> Option { + if ordinal > 366 { + return None; + } + let Of(of) = *self; - Of((of & 0b1111) | (ordinal << 4)) + Some(Of((of & 0b1111) | (ordinal << 4))) } #[inline] @@ -523,7 +519,12 @@ mod tests { fn test_of() { fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { for ordinal in range_inclusive(ordinal1, ordinal2) { - let of = Of::new(ordinal, flags); + let of = match Of::new(ordinal, flags) { + Some(of) => of, + None if !expected => continue, + None => panic!("Of::new({}, {:?}) returned None", ordinal, flags), + }; + assert!( of.valid() == expected, "ordinal {} = {:?} should be {} for dominical year {:?}", @@ -640,7 +641,7 @@ mod tests { fn test_of_fields() { for &flags in FLAGS.iter() { for ordinal in range_inclusive(1u32, 366) { - let of = Of::new(ordinal, flags); + let of = Of::new(ordinal, flags).unwrap(); if of.valid() { assert_eq!(of.ordinal(), ordinal); } @@ -651,11 +652,16 @@ mod tests { #[test] fn test_of_with_fields() { fn check(flags: YearFlags, ordinal: u32) { - let of = Of::new(ordinal, flags); + let of = Of::new(ordinal, flags).unwrap(); for ordinal in range_inclusive(0u32, 1024) { - let of = of.with_ordinal(ordinal); - assert_eq!(of.valid(), Of::new(ordinal, flags).valid()); + let of = match of.with_ordinal(ordinal) { + Some(of) => of, + None if ordinal > 366 => continue, + None => panic!("failed to create Of with ordinal {}", ordinal), + }; + + assert_eq!(of.valid(), Of::new(ordinal, flags).unwrap().valid()); if of.valid() { assert_eq!(of.ordinal(), ordinal); } @@ -674,25 +680,25 @@ mod tests { #[test] fn test_of_weekday() { - assert_eq!(Of::new(1, A).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, B).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, C).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, D).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, E).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, F).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, G).weekday(), Weekday::Mon); - assert_eq!(Of::new(1, AG).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, BA).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, CB).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, DC).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, ED).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, FE).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, GF).weekday(), Weekday::Mon); + assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); + assert_eq!(Of::new(1, B).unwrap().weekday(), Weekday::Sat); + assert_eq!(Of::new(1, C).unwrap().weekday(), Weekday::Fri); + assert_eq!(Of::new(1, D).unwrap().weekday(), Weekday::Thu); + assert_eq!(Of::new(1, E).unwrap().weekday(), Weekday::Wed); + assert_eq!(Of::new(1, F).unwrap().weekday(), Weekday::Tue); + assert_eq!(Of::new(1, G).unwrap().weekday(), Weekday::Mon); + assert_eq!(Of::new(1, AG).unwrap().weekday(), Weekday::Sun); + assert_eq!(Of::new(1, BA).unwrap().weekday(), Weekday::Sat); + assert_eq!(Of::new(1, CB).unwrap().weekday(), Weekday::Fri); + assert_eq!(Of::new(1, DC).unwrap().weekday(), Weekday::Thu); + assert_eq!(Of::new(1, ED).unwrap().weekday(), Weekday::Wed); + assert_eq!(Of::new(1, FE).unwrap().weekday(), Weekday::Tue); + assert_eq!(Of::new(1, GF).unwrap().weekday(), Weekday::Mon); for &flags in FLAGS.iter() { - let mut prev = Of::new(1, flags).weekday(); + let mut prev = Of::new(1, flags).unwrap().weekday(); for ordinal in range_inclusive(2u32, flags.ndays()) { - let of = Of::new(ordinal, flags); + let of = Of::new(ordinal, flags).unwrap(); let expected = prev.succ(); assert_eq!(of.weekday(), expected); prev = expected; @@ -761,7 +767,7 @@ mod tests { fn test_of_isoweekdate_raw() { for &flags in FLAGS.iter() { // January 4 should be in the first week - let (week, _) = Of::new(4 /* January 4 */, flags).isoweekdate_raw(); + let (week, _) = Of::new(4 /* January 4 */, flags).unwrap().isoweekdate_raw(); assert_eq!(week, 1); } } From eb1fae64fcf734c02bd4c158f46d5a64cb3f77f0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 11 Oct 2022 10:56:27 +0200 Subject: [PATCH 149/999] Make Mdf methods fallible in the face of invalid input --- src/naive/date.rs | 12 +++---- src/naive/internals.rs | 74 +++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index e337d379a1..ba89d608a8 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -267,7 +267,7 @@ impl NaiveDate { /// ``` pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { let flags = YearFlags::from_year(year); - NaiveDate::from_mdf(year, Mdf::new(month, day, flags)) + NaiveDate::from_mdf(year, Mdf::new(month, day, flags)?) } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) @@ -615,7 +615,7 @@ impl NaiveDate { let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; let day = Ord::min(self.day(), days[(month - 1) as usize]); - NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags)) + NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags)?) } /// Add a duration in [`Days`] to the date @@ -1429,7 +1429,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_month(&self, month: u32) -> Option { - self.with_mdf(self.mdf().with_month(month)) + self.with_mdf(self.mdf().with_month(month)?) } /// Makes a new `NaiveDate` with the month number (starting from 0) changed. @@ -1448,7 +1448,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { - self.with_mdf(self.mdf().with_month(month0 + 1)) + self.with_mdf(self.mdf().with_month(month0 + 1)?) } /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. @@ -1467,7 +1467,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_day(&self, day: u32) -> Option { - self.with_mdf(self.mdf().with_day(day)) + self.with_mdf(self.mdf().with_day(day)?) } /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. @@ -1486,7 +1486,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_day0(&self, day0: u32) -> Option { - self.with_mdf(self.mdf().with_day(day0 + 1)) + self.with_mdf(self.mdf().with_day(day0 + 1)?) } /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. diff --git a/src/naive/internals.rs b/src/naive/internals.rs index b6994347d2..1b113d51d3 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -371,30 +371,13 @@ pub(super) struct Mdf(pub(super) u32); impl Mdf { #[inline] - fn clamp_month(month: u32) -> u32 { - if month > 12 { - 0 - } else { - month - } - } - - #[inline] - fn clamp_day(day: u32) -> u32 { - if day > 31 { - 0 - } else { - day + pub(super) fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { + match month <= 12 && day <= 31 { + true => Some(Mdf((month << 9) | (day << 4) | u32::from(flags))), + false => None, } } - #[inline] - pub(super) fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf { - let month = Mdf::clamp_month(month); - let day = Mdf::clamp_day(day); - Mdf((month << 9) | (day << 4) | u32::from(flags)) - } - #[inline] pub(super) fn from_of(Of(of): Of) -> Mdf { let ol = of >> 3; @@ -421,10 +404,13 @@ impl Mdf { } #[inline] - pub(super) fn with_month(&self, month: u32) -> Mdf { - let month = Mdf::clamp_month(month); + pub(super) fn with_month(&self, month: u32) -> Option { + if month > 12 { + return None; + } + let Mdf(mdf) = *self; - Mdf((mdf & 0b1_1111_1111) | (month << 9)) + Some(Mdf((mdf & 0b1_1111_1111) | (month << 9))) } #[inline] @@ -434,10 +420,13 @@ impl Mdf { } #[inline] - pub(super) fn with_day(&self, day: u32) -> Mdf { - let day = Mdf::clamp_day(day); + pub(super) fn with_day(&self, day: u32) -> Option { + if day > 31 { + return None; + } + let Mdf(mdf) = *self; - Mdf((mdf & !0b1_1111_0000) | (day << 4)) + Some(Mdf((mdf & !0b1_1111_0000) | (day << 4))) } #[inline] @@ -556,7 +545,12 @@ mod tests { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { for month in range_inclusive(month1, month2) { for day in range_inclusive(day1, day2) { - let mdf = Mdf::new(month, day, flags); + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None if !expected => continue, + None => panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags), + }; + assert!( mdf.valid() == expected, "month {} day {} = {:?} should be {} for dominical year {:?}", @@ -711,7 +705,11 @@ mod tests { for &flags in FLAGS.iter() { for month in range_inclusive(1u32, 12) { for day in range_inclusive(1u32, 31) { - let mdf = Mdf::new(month, day, flags); + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None => continue, + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -724,11 +722,15 @@ mod tests { #[test] fn test_mdf_with_fields() { fn check(flags: YearFlags, month: u32, day: u32) { - let mdf = Mdf::new(month, day, flags); + let mdf = Mdf::new(month, day, flags).unwrap(); for month in range_inclusive(0u32, 16) { - let mdf = mdf.with_month(month); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + let mdf = match mdf.with_month(month) { + Some(mdf) => mdf, + None if month > 12 => continue, + None => panic!("failed to create Mdf with month {}", month), + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -736,8 +738,12 @@ mod tests { } for day in range_inclusive(0u32, 1024) { - let mdf = mdf.with_day(day); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + let mdf = match mdf.with_day(day) { + Some(mdf) => mdf, + None if day > 31 => continue, + None => panic!("failed to create Mdf with month {}", month), + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); From 2eda0f1a616e0e1d2413012fc0d48aef31f90347 Mon Sep 17 00:00:00 2001 From: Alex Sayers Date: Tue, 4 May 2021 20:31:54 +0900 Subject: [PATCH 150/999] Optionally implement Arbitrary for Naive{Date,Time,DateTime} --- Cargo.toml | 1 + src/naive/date.rs | 9 +++++++++ src/naive/datetime/mod.rs | 1 + src/naive/time/mod.rs | 9 +++++++++ 4 files changed, 20 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c823e23e23..850bc2dc1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} iana-time-zone = { version = "0.1.44", optional = true, features = ["fallback"] } +arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } diff --git a/src/naive/date.rs b/src/naive/date.rs index ba89d608a8..603d9e5d46 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -193,6 +193,15 @@ pub const MIN_DATE: NaiveDate = NaiveDate::MIN; #[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] pub const MAX_DATE: NaiveDate = NaiveDate::MAX; +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for NaiveDate { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; + let ord = u.int_in_range(1..=366)?; + NaiveDate::from_yo_opt(year, ord).ok_or(arbitrary::Error::IncorrectFormat) + } +} + // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, // we use a separate run-time test. #[test] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index b67c87338d..f2cee59fd1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -79,6 +79,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, time: NaiveTime, diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 44184f86b2..8fa1698e20 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -194,6 +194,15 @@ pub struct NaiveTime { frac: u32, } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for NaiveTime { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let secs = u.int_in_range(0..=86_400)?; + let frac = u.int_in_range(0..=2_000_000_000)?; + Ok(NaiveTime { secs, frac }) + } +} + impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// From c879b18dbfef04e3f046aa6fd4aeeae0ce6909f6 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 12:36:23 +0200 Subject: [PATCH 151/999] Tweak implementation of Arbitrary for NaiveDate and NaiveTime to make it infallible --- src/naive/date.rs | 3 ++- src/naive/time/mod.rs | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 603d9e5d46..c5fb7b1725 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -197,7 +197,8 @@ pub const MAX_DATE: NaiveDate = NaiveDate::MAX; impl arbitrary::Arbitrary<'_> for NaiveDate { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; - let ord = u.int_in_range(1..=366)?; + let max_days = YearFlags::from_year(year).ndays(); + let ord = u.int_in_range(1..=max_days)?; NaiveDate::from_yo_opt(year, ord).ok_or(arbitrary::Error::IncorrectFormat) } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8fa1698e20..c79fe6b3c6 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -197,9 +197,11 @@ pub struct NaiveTime { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NaiveTime { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { - let secs = u.int_in_range(0..=86_400)?; - let frac = u.int_in_range(0..=2_000_000_000)?; - Ok(NaiveTime { secs, frac }) + let secs = u.int_in_range(0..=86_399)?; + let nano = u.int_in_range(0..=1_999_999_999)?; + let time = NaiveTime::from_num_seconds_from_midnight_opt(secs, nano) + .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); + Ok(time) } } From de57f5617a1cfb61105e65437697bebe2916cd70 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 17:11:05 +0200 Subject: [PATCH 152/999] Implement Arbitrary for timezones --- src/offset/fixed.rs | 10 ++++++++++ src/offset/local/mod.rs | 1 + src/offset/utc.rs | 1 + 3 files changed, 12 insertions(+) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 0c6ccae7b8..18896173e6 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -152,6 +152,16 @@ impl fmt::Display for FixedOffset { } } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for FixedOffset { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let secs = u.int_in_range(-86_399..=86_399)?; + let fixed_offset = FixedOffset::east_opt(secs) + .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); + Ok(fixed_offset) + } +} + // addition or subtraction of FixedOffset to/from Timelike values is the same as // adding or subtracting the offset's local_minus_utc value // but keep keeps the leap second information. diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 29a3cdd429..6abfebadc6 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -52,6 +52,7 @@ mod tz_info; /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; impl Local { diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 76f24a4173..8cf03daa5c 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -41,6 +41,7 @@ use crate::{Date, DateTime}; /// ``` #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; #[cfg(feature = "clock")] From 7af32de08a2df26a7c07a2cbd8b15850de7aaf59 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 17:15:04 +0200 Subject: [PATCH 153/999] Implement Arbitrary for Date --- src/date.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/date.rs b/src/date.rs index d8ac15cd77..24e4aa9190 100644 --- a/src/date.rs +++ b/src/date.rs @@ -545,6 +545,21 @@ where } } +// Note that implementation of Arbitrary cannot be automatically derived for Date, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(feature = "arbitrary")] +impl<'a, Tz> arbitrary::Arbitrary<'a> for Date +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let date = NaiveDate::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(Date::from_utc(date, offset)) + } +} + #[cfg(test)] mod tests { use super::Date; From 44cee53a18ea2bdfb6270b64cd599a69d9f7f1d9 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 17:17:45 +0200 Subject: [PATCH 154/999] Implement Arbitrary for DateTime --- src/datetime/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 28b3bd64d2..c5a4c721bb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1150,6 +1150,21 @@ impl From> for js_sys::Date { } } +// Note that implementation of Arbitrary cannot be simply derived for DateTime, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(feature = "arbitrary")] +impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let datetime = NaiveDateTime::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(DateTime::from_utc(datetime, offset)) + } +} + #[test] fn test_add_sub_months() { let utc_dt = Utc.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); From 87b04c5b91acebcc56ac2bd5b4282357f7fb89ba Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 17:50:04 +0200 Subject: [PATCH 155/999] Implement Arbitrary for Duration --- src/oldtime.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/oldtime.rs b/src/oldtime.rs index 970dc90d16..f935e9a4b1 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -482,6 +482,24 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { (this / other, this % other) } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for Duration { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + const MIN_SECS: i64 = i64::MIN / MILLIS_PER_SEC - 1; + const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; + + let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; + let nanos: i32 = u.int_in_range(0..=(NANOS_PER_SEC - 1))?; + let duration = Duration { secs, nanos }; + + if duration < MIN || duration > MAX { + Err(arbitrary::Error::IncorrectFormat) + } else { + Ok(duration) + } + } +} + #[cfg(test)] mod tests { use super::{Duration, OutOfRangeError, MAX, MIN}; From 8b654b5f286673ed35cbf56346855be16e96b2c8 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 18:00:45 +0200 Subject: [PATCH 156/999] Implement Arbitrary for Month and Months --- src/month.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/month.rs b/src/month.rs index bba21be555..baa3a66022 100644 --- a/src/month.rs +++ b/src/month.rs @@ -30,6 +30,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { /// January January = 0, @@ -191,6 +192,7 @@ impl num_traits::FromPrimitive for Month { /// A duration in calendar months #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Months(pub(crate) u32); impl Months { From b9241c0666074315222de277e68a7cd6b9de2022 Mon Sep 17 00:00:00 2001 From: Sergey Potapov Date: Mon, 17 Oct 2022 18:02:49 +0200 Subject: [PATCH 157/999] Implement Arbitrary for Weekday --- src/weekday.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/weekday.rs b/src/weekday.rs index bebdd116a9..bd30e1934d 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -11,6 +11,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. Mon = 0, From 64e082a5f89a3eda781efc74670d88de30932d37 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Oct 2022 10:33:35 +0100 Subject: [PATCH 158/999] use less intermediate formatting (-5% improvement) --- src/date.rs | 6 ++++-- src/datetime/mod.rs | 8 ++++++-- src/format/mod.rs | 2 +- src/naive/datetime/mod.rs | 9 +++++++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/date.rs b/src/date.rs index 24e4aa9190..1e0bf0e743 100644 --- a/src/date.rs +++ b/src/date.rs @@ -532,7 +532,8 @@ impl Sub> for Date { impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) } } @@ -541,7 +542,8 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index c5a4c721bb..0a9c610beb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -11,6 +11,7 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; +use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] @@ -990,7 +991,8 @@ impl Sub for DateTime { impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) } } @@ -999,7 +1001,9 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + f.write_char(' ')?; + self.offset.fmt(f) } } diff --git a/src/format/mod.rs b/src/format/mod.rs index da347531d3..ea015e7472 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -737,7 +737,7 @@ fn format_inner<'a>( if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. - write!(result, "{:?}T{:?}", d, t)?; + write!(result, "{:?}", crate::NaiveDateTime::new(*d, *t))?; Some(write_local_minus_utc(result, off, false, Colons::Single)) } else { None diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index f2cee59fd1..5cf29739fa 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -6,6 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::convert::TryFrom; +use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -1629,7 +1630,9 @@ impl Sub for NaiveDateTime { /// ``` impl fmt::Debug for NaiveDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}T{:?}", self.date, self.time) + self.date.fmt(f)?; + f.write_char('T')?; + self.time.fmt(f) } } @@ -1660,7 +1663,9 @@ impl fmt::Debug for NaiveDateTime { /// ``` impl fmt::Display for NaiveDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.date, self.time) + self.date.fmt(f)?; + f.write_char(' ')?; + self.time.fmt(f) } } From 66491783a2e0141129777a6450f43267ee20a925 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Oct 2022 10:43:37 +0100 Subject: [PATCH 159/999] skip DelayedFormat for rfc3339 (net -58% improvement) --- src/datetime/mod.rs | 6 ++- src/format/mod.rs | 93 ++++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 0a9c610beb..9f10eeefd2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -608,8 +608,10 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn to_rfc3339(&self) -> String { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - self.format_with_items(ITEMS.iter()).to_string() + let mut result = String::with_capacity(32); + crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc3339 datetime to string should never fail"); + result } /// Return an RFC 3339 and ISO 8601 date and time string with subseconds diff --git a/src/format/mod.rs b/src/format/mod.rs index ea015e7472..6705e3ed6a 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -40,6 +40,8 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::fmt; +#[cfg(any(feature = "alloc", feature = "std", test))] +use core::fmt::Write; use core::str::FromStr; #[cfg(any(feature = "std", test))] use std::error::Error; @@ -501,7 +503,6 @@ fn format_inner<'a>( ) }; - use core::fmt::Write; use num_integer::{div_floor, mod_floor}; match *item { @@ -577,45 +578,6 @@ fn format_inner<'a>( Item::Fixed(ref spec) => { use self::Fixed::*; - /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. - /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. - fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - colon_type: Colons, - ) -> fmt::Result { - let off = off.local_minus_utc(); - if !allow_zulu || off != 0 { - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - - match colon_type { - Colons::None => { - write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) - } - Colons::Single => { - write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) - } - Colons::Double => { - write!( - result, - "{}{:02}:{:02}:{:02}", - sign, - off / 3600, - off / 60 % 60, - off % 60 - ) - } - Colons::Triple => { - write!(result, "{}{:02}", sign, off / 3600) - } - } - } else { - result.push('Z'); - Ok(()) - } - } - let ret = match *spec { ShortMonthName => date.map(|d| { @@ -735,10 +697,7 @@ fn format_inner<'a>( // same as `%Y-%m-%dT%H:%M:%S%.f%:z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(result, "{:?}", crate::NaiveDateTime::new(*d, *t))?; - Some(write_local_minus_utc(result, off, false, Colons::Single)) + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) } else { None } @@ -756,6 +715,52 @@ fn format_inner<'a>( Ok(()) } +/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. +/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. +#[cfg(any(feature = "alloc", feature = "std", test))] +fn write_local_minus_utc( + result: &mut String, + off: FixedOffset, + allow_zulu: bool, + colon_type: Colons, +) -> fmt::Result { + let off = off.local_minus_utc(); + if !allow_zulu || off != 0 { + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + + match colon_type { + Colons::None => { + write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) + } + Colons::Single => { + write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) + } + Colons::Double => { + write!(result, "{}{:02}:{:02}:{:02}", sign, off / 3600, off / 60 % 60, off % 60) + } + Colons::Triple => { + write!(result, "{}{:02}", sign, off / 3600) + } + } + } else { + result.push('Z'); + Ok(()) + } +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) fn write_rfc3339( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + // reuse `Debug` impls which already print ISO 8601 format. + // this is faster in this way. + write!(result, "{:?}", dt)?; + write_local_minus_utc(result, off, false, Colons::Single) +} + /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std", test))] From 8706fe0862537f159508a7a1362fc4e9363e81c9 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Oct 2022 11:03:13 +0100 Subject: [PATCH 160/999] extract out locales for a later change --- src/format/mod.rs | 130 +++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 6705e3ed6a..d4560b9396 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -441,6 +441,63 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); +#[cfg(any(feature = "alloc", feature = "std", test))] +struct Locales { + short_months: &'static [&'static str], + long_months: &'static [&'static str], + short_weekdays: &'static [&'static str], + long_weekdays: &'static [&'static str], + am_pm: &'static [&'static str], +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl Locales { + fn new(_locale: Option) -> Self { + #[cfg(feature = "unstable-locales")] + { + let locale = _locale.unwrap_or(Locale::POSIX); + Self { + short_months: locales::short_months(locale), + long_months: locales::long_months(locale), + short_weekdays: locales::short_weekdays(locale), + long_weekdays: locales::long_weekdays(locale), + am_pm: locales::am_pm(locale), + } + } + #[cfg(not(feature = "unstable-locales"))] + Self { + short_months: &[ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ], + long_months: &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + long_weekdays: &[ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + am_pm: &["AM", "PM"], + } + } +} + /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] @@ -463,45 +520,9 @@ fn format_inner<'a>( time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, item: &Item<'a>, - _locale: Option, + locale: Option, ) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - let locale = _locale.unwrap_or(Locale::POSIX); - let am_pm = locales::am_pm(locale); - ( - locales::short_months(locale), - locales::long_months(locale), - locales::short_weekdays(locale), - locales::long_weekdays(locale), - am_pm, - &[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()], - ) - }; - #[cfg(not(feature = "unstable-locales"))] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - ( - &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - &["AM", "PM"], - &["am", "pm"], - ) - }; + let locale = Locales::new(locale); use num_integer::{div_floor, mod_floor}; @@ -581,35 +602,38 @@ fn format_inner<'a>( let ret = match *spec { ShortMonthName => date.map(|d| { - result.push_str(short_months[d.month0() as usize]); + result.push_str(locale.short_months[d.month0() as usize]); Ok(()) }), LongMonthName => date.map(|d| { - result.push_str(long_months[d.month0() as usize]); + result.push_str(locale.long_months[d.month0() as usize]); Ok(()) }), ShortWeekdayName => date.map(|d| { - result - .push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str( + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + ); Ok(()) }), LongWeekdayName => date.map(|d| { - result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str( + locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + ); Ok(()) }), LowerAmPm => time.map(|t| { - #[cfg_attr(feature = "cargo-clippy", allow(clippy::useless_asref))] - { - result.push_str(if t.hour12().0 { - am_pm_lowercase[1].as_ref() - } else { - am_pm_lowercase[0].as_ref() - }); + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) } Ok(()) }), UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] }); + result.push_str(if t.hour12().0 { + locale.am_pm[1] + } else { + locale.am_pm[0] + }); Ok(()) }), Nanosecond => time.map(|t| { @@ -680,9 +704,9 @@ fn format_inner<'a>( write!( result, "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", - short_weekdays[d.weekday().num_days_from_sunday() as usize], + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], d.day(), - short_months[d.month0() as usize], + locale.short_months[d.month0() as usize], d.year(), t.hour(), t.minute(), From 8a90e128f8404a56459d011a2093499871912f86 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Oct 2022 11:09:01 +0100 Subject: [PATCH 161/999] skip DelayedFormat for rfc2822 (net -55% improvement for 2822) --- src/datetime/mod.rs | 6 +++-- src/format/mod.rs | 54 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 9f10eeefd2..f46cd4fa20 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -600,8 +600,10 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn to_rfc2822(&self) -> String { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - self.format_with_items(ITEMS.iter()).to_string() + let mut result = String::with_capacity(32); + crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc2822 datetime to string should never fail"); + result } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. diff --git a/src/format/mod.rs b/src/format/mod.rs index d4560b9396..ab020c5cf8 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -700,19 +700,7 @@ fn format_inner<'a>( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - let sec = t.second() + t.nanosecond() / 1_000_000_000; - write!( - result, - "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - d.day(), - locale.short_months[d.month0() as usize], - d.year(), - t.hour(), - t.minute(), - sec - )?; - Some(write_local_minus_utc(result, off, false, Colons::None)) + Some(write_rfc2822_inner(result, d, t, off, locale)) } else { None } @@ -785,6 +773,46 @@ pub(crate) fn write_rfc3339( write_local_minus_utc(result, off, false, Colons::Single) } +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +fn write_rfc2822_inner( + result: &mut String, + d: &NaiveDate, + t: &NaiveTime, + off: FixedOffset, + locale: Locales, +) -> fmt::Result { + let year = d.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + let sec = t.second() + t.nanosecond() / 1_000_000_000; + write!( + result, + "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + d.day(), + locale.short_months[d.month0() as usize], + year, + t.hour(), + t.minute(), + sec + )?; + write_local_minus_utc(result, off, false, Colons::None) +} + /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std", test))] From 86325bc9aa09d243d5d0a3664eb1636a9cb62667 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Oct 2022 11:38:28 +0100 Subject: [PATCH 162/999] avoid int formatting as much as possible (net -70%/-68% on 2822/3339 respectively) --- src/format/mod.rs | 39 +++++++++++++++++++++++++++------------ src/naive/date.rs | 14 +++++++++++--- src/naive/time/mod.rs | 10 ++++++++-- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index ab020c5cf8..ed63e4eb7a 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -40,7 +40,6 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::fmt; -#[cfg(any(feature = "alloc", feature = "std", test))] use core::fmt::Write; use core::str::FromStr; #[cfg(any(feature = "std", test))] @@ -798,21 +797,37 @@ fn write_rfc2822_inner( return Err(fmt::Error); } + result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str(", "); + write_hundreds(result, d.day() as u8)?; + result.push(' '); + result.push_str(locale.short_months[d.month0() as usize]); + result.push(' '); + write_hundreds(result, (year / 100) as u8)?; + write_hundreds(result, (year % 100) as u8)?; + result.push(' '); + write_hundreds(result, t.hour() as u8)?; + result.push(':'); + write_hundreds(result, t.minute() as u8)?; + result.push(':'); let sec = t.second() + t.nanosecond() / 1_000_000_000; - write!( - result, - "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - d.day(), - locale.short_months[d.month0() as usize], - year, - t.hour(), - t.minute(), - sec - )?; + write_hundreds(result, sec as u8)?; + result.push(' '); write_local_minus_utc(result, off, false, Colons::None) } +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} + /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std", test))] diff --git a/src/naive/date.rs b/src/naive/date.rs index c5fb7b1725..e362cd96ef 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; @@ -1830,14 +1830,22 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { /// ``` impl fmt::Debug for NaiveDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use core::fmt::Write; + let year = self.year(); let mdf = self.mdf(); if (0..=9999).contains(&year) { - write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day()) + write_hundreds(f, (year / 100) as u8)?; + write_hundreds(f, (year % 100) as u8)?; } else { // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day()) + write!(f, "{:+05}", year)?; } + + f.write_char('-')?; + write_hundreds(f, mdf.month() as u8)?; + f.write_char('-')?; + write_hundreds(f, mdf.day() as u8) } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index c79fe6b3c6..d0354a3f0a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -14,7 +14,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; @@ -1188,7 +1188,13 @@ impl fmt::Debug for NaiveTime { (sec, self.frac) }; - write!(f, "{:02}:{:02}:{:02}", hour, min, sec)?; + use core::fmt::Write; + write_hundreds(f, hour as u8)?; + f.write_char(':')?; + write_hundreds(f, min as u8)?; + f.write_char(':')?; + write_hundreds(f, sec as u8)?; + if nano == 0 { Ok(()) } else if nano % 1_000_000 == 0 { From 3e2f151613899586672cc6d4f2246565e44a7bf4 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Tue, 18 Oct 2022 07:53:49 +0100 Subject: [PATCH 163/999] remove dyn formatting from timezone (-74/78% on 2822/3339 respectively) --- src/format/mod.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index ed63e4eb7a..39b3db7d82 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -736,26 +736,28 @@ fn write_local_minus_utc( colon_type: Colons, ) -> fmt::Result { let off = off.local_minus_utc(); - if !allow_zulu || off != 0 { - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + if allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + result.push(sign); - match colon_type { - Colons::None => { - write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) - } - Colons::Single => { - write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) - } - Colons::Double => { - write!(result, "{}{:02}:{:02}:{:02}", sign, off / 3600, off / 60 % 60, off % 60) - } - Colons::Triple => { - write!(result, "{}{:02}", sign, off / 3600) - } + write_hundreds(result, (off / 3600) as u8)?; + + match colon_type { + Colons::None => write_hundreds(result, (off / 60 % 60) as u8), + Colons::Single => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8) } - } else { - result.push('Z'); - Ok(()) + Colons::Double => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8)?; + result.push(':'); + write_hundreds(result, (off % 60) as u8) + } + Colons::Triple => Ok(()), } } From 280208499ea52119f7298f832e2ca41b473bf308 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Mon, 7 Nov 2022 23:09:47 -0800 Subject: [PATCH 164/999] Fix GitHub Actions badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7296a76fc3..ad95918899 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Chrono on docs.rs][docsrs-image]][docsrs] [![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] -[gh-image]: https://github.com/chronotope/chrono/workflows/test/badge.svg +[gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg [gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest [cratesio-image]: https://img.shields.io/crates/v/chrono.svg [cratesio]: https://crates.io/crates/chrono From 645fca0c55a461a6cd220062feb033e5712cda9c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 4 Nov 2022 23:17:41 +0100 Subject: [PATCH 165/999] chore: apply clippy suggestions for 1.65 --- src/format/parsed.rs | 2 +- src/naive/time/mod.rs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 06b1b2efb3..1a09721d9a 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -225,7 +225,7 @@ impl Parsed { /// (`false` for AM, `true` for PM) #[inline] pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { - set_if_consistent(&mut self.hour_div_12, if value { 1 } else { 0 }) + set_if_consistent(&mut self.hour_div_12, u32::from(value)) } /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index d0354a3f0a..c90cba8619 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -652,13 +652,7 @@ impl NaiveTime { // `secs` may contain a leap second yet to be counted let adjust = match self.secs.cmp(&rhs.secs) { - Ordering::Greater => { - if rhs.frac >= 1_000_000_000 { - 1 - } else { - 0 - } - } + Ordering::Greater => i64::from(rhs.frac >= 1_000_000_000), Ordering::Equal => 0, Ordering::Less => { if self.frac >= 1_000_000_000 { From 03165c8658528a0ec267bf724d092e677d3c6e34 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 20 Oct 2022 12:29:22 +0200 Subject: [PATCH 166/999] Move Date::years_since() implementation into NaiveDate --- src/date.rs | 10 +--------- src/naive/date.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/date.rs b/src/date.rs index 1e0bf0e743..697ac9064a 100644 --- a/src/date.rs +++ b/src/date.rs @@ -288,15 +288,7 @@ impl Date { /// Returns the number of whole years from the given `base` until `self`. pub fn years_since(&self, base: Self) -> Option { - let mut years = self.year() - base.year(); - if (self.month(), self.day()) < (base.month(), base.day()) { - years -= 1; - } - - match years >= 0 { - true => Some(years as u32), - false => None, - } + self.date.years_since(base.date) } /// The minimum possible `Date`. diff --git a/src/naive/date.rs b/src/naive/date.rs index e362cd96ef..9db2ccf9a9 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1054,6 +1054,19 @@ impl NaiveDate { ) } + /// Returns the number of whole years from the given `base` until `self`. + pub fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + if (self.month(), self.day()) < (base.month(), base.day()) { + years -= 1; + } + + match years >= 0 { + true => Some(years as u32), + false => None, + } + } + /// Formats the date with the specified formatting items. /// Otherwise it is the same as the ordinary `format` method. /// From 7ba090d484cb93f73e5a7803dd62e4c4295f3b50 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 20 Oct 2022 12:31:01 +0200 Subject: [PATCH 167/999] Add TimeZone::with_ymd_and_hms() helper method We don't add `with_yo_and_hms()` and `with_isoywd_and_hms()` here, instead recommending users can use `from_local_datetime()` with a separately constructed `NaiveDateTime`. --- src/offset/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 40d5a76f9b..78256a663c 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -206,6 +206,27 @@ pub trait TimeZone: Sized + Clone { /// The original `TimeZone` value can be recovered via `TimeZone::from_offset`. type Offset: Offset; + /// Make a new `DateTime` from year, month, day, time components and current time zone. + /// + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// Returns `LocalResult::None` on invalid input data. + fn with_ymd_and_hms( + &self, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + ) -> LocalResult> { + match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) + { + Some(dt) => self.from_local_datetime(&dt), + None => LocalResult::None, + } + } + /// Makes a new `Date` from year, month, day and the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// From 77317d5255679292623b81381c8036451e9703a3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 20 Oct 2022 12:40:08 +0200 Subject: [PATCH 168/999] Deprecate usage of the Date type --- src/date.rs | 10 +------- src/datetime/mod.rs | 13 ++-------- src/offset/local/mod.rs | 3 +++ src/offset/mod.rs | 56 ++++++++++++++++++++--------------------- src/offset/utc.rs | 5 ++++ 5 files changed, 39 insertions(+), 48 deletions(-) diff --git a/src/date.rs b/src/date.rs index 697ac9064a..209d26c753 100644 --- a/src/date.rs +++ b/src/date.rs @@ -53,6 +53,7 @@ use crate::{Datelike, Weekday}; /// - The date is timezone-agnostic up to one day (i.e. practically always), /// so the local date and UTC date should be equal for most cases /// even though the raw calculation between `NaiveDate` and `Duration` may not. +#[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime` instead")] #[derive(Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Date { @@ -324,15 +325,6 @@ where /// Formats the date with the specified format string. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - /// - /// # Example - /// ```rust - /// use chrono::prelude::*; - /// - /// let date_time: Date = Utc.ymd_opt(2017, 04, 02).unwrap(); - /// let formatted = format!("{}", date_time.format("%d/%m/%Y")); - /// assert_eq!(formatted, "02/04/2017"); - /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index f46cd4fa20..170b358593 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -152,18 +152,9 @@ impl DateTime { /// 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. - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let date: Date = Utc.ymd_opt(2020, 1, 1).unwrap(); - /// let dt: DateTime = date.and_hms_opt(0, 0, 0).unwrap(); - /// - /// assert_eq!(dt.date(), date); - /// - /// assert_eq!(dt.date().and_hms_opt(1, 1, 1).unwrap(), date.and_hms_opt(1, 1, 1).unwrap()); - /// ``` #[inline] + #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] + #[allow(deprecated)] pub fn date(&self) -> Date { Date::from_utc(self.naive_local().date(), self.offset.clone()) } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 6abfebadc6..bc727713c2 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -57,6 +57,8 @@ pub struct Local; impl Local { /// Returns a `Date` which corresponds to the current date. + #[deprecated(since = "0.4.23", note = "use `Local::now()` instead")] + #[allow(deprecated)] pub fn today() -> Date { Local::now().date() } @@ -97,6 +99,7 @@ impl TimeZone for Local { } // they are easier to define in terms of the finished date and time unlike other offsets + #[allow(deprecated)] fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { self.from_local_date(local).map(|date| *date.offset()) } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 78256a663c..20fa4ecaf2 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -234,7 +234,8 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date, invalid month and/or day. - #[deprecated(since = "0.4.23", note = "use `ymd_opt()` instead")] + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] fn ymd(&self, year: i32, month: u32, day: u32) -> Date { self.ymd_opt(year, month, day).unwrap() } @@ -246,15 +247,8 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, LocalResult, TimeZone}; - /// - /// assert_eq!(Utc.ymd_opt(2015, 5, 15).unwrap().to_string(), "2015-05-15UTC"); - /// assert_eq!(Utc.ymd_opt(2000, 0, 0), LocalResult::None); - /// ``` + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), @@ -269,7 +263,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid DOY. - #[deprecated(since = "0.4.23", note = "use `ymd_opt()` instead")] + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn yo(&self, year: i32, ordinal: u32) -> Date { self.yo_opt(year, ordinal).unwrap() } @@ -281,14 +279,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid DOY. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.yo_opt(2015, 135).unwrap().to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), @@ -305,7 +300,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid week number. - #[deprecated(since = "0.4.23", note = "use `isoywd_opt()` instead")] + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date { self.isoywd_opt(year, week, weekday).unwrap() } @@ -319,14 +318,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ``` - /// use chrono::{Utc, Weekday, TimeZone}; - /// - /// assert_eq!(Utc.isoywd_opt(2015, 20, Weekday::Fri).unwrap().to_string(), "2015-05-15UTC"); - /// ``` + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), @@ -452,6 +448,8 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")] + #[allow(deprecated)] fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected @@ -475,6 +473,8 @@ pub trait TimeZone: Sized + Clone { /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")] + #[allow(deprecated)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { Date::from_utc(*utc, self.offset_from_utc_date(utc)) } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 8cf03daa5c..ef9a6f7d72 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -48,6 +48,11 @@ pub struct Utc; #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Utc { /// Returns a `Date` which corresponds to the current date. + #[deprecated( + since = "0.4.23", + note = "use `Utc::now()` instead, potentially with `.date_naive()`" + )] + #[allow(deprecated)] pub fn today() -> Date { Utc::now().date() } From 22b4d324988323fdadb6bac8706b79d936dcdee3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 20 Oct 2022 12:40:19 +0200 Subject: [PATCH 169/999] Avoid use of deprecated API --- benches/chrono.rs | 18 +- ci/core-test/src/lib.rs | 2 +- src/date.rs | 1 + src/datetime/mod.rs | 147 ++++++---------- src/datetime/serde.rs | 58 +++---- src/datetime/tests.rs | 324 ++++++++++++++++++++++++------------ src/format/mod.rs | 2 +- src/format/parse.rs | 23 +-- src/format/parsed.rs | 33 ++-- src/format/strftime.rs | 10 +- src/lib.rs | 39 ++--- src/month.rs | 12 +- src/naive/datetime/serde.rs | 8 +- src/naive/datetime/tests.rs | 2 +- src/naive/time/mod.rs | 12 +- src/offset/fixed.rs | 28 ++-- src/offset/local/mod.rs | 10 +- src/offset/mod.rs | 2 + src/offset/utc.rs | 3 +- src/round.rs | 231 ++++++++++++++++--------- tests/dateutils.rs | 2 +- 21 files changed, 574 insertions(+), 393 deletions(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index c0704e33cb..a0fc737c2f 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -36,13 +36,27 @@ fn bench_datetime_from_str(c: &mut Criterion) { fn bench_datetime_to_rfc2822(c: &mut Criterion) { let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); } fn bench_datetime_to_rfc3339(c: &mut Criterion) { let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - let dt = pst.ymd_opt(2018, 1, 11).and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); } diff --git a/ci/core-test/src/lib.rs b/ci/core-test/src/lib.rs index c4d7e9cc1a..4af7d2ecc5 100644 --- a/ci/core-test/src/lib.rs +++ b/ci/core-test/src/lib.rs @@ -3,5 +3,5 @@ use chrono::{TimeZone, Utc}; pub fn create_time() { - let _ = Utc.ymd_opt(2019, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let _ = Utc.with_ymd_and_hms(2019, 1, 1, 0, 0, 0).unwrap(); } diff --git a/src/date.rs b/src/date.rs index 209d26c753..bad4bfbb8a 100644 --- a/src/date.rs +++ b/src/date.rs @@ -2,6 +2,7 @@ // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date with time zone. +#![allow(deprecated)] #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 170b358593..1fc9e3dce6 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -30,6 +30,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; +#[allow(deprecated)] use crate::Date; use crate::Months; use crate::{Datelike, Timelike, Weekday}; @@ -167,8 +168,8 @@ impl DateTime { /// ``` /// use chrono::prelude::*; /// - /// let date: DateTime = Utc.ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); - /// let other: DateTime = FixedOffset::east_opt(23).unwrap().ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// let other: DateTime = FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); /// assert_eq!(date.date_naive(), other.date_naive()); /// ``` #[inline] @@ -201,13 +202,12 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; + /// use chrono::{Utc, TimeZone, NaiveDate}; /// - /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); /// - /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// ``` #[inline] @@ -225,13 +225,12 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; + /// use chrono::{Utc, TimeZone, NaiveDate}; /// - /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); /// - /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] @@ -249,13 +248,12 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::Utc; - /// use chrono::TimeZone; + /// use chrono::{Utc, TimeZone, NaiveDate}; /// - /// let dt = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); /// - /// let dt = Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); /// ``` #[inline] @@ -529,10 +527,10 @@ impl DateTime { /// representation of times in HTTP and email headers. /// /// ``` - /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; /// assert_eq!( /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), - /// FixedOffset::east_opt(0).unwrap().ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() + /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` pub fn parse_from_rfc2822(s: &str) -> ParseResult> { @@ -570,11 +568,11 @@ impl DateTime { /// # Example /// /// ```rust - /// use chrono::{DateTime, FixedOffset, TimeZone}; + /// use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; /// /// let dt = DateTime::parse_from_str( /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east_opt(0).unwrap().ymd_opt(1983, 4, 13).unwrap().and_hms_milli_opt(12, 9, 14, 274).unwrap())); + /// assert_eq!(dt, Ok(FixedOffset::east_opt(0).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(1983, 4, 13).unwrap().and_hms_milli_opt(12, 9, 14, 274).unwrap()).unwrap())); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); @@ -617,8 +615,8 @@ where /// # Examples /// /// ```rust - /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap(); + /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), /// "2018-01-26T18:30:09.453+00:00"); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), @@ -627,7 +625,7 @@ where /// "2018-01-26T18:30:09Z"); /// /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - /// let dt = pst.ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap(); + /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` @@ -696,7 +694,7 @@ where /// ```rust /// use chrono::prelude::*; /// - /// let date_time: DateTime = Utc.ymd_opt(2017, 04, 02).unwrap().and_hms_opt(12, 50, 32).unwrap(); + /// let date_time: DateTime = Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap(); /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` @@ -880,8 +878,8 @@ impl PartialOrd> for DateTime { /// ``` /// use chrono::prelude::*; /// - /// let earlier = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(2, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); - /// let later = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(3, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); + /// let earlier = Utc.with_ymd_and_hms(2015, 5, 15, 2, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); + /// let later = Utc.with_ymd_and_hms(2015, 5, 15, 3, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); /// /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); @@ -1166,47 +1164,27 @@ where #[test] fn test_add_sub_months() { - let utc_dt = Utc.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); - assert_eq!( - utc_dt + Months::new(15), - Utc.ymd_opt(2019, 12, 5).unwrap().and_hms_opt(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(2019, 12, 5, 23, 58, 0).unwrap()); - let utc_dt = Utc.ymd_opt(2020, 1, 31).unwrap().and_hms_opt(23, 58, 0).unwrap(); - assert_eq!( - utc_dt + Months::new(1), - Utc.ymd_opt(2020, 2, 29).unwrap().and_hms_opt(23, 58, 0).unwrap() - ); - assert_eq!( - utc_dt + Months::new(2), - Utc.ymd_opt(2020, 3, 31).unwrap().and_hms_opt(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.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); - assert_eq!( - utc_dt - Months::new(15), - Utc.ymd_opt(2017, 6, 5).unwrap().and_hms_opt(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.ymd_opt(2020, 3, 31).unwrap().and_hms_opt(23, 58, 0).unwrap(); - assert_eq!( - utc_dt - Months::new(1), - Utc.ymd_opt(2020, 2, 29).unwrap().and_hms_opt(23, 58, 0).unwrap() - ); - assert_eq!( - utc_dt - Months::new(2), - Utc.ymd_opt(2020, 1, 31).unwrap().and_hms_opt(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.ymd_opt(2018, 9, 5).unwrap().and_hms_opt(23, 58, 0).unwrap(); + 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() - .ymd_opt(2018, 9, 5) - .unwrap() - .and_hms_opt(18, 58, 0) + .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) .unwrap(); let utc_dt2: DateTime = cdt_dt.into(); assert_eq!(utc_dt, utc_dt2); @@ -1220,30 +1198,20 @@ where E: ::core::fmt::Debug, { assert_eq!( - to_string_utc(&Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()).ok(), + to_string_utc(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), Some(r#""2014-07-24T12:34:06Z""#.into()) ); assert_eq!( to_string_fixed( - &FixedOffset::east_opt(3660) - .unwrap() - .ymd_opt(2014, 7, 24) - .unwrap() - .and_hms_opt(12, 34, 6) - .unwrap() + &FixedOffset::east_opt(3660).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ) .ok(), Some(r#""2014-07-24T12:34:06+01:01""#.into()) ); assert_eq!( to_string_fixed( - &FixedOffset::east_opt(3650) - .unwrap() - .ymd_opt(2014, 7, 24) - .unwrap() - .and_hms_opt(12, 34, 6) - .unwrap() + &FixedOffset::east_opt(3650).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ) .ok(), Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) @@ -1268,22 +1236,17 @@ fn test_decodable_json( assert_eq!( norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap())) + norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap())) ); assert_eq!( norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap())) + norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap())) ); assert_eq!( norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), norm(&Some( - FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2014, 7, 24) - .unwrap() - .and_hms_opt(12, 34, 6) - .unwrap() + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() )) ); assert_eq!( @@ -1291,9 +1254,7 @@ fn test_decodable_json( norm(&Some( FixedOffset::east_opt(60 * 60 + 23 * 60) .unwrap() - .ymd_opt(2014, 7, 24) - .unwrap() - .and_hms_opt(13, 57, 6) + .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) .unwrap() )) ); @@ -1302,11 +1263,11 @@ fn test_decodable_json( // the conversion didn't change the instant itself assert_eq!( local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), - Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() + Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ); assert_eq!( local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() + Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ); assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); @@ -1330,42 +1291,32 @@ fn test_decodable_json_timestamps( assert_eq!( norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap())) + norm(&Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap())) ); assert_eq!( norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap())) + norm(&Some(Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap())) ); assert_eq!( norm(&fixed_from_str("0").ok().map(DateTime::from)), norm(&Some( - FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(1970, 1, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() )) ); assert_eq!( norm(&fixed_from_str("-1").ok().map(DateTime::from)), norm(&Some( - FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(1969, 12, 31) - .unwrap() - .and_hms_opt(23, 59, 59) - .unwrap() + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap() )) ); assert_eq!( *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() + Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() ); assert_eq!( *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap() + Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap() ); } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 890ec985d5..ab0126e2b4 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -122,7 +122,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] @@ -131,7 +131,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// time: DateTime /// } /// -/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -158,7 +158,7 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; /// #[derive(Serialize)] @@ -168,7 +168,7 @@ pub mod ts_nanoseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -245,7 +245,7 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds_option; /// #[derive(Deserialize, Serialize)] @@ -254,7 +254,7 @@ pub mod ts_nanoseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -280,7 +280,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; /// #[derive(Serialize)] @@ -290,7 +290,7 @@ pub mod ts_nanoseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -374,7 +374,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] @@ -383,7 +383,7 @@ pub mod ts_nanoseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -409,7 +409,7 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; /// #[derive(Serialize)] @@ -419,7 +419,7 @@ pub mod ts_microseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -496,7 +496,7 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds_option; /// #[derive(Deserialize, Serialize)] @@ -505,7 +505,7 @@ pub mod ts_microseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -530,7 +530,7 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; /// #[derive(Serialize)] @@ -540,7 +540,7 @@ pub mod ts_microseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -624,7 +624,7 @@ pub mod ts_microseconds_option { /// # Example /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] @@ -633,7 +633,7 @@ pub mod ts_microseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -659,7 +659,7 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; /// #[derive(Serialize)] @@ -669,7 +669,7 @@ pub mod ts_milliseconds { /// } /// /// let my_s = S { - /// time: Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -743,7 +743,7 @@ pub mod ts_milliseconds { /// # Example /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] @@ -752,7 +752,7 @@ pub mod ts_milliseconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()); +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -777,7 +777,7 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; /// #[derive(Serialize)] @@ -787,7 +787,7 @@ pub mod ts_milliseconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()), + /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -893,7 +893,7 @@ pub mod ts_milliseconds_option { /// time: DateTime /// } /// -/// let time = Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); +/// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); /// let my_s = S { /// time: time.clone(), /// }; @@ -929,7 +929,7 @@ pub mod ts_seconds { /// } /// /// let my_s = S { - /// time: Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(), + /// time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -1009,7 +1009,7 @@ pub mod ts_seconds { /// time: Option> /// } /// -/// let time = Some(Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap()); +/// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); /// let my_s = S { /// time: time.clone(), /// }; @@ -1044,7 +1044,7 @@ pub mod ts_seconds_option { /// } /// /// let my_s = S { - /// time: Some(Utc.ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap()), + /// time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -1142,7 +1142,7 @@ fn test_serde_bincode() { // it is not self-describing. use bincode::{deserialize, serialize}; - let dt = Utc.ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap(); + 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); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index f92000675f..879e2baa0a 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -16,96 +16,90 @@ fn test_datetime_offset() { let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); assert_eq!( - format!("{}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 UTC" ); assert_eq!( - format!("{}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 -04:00" ); assert_eq!( - format!("{}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06 07:08:09 +09:00" ); assert_eq!( - format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09Z" ); assert_eq!( - format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09-04:00" ); assert_eq!( - format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), "2014-05-06T07:08:09+09:00" ); // edge cases assert_eq!( - format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00Z" ); assert_eq!( - format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00-04:00" ); assert_eq!( - format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), "2014-05-06T00:00:00+09:00" ); assert_eq!( - format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59Z" ); assert_eq!( - format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59-04:00" ); assert_eq!( - format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), "2014-05-06T23:59:59+09:00" ); - let dt = Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap(); - assert_eq!(dt, edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(3, 8, 9).unwrap()); + let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( dt + Duration::seconds(3600 + 60 + 1), - Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(8, 9, 10).unwrap() + Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() ); assert_eq!( - dt.signed_duration_since(edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(10, 11, 12).unwrap()), + dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), Duration::seconds(-7 * 3600 - 3 * 60 - 3) ); - assert_eq!(*Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset(), Utc); - assert_eq!(*edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset(), edt); - assert!(*edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap().offset() != est); + assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); + assert_eq!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), edt); + assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); } #[test] fn test_datetime_date_and_time() { let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); - let d = tz.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap(); + let d = tz.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(d.time(), NaiveTime::from_hms_opt(7, 8, 9).unwrap()); - assert_eq!(d.date(), tz.ymd_opt(2014, 5, 6).unwrap()); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2014, 5, 6).unwrap()); - assert_eq!(d.date().and_time(d.time()), Some(d)); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2014, 5, 6).unwrap()); let tz = FixedOffset::east_opt(4 * 60 * 60).unwrap(); - let d = tz.ymd_opt(2016, 5, 4).unwrap().and_hms_opt(3, 2, 1).unwrap(); + let d = tz.with_ymd_and_hms(2016, 5, 4, 3, 2, 1).unwrap(); assert_eq!(d.time(), NaiveTime::from_hms_opt(3, 2, 1).unwrap()); - assert_eq!(d.date(), tz.ymd_opt(2016, 5, 4).unwrap()); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2016, 5, 4).unwrap()); - assert_eq!(d.date().and_time(d.time()), Some(d)); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2016, 5, 4).unwrap()); let tz = FixedOffset::west_opt(13 * 60 * 60).unwrap(); - let d = tz.ymd_opt(2017, 8, 9).unwrap().and_hms_opt(12, 34, 56).unwrap(); + let d = tz.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); assert_eq!(d.time(), NaiveTime::from_hms_opt(12, 34, 56).unwrap()); - assert_eq!(d.date(), tz.ymd_opt(2017, 8, 9).unwrap()); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd_opt(2017, 8, 9).unwrap()); - assert_eq!(d.date().and_time(d.time()), Some(d)); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2017, 8, 9).unwrap()); - let utc_d = Utc.ymd_opt(2017, 8, 9).unwrap().and_hms_opt(12, 34, 56).unwrap(); + let utc_d = Utc.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); assert!(utc_d < d); } @@ -122,73 +116,92 @@ fn test_datetime_with_timezone() { fn test_datetime_rfc2822_and_rfc3339() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc2822(), + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc2822(), + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc2822(), + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap() + .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:59:60.234567+05:00" ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap() + ) + .unwrap()) ); assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap()) ); } @@ -196,7 +209,14 @@ fn test_datetime_rfc2822_and_rfc3339() { fn test_rfc3339_opts() { use crate::SecondsFormat::*; let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_000).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); @@ -218,7 +238,7 @@ fn test_rfc3339_opts() { #[should_panic] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; - let dt = Utc.ymd_opt(1999, 10, 9).unwrap().and_hms_opt(1, 2, 3).unwrap(); + let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); } @@ -228,51 +248,95 @@ fn test_datetime_from_str() { "2015-02-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east_opt(0) .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east_opt(0) .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) .unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(FixedOffset::west_opt(10 * 3600) .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(13, 16, 9, 150) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap() + ) .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -282,7 +346,7 @@ fn test_datetime_from_str() { #[test] fn test_datetime_parse_from_str() { let ymdhms = |y, m, d, h, n, s, off| { - FixedOffset::east_opt(off).unwrap().ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() }; assert_eq!( DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), @@ -293,13 +357,13 @@ fn test_datetime_parse_from_str() { .is_err()); assert_eq!( Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), - Ok(Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap()) + Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap()) ); } #[test] fn test_to_string_round_trip() { - let dt = Utc.ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let dt = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let _dt: DateTime = dt.to_string().parse().unwrap(); let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap()); @@ -349,7 +413,14 @@ fn test_datetime_is_send() { #[test] fn test_subsecond_part() { - let datetime = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 1234567).unwrap(); + let datetime = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 7, 8) + .unwrap() + .and_hms_nano_opt(9, 10, 11, 1234567) + .unwrap(), + ) + .unwrap(); assert_eq!(1, datetime.timestamp_subsec_millis()); assert_eq!(1234, datetime.timestamp_subsec_micros()); @@ -361,31 +432,52 @@ fn test_subsecond_part() { fn test_from_system_time() { use std::time::Duration; - let epoch = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); let nanos = 999_999_999; // SystemTime -> DateTime assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); assert_eq!( DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() ); assert_eq!( DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() + ) + .unwrap() ); // DateTime -> SystemTime assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); assert_eq!( SystemTime::from( - Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() ), UNIX_EPOCH + Duration::new(999_999_999, nanos) ); assert_eq!( SystemTime::from( - Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1) + .unwrap() + ) + .unwrap() ), UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) ); @@ -412,30 +504,54 @@ fn test_from_system_time() { let nanos = 999_999_000; - let epoch = Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); // SystemTime -> DateTime assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); assert_eq!( DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() ); assert_eq!( DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1_000).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() ); // DateTime -> SystemTime assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); assert_eq!( SystemTime::from( - Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 39, nanos).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() ), UNIX_EPOCH + Duration::new(999_999_999, nanos) ); assert_eq!( SystemTime::from( - Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1_000).unwrap() + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() ), UNIX_EPOCH - Duration::new(999_999_999, nanos) ); @@ -457,7 +573,7 @@ fn test_from_system_time() { #[test] fn test_datetime_format_alignment() { - let datetime = Utc.ymd_opt(2007, 1, 2).unwrap(); + let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap(); // Item::Literal let percent = datetime.format("%%"); @@ -514,16 +630,18 @@ fn test_years_elapsed() { const WEEKS_PER_YEAR: f32 = 52.1775; // This is always at least one year because 1 year = 52.1775 weeks. - let one_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + let one_year_ago = + Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. - let two_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + let two_year_ago = + Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); - assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); - assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); + assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::today() + Duration::weeks(12); - assert_eq!(Utc::today().years_since(future), None); + let future = Utc::now().date_naive() + Duration::weeks(12); + assert_eq!(Utc::now().date_naive().years_since(future), None); } #[test] diff --git a/src/format/mod.rs b/src/format/mod.rs index 39b3db7d82..5e706db998 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -20,7 +20,7 @@ //! # use std::error::Error; //! use chrono::prelude::*; //! -//! let date_time = Utc.ymd_opt(2020, 11, 10).unwrap().and_hms_opt(0, 1, 32).unwrap(); +//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); //! //! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); //! assert_eq!(formatted, "2020-11-10 00:01:32"); diff --git a/src/format/parse.rs b/src/format/parse.rs index c6a469062b..69204d2e95 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -892,7 +892,7 @@ fn parse_rfc850() { static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.ymd_opt(1994, 11, 6).unwrap().and_hms_opt(8, 49, 37).unwrap(); + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); @@ -903,28 +903,19 @@ fn parse_rfc850() { // Check that the rest of the weekdays parse correctly (this test originally failed because // Sunday parsed incorrectly). let testdates = [ + (Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), "Monday, 07-Nov-94 08:49:37 GMT"), + (Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), "Tuesday, 08-Nov-94 08:49:37 GMT"), ( - Utc.ymd_opt(1994, 11, 7).unwrap().and_hms_opt(8, 49, 37).unwrap(), - "Monday, 07-Nov-94 08:49:37 GMT", - ), - ( - Utc.ymd_opt(1994, 11, 8).unwrap().and_hms_opt(8, 49, 37).unwrap(), - "Tuesday, 08-Nov-94 08:49:37 GMT", - ), - ( - Utc.ymd_opt(1994, 11, 9).unwrap().and_hms_opt(8, 49, 37).unwrap(), + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), "Wednesday, 09-Nov-94 08:49:37 GMT", ), ( - Utc.ymd_opt(1994, 11, 10).unwrap().and_hms_opt(8, 49, 37).unwrap(), + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), "Thursday, 10-Nov-94 08:49:37 GMT", ), + (Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), "Friday, 11-Nov-94 08:49:37 GMT"), ( - Utc.ymd_opt(1994, 11, 11).unwrap().and_hms_opt(8, 49, 37).unwrap(), - "Friday, 11-Nov-94 08:49:37 GMT", - ), - ( - Utc.ymd_opt(1994, 11, 12).unwrap().and_hms_opt(8, 49, 37).unwrap(), + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), "Saturday, 12-Nov-94 08:49:37 GMT", ), ]; diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 1a09721d9a..54679ccda1 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -1181,9 +1181,12 @@ mod tests { let ymdhmsn = |y, m, d, h, n, s, nano, off| { Ok(FixedOffset::east_opt(off) .unwrap() - .ymd_opt(y, m, d) - .unwrap() - .and_hms_nano_opt(h, n, s, nano) + .from_local_datetime( + &NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_nano_opt(h, n, s, nano) + .unwrap(), + ) .unwrap()) }; @@ -1228,7 +1231,14 @@ mod tests { parse!(Utc; year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), - Ok(Utc.ymd_opt(2014, 12, 31).unwrap().and_hms_nano_opt(4, 26, 40, 12_345_678).unwrap()) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(4, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); assert_eq!( parse!(Utc; @@ -1248,16 +1258,19 @@ mod tests { minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), Ok(FixedOffset::east_opt(32400) .unwrap() - .ymd_opt(2014, 12, 31) - .unwrap() - .and_hms_nano_opt(13, 26, 40, 12_345_678) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(13, 26, 40, 12_345_678) + .unwrap() + ) .unwrap()) ); // single result from timestamp assert_eq!( parse!(Utc; timestamp: 1_420_000_000, offset: 0), - Ok(Utc.ymd_opt(2014, 12, 31).unwrap().and_hms_opt(4, 26, 40).unwrap()) + Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) ); assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); assert_eq!( @@ -1268,9 +1281,7 @@ mod tests { parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), Ok(FixedOffset::east_opt(32400) .unwrap() - .ymd_opt(2014, 12, 31) - .unwrap() - .and_hms_opt(13, 26, 40) + .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) .unwrap()) ); diff --git a/src/format/strftime.rs b/src/format/strftime.rs index d4bf43d614..b2588fd961 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -556,13 +556,17 @@ fn test_strftime_items() { #[cfg(test)] #[test] fn test_strftime_docs() { + use crate::NaiveDate; use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; let dt = FixedOffset::east_opt(34200) .unwrap() - .ymd_opt(2001, 7, 8) - .unwrap() - .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) .unwrap(); // date specifiers diff --git a/src/lib.rs b/src/lib.rs index b5401a0cd1..861ee10593 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,26 +126,26 @@ //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! -//! let dt = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` +//! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") //! assert_eq!(dt, Utc.yo(2014, 189).and_hms_opt(9, 10, 11).unwrap()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. //! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms_opt(9, 10, 11).unwrap()); //! -//! let dt = Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap()); -//! assert_eq!(dt, Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap()); +//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap().and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap().and_local_timezone(Utc).unwrap()); +//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap()); //! //! // dynamic verification //! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), -//! LocalResult::Single(Utc.ymd_opt(2014, 7, 8).unwrap().and_hms_opt(21, 15, 33).unwrap())); +//! LocalResult::Single(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33).unwrap())); //! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); //! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); //! //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -//! let local_dt = Local.ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(); -//! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap(); +//! let local_dt = Local.from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap()).unwrap(); +//! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; //! ``` @@ -161,7 +161,7 @@ //! use chrono::Duration; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: -//! let dt = FixedOffset::east_opt(9*3600).unwrap().ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap(); +//! let dt = FixedOffset::east_opt(9*3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap()).unwrap(); //! //! // property accessors //! assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); @@ -175,7 +175,7 @@ //! // time zone accessor and manipulation //! assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); //! assert_eq!(dt.timezone(), FixedOffset::east_opt(9 * 3600).unwrap()); -//! assert_eq!(dt.with_timezone(&Utc), Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 45, 59, 324310806).unwrap()); +//! assert_eq!(dt.with_timezone(&Utc), NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 45, 59, 324310806).unwrap().and_local_timezone(Utc).unwrap()); //! //! // a sample of property manipulations (validates dynamically) //! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -183,14 +183,14 @@ //! assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE //! //! // arithmetic operations -//! let dt1 = Utc.ymd_opt(2014, 11, 14).unwrap().and_hms_opt(8, 9, 10).unwrap(); -//! let dt2 = Utc.ymd_opt(2014, 11, 14).unwrap().and_hms_opt(10, 9, 8).unwrap(); +//! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); +//! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); //! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); //! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() + Duration::seconds(1_000_000_000), -//! Utc.ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap()); -//! assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() - Duration::seconds(1_000_000_000), -//! Utc.ymd_opt(1938, 4, 24).unwrap().and_hms_opt(22, 13, 20).unwrap()); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + Duration::seconds(1_000_000_000), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - Duration::seconds(1_000_000_000), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); //! ``` //! //! ### Formatting and Parsing @@ -221,7 +221,7 @@ //! //! # #[cfg(feature = "unstable-locales")] //! # fn test() { -//! let dt = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_opt(12, 0, 9).unwrap(); +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); //! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); @@ -233,7 +233,7 @@ //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); //! //! // Note that milli/nanoseconds are only printed if they are non-zero -//! let dt_nano = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap(); +//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap().and_local_timezone(Utc).unwrap(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } //! # #[cfg(not(feature = "unstable-locales"))] @@ -273,7 +273,7 @@ //! ```rust //! use chrono::prelude::*; //! -//! let dt = Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_opt(12, 0, 9).unwrap(); +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9*3600).unwrap()); //! //! // method 1 @@ -343,7 +343,7 @@ //! //! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().weekday(), Weekday::Fri); //! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().format("%H%M%S").to_string(), +//! assert_eq!(NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().and_local_timezone(Utc).unwrap().format("%H%M%S").to_string(), //! "070809"); //! ``` //! @@ -429,6 +429,7 @@ doctest!("../README.md"); /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { #[doc(no_inline)] + #[allow(deprecated)] pub use crate::Date; #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] diff --git a/src/month.rs b/src/month.rs index baa3a66022..f444dc0270 100644 --- a/src/month.rs +++ b/src/month.rs @@ -12,7 +12,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// ``` /// use num_traits::FromPrimitive; /// use chrono::prelude::*; -/// let date = Utc.ymd_opt(2019, 10, 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); /// // `2019-10-28T09:10:11Z` /// let month = Month::from_u32(date.month()); /// assert_eq!(month, Some(Month::October)) @@ -21,7 +21,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// ``` /// # use chrono::prelude::*; /// let month = Month::January; -/// let dt = Utc.ymd_opt(2019, month.number_from_month(), 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// 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)); /// ``` /// Allows mapping from and to month, from 1-January to 12-December. @@ -337,15 +337,11 @@ mod tests { assert_eq!(dec_opt, Some(Month::December)); assert_eq!(no_month, None); - let date = Utc.ymd_opt(2019, 10, 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); assert_eq!(Month::from_u32(date.month()), Some(Month::October)); let month = Month::January; - let dt = Utc - .ymd_opt(2019, month.number_from_month(), 28) - .unwrap() - .and_hms_opt(9, 10, 11) - .unwrap(); + 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)); } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 629f12f2e3..40695fa74b 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1053,7 +1053,7 @@ fn test_serde_deserialize() { // it is not self-describing. #[test] fn test_serde_bincode() { - use crate::naive::NaiveDate; + 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(); @@ -1076,10 +1076,8 @@ fn test_serde_bincode_optional() { two: Option>, } - let expected = Test { - one: Some(1), - two: Some(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 1, 1).unwrap()), - }; + 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(); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index bd070994f9..01c21f1004 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,6 +1,6 @@ use super::NaiveDateTime; -use crate::naive::NaiveDate; use crate::oldtime::Duration; +use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; use std::i64; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index c90cba8619..260423a9d1 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -80,7 +80,7 @@ mod tests; /// /// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_micro_opt(8, 59, 59, 1_000_000).unwrap(); /// -/// let dt2 = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap(); +/// let dt2 = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap().and_local_timezone(Utc).unwrap(); /// # let _ = (t, dt1, dt2); /// ``` /// @@ -161,9 +161,9 @@ mod tests; /// will be represented as the second part being 60, as required by ISO 8601. /// /// ``` -/// use chrono::{Utc, TimeZone}; +/// use chrono::{Utc, TimeZone, NaiveDate}; /// -/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// @@ -175,12 +175,12 @@ mod tests; /// and would be read back to the next non-leap second. /// /// ``` -/// use chrono::{DateTime, Utc, TimeZone}; +/// use chrono::{DateTime, Utc, TimeZone, NaiveDate}; /// -/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 56, 4, 1_000).unwrap(); +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 56, 4, 1_000).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); /// -/// let dt = Utc.ymd_opt(2015, 6, 30).unwrap().and_hms_opt(23, 56, 5).unwrap(); +/// let dt = Utc.with_ymd_and_hms(2015, 6, 30, 23, 56, 5).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); /// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); /// ``` diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 18896173e6..0a8b26ebfc 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -241,33 +241,41 @@ mod tests { // starting from 0.3 we don't have an offset exceeding one day. // this makes everything easier! assert_eq!( - format!("{:?}", FixedOffset::east_opt(86399).unwrap().ymd_opt(2012, 2, 29).unwrap()), - "2012-02-29+23:59:59".to_string() + format!( + "{:?}", + FixedOffset::east_opt(86399) + .unwrap() + .with_ymd_and_hms(2012, 2, 29, 5, 6, 7) + .unwrap() + ), + "2012-02-29T05:06:07+23:59:59".to_string() ); assert_eq!( format!( "{:?}", FixedOffset::east_opt(86399) .unwrap() - .ymd_opt(2012, 2, 29) - .unwrap() - .and_hms_opt(5, 6, 7) + .with_ymd_and_hms(2012, 2, 29, 5, 6, 7) .unwrap() ), "2012-02-29T05:06:07+23:59:59".to_string() ); assert_eq!( - format!("{:?}", FixedOffset::west_opt(86399).unwrap().ymd_opt(2012, 3, 4).unwrap()), - "2012-03-04-23:59:59".to_string() + format!( + "{:?}", + FixedOffset::west_opt(86399) + .unwrap() + .with_ymd_and_hms(2012, 3, 4, 5, 6, 7) + .unwrap() + ), + "2012-03-04T05:06:07-23:59:59".to_string() ); assert_eq!( format!( "{:?}", FixedOffset::west_opt(86399) .unwrap() - .ymd_opt(2012, 3, 4) - .unwrap() - .and_hms_opt(5, 6, 7) + .with_ymd_and_hms(2012, 3, 4, 5, 6, 7) .unwrap() ), "2012-03-04T05:06:07-23:59:59".to_string() diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index bc727713c2..e280c78002 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -9,6 +9,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; +#[allow(deprecated)] use crate::{Date, DateTime}; // we don't want `stub.rs` when the target_os is not wasi or emscripten @@ -108,6 +109,7 @@ impl TimeZone for Local { self.from_local_datetime(local).map(|datetime| *datetime.offset()) } + #[allow(deprecated)] fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { *self.from_utc_date(utc).offset() } @@ -117,6 +119,7 @@ impl TimeZone for Local { } // override them for avoiding redundant works + #[allow(deprecated)] fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { // this sounds very strange, but required for keeping `TimeZone::ymd` sane. // in the other words, we use the offset at the local midnight @@ -149,6 +152,7 @@ impl TimeZone for Local { inner::naive_to_local(local, true) } + #[allow(deprecated)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap()); Date::from_utc(*utc, *midnight.offset()) @@ -183,7 +187,7 @@ impl TimeZone for Local { mod tests { use super::Local; use crate::offset::TimeZone; - use crate::{Datelike, Duration}; + use crate::{Datelike, Duration, Utc}; #[test] fn verify_correct_offsets() { @@ -231,13 +235,13 @@ mod tests { #[test] fn test_local_date_sanity_check() { // issue #27 - assert_eq!(Local.ymd_opt(2999, 12, 28).unwrap().day(), 28); + assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28); } #[test] fn test_leap_second() { // issue #123 - let today = Local::today(); + let today = Utc::now().date_naive(); let dt = today.and_hms_milli_opt(1, 2, 59, 1000).unwrap(); let timestr = dt.time().to_string(); diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 20fa4ecaf2..09d0714e98 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -23,6 +23,7 @@ use core::fmt; use crate::format::{parse, ParseResult, Parsed, StrftimeItems}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; +#[allow(deprecated)] use crate::{Date, DateTime}; mod fixed; @@ -84,6 +85,7 @@ impl LocalResult { } } +#[allow(deprecated)] impl LocalResult> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. diff --git a/src/offset/utc.rs b/src/offset/utc.rs index ef9a6f7d72..812d7b062e 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -20,6 +20,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[cfg(feature = "clock")] +#[allow(deprecated)] use crate::{Date, DateTime}; /// The UTC time zone. This is the most efficient time zone when you don't need the local time. @@ -37,7 +38,7 @@ use crate::{Date, DateTime}; /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); /// /// assert_eq!(Utc.timestamp(61, 0), dt); -/// assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 1, 1).unwrap(), dt); +/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] diff --git a/src/round.rs b/src/round.rs index a75cac0b2b..b95f245708 100644 --- a/src/round.rs +++ b/src/round.rs @@ -2,8 +2,8 @@ // See README.md and LICENSE.txt for details. use crate::datetime::DateTime; -use crate::naive::NaiveDateTime; use crate::oldtime::Duration; +use crate::NaiveDateTime; use crate::TimeZone; use crate::Timelike; use core::cmp::Ordering; @@ -24,8 +24,8 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -36,8 +36,8 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -111,8 +111,8 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -128,8 +128,8 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -244,7 +244,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(1970, 12, 12).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!( /// dt.duration_round(Duration::days(365)), @@ -256,8 +256,8 @@ pub enum RoundingError { /// Error when `Duration.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap(); + /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap(); /// /// assert_eq!( /// dt.duration_round(Duration::days(300 * 365)), @@ -270,7 +270,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2300, 12, 12).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); /// ``` @@ -306,12 +306,20 @@ impl std::error::Error for RoundingError { mod tests { use super::{Duration, DurationRound, SubsecRound}; use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::NaiveDate; use crate::Timelike; #[test] fn test_round_subsecs() { let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(10), dt); assert_eq!(dt.round_subsecs(9), dt); @@ -327,8 +335,14 @@ mod tests { assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 13); - let dt = - Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); @@ -341,8 +355,14 @@ mod tests { #[test] fn test_round_leap_nanos() { - let dt = - Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); @@ -356,7 +376,14 @@ mod tests { #[test] fn test_trunc_subsecs() { let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(10), dt); assert_eq!(dt.trunc_subsecs(9), dt); @@ -372,8 +399,14 @@ mod tests { assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).second(), 13); - let dt = - pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); @@ -386,8 +419,14 @@ mod tests { #[test] fn test_trunc_leap_nanos() { - let dt = - Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); @@ -400,8 +439,14 @@ mod tests { #[test] fn test_duration_round() { - let dt = - Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_round(Duration::zero()).unwrap().to_string(), @@ -414,13 +459,27 @@ mod tests { ); // round up - let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down - let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_round(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" @@ -444,12 +503,8 @@ mod tests { ); // timezone east - let dt = FixedOffset::east_opt(3600) - .unwrap() - .ymd_opt(2020, 10, 27) - .unwrap() - .and_hms_opt(15, 0, 0) - .unwrap(); + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( dt.duration_round(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" @@ -460,12 +515,8 @@ mod tests { ); // timezone west - let dt = FixedOffset::west_opt(3600) - .unwrap() - .ymd_opt(2020, 10, 27) - .unwrap() - .and_hms_opt(15, 0, 0) - .unwrap(); + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( dt.duration_round(Duration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" @@ -479,9 +530,12 @@ mod tests { #[test] fn test_duration_round_naive() { let dt = Utc - .ymd_opt(2016, 12, 31) - .unwrap() - .and_hms_nano_opt(23, 59, 59, 175_500_000) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) .unwrap() .naive_utc(); @@ -497,9 +551,12 @@ mod tests { // round up let dt = Utc - .ymd_opt(2012, 12, 12) - .unwrap() - .and_hms_milli_opt(18, 22, 30, 0) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) .unwrap() .naive_utc(); assert_eq!( @@ -508,9 +565,12 @@ mod tests { ); // round down let dt = Utc - .ymd_opt(2012, 12, 12) - .unwrap() - .and_hms_milli_opt(18, 22, 29, 999) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) .unwrap() .naive_utc(); assert_eq!( @@ -538,7 +598,7 @@ mod tests { #[test] fn test_duration_round_pre_epoch() { - let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( dt.duration_round(Duration::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" @@ -547,8 +607,14 @@ mod tests { #[test] fn test_duration_trunc() { - let dt = - Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), @@ -556,13 +622,27 @@ mod tests { ); // would round up - let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down - let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" @@ -585,12 +665,8 @@ mod tests { ); // timezone east - let dt = FixedOffset::east_opt(3600) - .unwrap() - .ymd_opt(2020, 10, 27) - .unwrap() - .and_hms_opt(15, 0, 0) - .unwrap(); + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( dt.duration_trunc(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" @@ -601,12 +677,8 @@ mod tests { ); // timezone west - let dt = FixedOffset::west_opt(3600) - .unwrap() - .ymd_opt(2020, 10, 27) - .unwrap() - .and_hms_opt(15, 0, 0) - .unwrap(); + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( dt.duration_trunc(Duration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" @@ -620,9 +692,12 @@ mod tests { #[test] fn test_duration_trunc_naive() { let dt = Utc - .ymd_opt(2016, 12, 31) - .unwrap() - .and_hms_nano_opt(23, 59, 59, 175_500_000) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) .unwrap() .naive_utc(); @@ -633,9 +708,12 @@ mod tests { // would round up let dt = Utc - .ymd_opt(2012, 12, 12) - .unwrap() - .and_hms_milli_opt(18, 22, 30, 0) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) .unwrap() .naive_utc(); assert_eq!( @@ -644,9 +722,12 @@ mod tests { ); // would round down let dt = Utc - .ymd_opt(2012, 12, 12) - .unwrap() - .and_hms_milli_opt(18, 22, 29, 999) + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) .unwrap() .naive_utc(); assert_eq!( @@ -673,7 +754,7 @@ mod tests { #[test] fn test_duration_trunc_pre_epoch() { - let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" diff --git a/tests/dateutils.rs b/tests/dateutils.rs index a37e854d5c..130649a71f 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -19,7 +19,7 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // seems to be consistent with the output of the `date` command, so we simply // compare both. // let local = Local - // .from_local_datetime(&NaiveDate::from_ymd_opt(year, month, day).unwrap().and_hms_opt(hour, 5, 1).unwrap()) + // .with_ymd_and_hms(year, month, day, hour, 5, 1) // // looks like the "date" command always returns a given time when it is ambiguous // .earliest(); From 8bc413936f16341b2e5c265d1524c56a7dbf1097 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sat, 22 Oct 2022 22:13:05 +1100 Subject: [PATCH 170/999] add bench for Local::now() --- benches/chrono.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index a0fc737c2f..246271b81d 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -4,7 +4,7 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use chrono::prelude::*; -use chrono::{DateTime, FixedOffset, Utc, __BenchYearFlags}; +use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags}; fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { c.bench_function("bench_datetime_parse_from_rfc2822", |b| { @@ -70,6 +70,14 @@ fn bench_year_flags_from_year(c: &mut Criterion) { }); } +fn bench_get_local_time(c: &mut Criterion) { + c.bench_function("bench_get_local_time", |b| { + b.iter(|| { + let _ = Local::now(); + }) + }); +} + /// Returns the number of multiples of `div` in the range `start..end`. /// /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the @@ -123,6 +131,7 @@ criterion_group!( bench_datetime_to_rfc3339, bench_year_flags_from_year, bench_num_days_from_ce, + bench_get_local_time, ); criterion_main!(benches); From 84f3c30d1ef1649772f1ffc99cef5b8436b07842 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 30 Oct 2022 22:30:27 +1100 Subject: [PATCH 171/999] move last_changed to the Cache --- src/offset/local/unix.rs | 65 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 398f07ec2f..1a1bdf888a 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -32,7 +32,7 @@ thread_local! { } enum Source { - LocalTime { mtime: SystemTime, last_checked: SystemTime }, + LocalTime { mtime: SystemTime }, // we don't bother storing the contents of the environment variable in this case. // changing the environment while the process is running is generally not reccomended Environment, @@ -53,47 +53,21 @@ impl Default for Source { // by picking SystemTime::now() we raise the probability of // the cache being invalidated if/when the mtime starts working mtime: data.modified().unwrap_or_else(|_| SystemTime::now()), - last_checked: SystemTime::now(), }, Err(_) => { // as above, now() should be a better default than some constant // TODO: see if we can improve caching in the case where the fallback is a valid timezone - Source::LocalTime { mtime: SystemTime::now(), last_checked: SystemTime::now() } + Source::LocalTime { mtime: SystemTime::now() } } }, } } } -impl Source { - fn out_of_date(&mut self) -> bool { - let now = SystemTime::now(); - let prev = match self { - Source::LocalTime { mtime, last_checked } => match now.duration_since(*last_checked) { - Ok(d) if d.as_secs() < 1 => return false, - Ok(_) | Err(_) => *mtime, - }, - Source::Environment => return false, - }; - - match Source::default() { - Source::LocalTime { mtime, .. } => { - *self = Source::LocalTime { mtime, last_checked: now }; - prev != mtime - } - // will only reach here if TZ has been set while - // the process is running - Source::Environment => { - *self = Source::Environment; - true - } - } - } -} - struct Cache { zone: TimeZone, source: Source, + last_checked: SystemTime, } #[cfg(target_os = "android")] @@ -118,14 +92,43 @@ impl Default for Cache { Cache { zone: TimeZone::local().ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc), source: Source::default(), + last_checked: SystemTime::now(), } } } impl Cache { fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult> { - if self.source.out_of_date() { - *self = Cache::default(); + let now = SystemTime::now(); + + match now.duration_since(self.last_checked) { + // If the cache has been around for less than a second then we reuse it + // unconditionally. This is a reasonable tradeoff because the timezone + // generally won't be changing _that_ often, but if the time zone does + // change, it will reflect sufficiently quickly from an application + // user's perspective. + Ok(d) if d.as_secs() < 1 => (), + Ok(_) | Err(_) => { + let new_source = Source::default(); + + let out_of_date = match (&self.source, &new_source) { + // change from env to file or file to env, must recreate the zone + (Source::Environment, Source::LocalTime { .. }) + | (Source::LocalTime { .. }, Source::Environment) => true, + // stay as file, but mtime has changed + (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime }) + if old_mtime != mtime => + { + true + } + // cache can be reused + _ => false, + }; + + if out_of_date { + *self = Cache::default(); + } + } } if !local { From 57908e918b88e26dc1d54feeb71a8e3b63f1d358 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 30 Oct 2022 22:44:54 +1100 Subject: [PATCH 172/999] allow sharing of the allocated environment variable --- src/offset/local/tz_info/timezone.rs | 11 +++++------ src/offset/local/unix.rs | 29 +++++++++++++++++++--------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 085b25c117..ae6fb5f848 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -26,11 +26,10 @@ impl TimeZone { /// /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. /// - pub(crate) fn local() -> Result { - if let Ok(tz) = std::env::var("TZ") { - Self::from_posix_tz(&tz) - } else { - Self::from_posix_tz("localtime") + pub(crate) fn local(env_tz: Option<&str>) -> Result { + match env_tz { + Some(tz) => Self::from_posix_tz(tz), + None => Self::from_posix_tz("localtime"), } } @@ -813,7 +812,7 @@ mod tests { // so just ensure that ::local() acts as expected // in this case if let Ok(tz) = std::env::var("TZ") { - let time_zone_local = TimeZone::local()?; + let time_zone_local = TimeZone::local(Some(tz.as_str()))?; let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; assert_eq!(time_zone_local, time_zone_local_1); } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 1a1bdf888a..e71c16ebee 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -38,16 +38,16 @@ enum Source { Environment, } -impl Default for Source { - fn default() -> Source { +impl Source { + fn new(env_tz: Option<&str>) -> Source { // use of var_os avoids allocating, which is nice // as we are only going to discard the string anyway // but we must ensure the contents are valid unicode // otherwise the behaivour here would be different // to that in `naive_to_local` - match env::var_os("TZ") { - Some(ref s) if s.to_str().is_some() => Source::Environment, - Some(_) | None => match fs::symlink_metadata("/etc/localtime") { + match env_tz { + Some(_) => Source::Environment, + None => match fs::symlink_metadata("/etc/localtime") { Ok(data) => Source::LocalTime { // we have to pick a sensible default when the mtime fails // by picking SystemTime::now() we raise the probability of @@ -89,14 +89,20 @@ fn fallback_timezone() -> Option { impl Default for Cache { fn default() -> Cache { // default to UTC if no local timezone can be found + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_ref().map(|s| s.as_str()); Cache { - zone: TimeZone::local().ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc), - source: Source::default(), last_checked: SystemTime::now(), + source: Source::new(env_ref), + zone: current_zone(env_ref), } } } +fn current_zone(var: Option<&str>) -> TimeZone { + TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc) +} + impl Cache { fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult> { let now = SystemTime::now(); @@ -109,7 +115,9 @@ impl Cache { // user's perspective. Ok(d) if d.as_secs() < 1 => (), Ok(_) | Err(_) => { - let new_source = Source::default(); + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_ref().map(|s| s.as_str()); + let new_source = Source::new(env_ref); let out_of_date = match (&self.source, &new_source) { // change from env to file or file to env, must recreate the zone @@ -126,8 +134,11 @@ impl Cache { }; if out_of_date { - *self = Cache::default(); + self.zone = current_zone(env_ref); } + + self.last_checked = now; + self.source = new_source; } } From dc4287a6278cacd26ec914e5a280c30c0b0e3d9b Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 30 Oct 2022 22:50:15 +1100 Subject: [PATCH 173/999] store hash of environment variable --- src/offset/local/unix.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index e71c16ebee..32aa31618b 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::{cell::RefCell, env, fs, time::SystemTime}; +use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; use super::tz_info::TimeZone; use super::{DateTime, FixedOffset, Local, NaiveDateTime}; @@ -33,20 +33,18 @@ thread_local! { enum Source { LocalTime { mtime: SystemTime }, - // we don't bother storing the contents of the environment variable in this case. - // changing the environment while the process is running is generally not reccomended - Environment, + Environment { hash: u64 }, } impl Source { fn new(env_tz: Option<&str>) -> Source { - // use of var_os avoids allocating, which is nice - // as we are only going to discard the string anyway - // but we must ensure the contents are valid unicode - // otherwise the behaivour here would be different - // to that in `naive_to_local` match env_tz { - Some(_) => Source::Environment, + Some(tz) => { + let mut hasher = hash_map::DefaultHasher::new(); + hasher.write(tz.as_bytes()); + let hash = hasher.finish(); + Source::Environment { hash } + } None => match fs::symlink_metadata("/etc/localtime") { Ok(data) => Source::LocalTime { // we have to pick a sensible default when the mtime fails @@ -121,14 +119,20 @@ impl Cache { let out_of_date = match (&self.source, &new_source) { // change from env to file or file to env, must recreate the zone - (Source::Environment, Source::LocalTime { .. }) - | (Source::LocalTime { .. }, Source::Environment) => true, + (Source::Environment { .. }, Source::LocalTime { .. }) + | (Source::LocalTime { .. }, Source::Environment { .. }) => true, // stay as file, but mtime has changed (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime }) if old_mtime != mtime => { true } + // stay as env, but hash of variable has changed + (Source::Environment { hash: old_hash }, Source::Environment { hash }) + if old_hash != hash => + { + true + } // cache can be reused _ => false, }; From 9e5eb49838835337f136472fc5103357113b64aa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 9 Nov 2022 13:36:16 +0100 Subject: [PATCH 174/999] Bump version to 0.4.23 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 850bc2dc1e..9c826c7c80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.22" +version = "0.4.23" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From e7eb35f1daa3978b5d8df17f851ad46ebdf25780 Mon Sep 17 00:00:00 2001 From: Daniel Brotsky Date: Mon, 14 Nov 2022 07:55:41 -0800 Subject: [PATCH 175/999] Fix doc on Days::new to refer to days, not months. This fixes chronotope/chrono#871 --- src/naive/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9db2ccf9a9..f88a247275 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -126,7 +126,7 @@ impl NaiveWeek { pub struct Days(pub(crate) u64); impl Days { - /// Construct a new `Days` from a number of months + /// Construct a new `Days` from a number of days pub fn new(num: u64) -> Self { Self(num) } From ff9db15b0989ac43223d1be2784d1646131fb8a2 Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Wed, 16 Nov 2022 12:10:53 +0100 Subject: [PATCH 176/999] Clarify out of range value for from_timestamp_opt The limits on the secs count has to be hunted down to the bottom of the readme / root document, which is a bit annoying when e.g. migrating from the deprecated `from_timestamp` and wondering why `from_timestamp_opt` is fallible. The nsecs limits can be inferred from the rest of the text but explicit is better than implicit. --- src/naive/datetime/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 5cf29739fa..2bf2be3b13 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -174,7 +174,8 @@ 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 and/or invalid nanosecond. + /// 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). /// /// # Example /// From 698d14795ef92ef76a795f40aed2e4bc6c694adc Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 18 Nov 2022 21:31:49 +0100 Subject: [PATCH 177/999] Add format_localized for chrono::naive::NaiveDate --- src/format/strftime.rs | 15 ++++++++++++++- src/naive/date.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b2588fd961..6338194366 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -663,7 +663,7 @@ fn test_strftime_docs() { #[cfg(feature = "unstable-locales")] #[test] fn test_strftime_docs_localized() { - use crate::{FixedOffset, TimeZone}; + use crate::{FixedOffset, NaiveDate, TimeZone}; let dt = FixedOffset::east_opt(34200).unwrap().ymd_opt(2001, 7, 8).unwrap().and_hms_nano( 0, @@ -696,4 +696,17 @@ fn test_strftime_docs_localized() { 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"); } diff --git a/src/naive/date.rs b/src/naive/date.rs index f88a247275..3fa2884406 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -14,6 +14,10 @@ use num_traits::ToPrimitive; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +/// L10n locales. +#[cfg(feature = "unstable-locales")] +use pure_rust_locales::Locale; + #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -1144,6 +1148,37 @@ impl NaiveDate { self.format_with_items(StrftimeItems::new(fmt)) } + /// Formats the date with the specified formatting items and locale. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[inline] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new_with_locale(Some(*self), None, items, locale) + } + + /// Formats the date with the specified format string and locale. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[inline] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } + /// Returns an iterator that steps by days across all representable dates. /// /// # Example From 1624fc32bd59caf4d2c2005a0d06a66d1c504581 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 15 Nov 2022 23:48:36 +1100 Subject: [PATCH 178/999] Fix add and sub days implemention and test with dst timezone This previously called the `.datetime` field on the datetime, however this is problematic as it is in UTC time. It now correctly uses the `.naive_local()` method to get the local time as a NaiveTime --- src/datetime/mod.rs | 4 +- src/datetime/tests.rs | 233 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1fc9e3dce6..05d0458e84 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -360,7 +360,7 @@ impl DateTime { /// /// Returns `None` if the resulting date would be out of range. pub fn checked_add_days(self, days: Days) -> Option { - self.datetime + self.naive_local() .checked_add_days(days)? .and_local_timezone(TimeZone::from_offset(&self.offset)) .single() @@ -370,7 +370,7 @@ impl DateTime { /// /// Returns `None` if the resulting date would be out of range. pub fn checked_sub_days(self, days: Days) -> Option { - self.datetime + self.naive_local() .checked_sub_days(days)? .and_local_timezone(TimeZone::from_offset(&self.offset)) .single() diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 879e2baa0a..ebd32cae93 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -8,6 +8,239 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::oldtime::Duration; #[cfg(feature = "clock")] use crate::Datelike; +use crate::{Days, LocalResult, Months, NaiveDateTime}; + +#[derive(Clone)] +struct DstTester; + +impl DstTester { + fn winter_offset() -> FixedOffset { + FixedOffset::east_opt(8 * 60 * 60).unwrap() + } + fn summer_offset() -> FixedOffset { + FixedOffset::east_opt(9 * 60 * 60).unwrap() + } + + const TO_WINTER_MONTH_DAY: (u32, u32) = (4, 15); + const TO_SUMMER_MONTH_DAY: (u32, u32) = (9, 15); + + fn transition_start_local() -> NaiveTime { + NaiveTime::from_hms_opt(2, 0, 0).unwrap() + } +} + +impl TimeZone for DstTester { + type Offset = FixedOffset; + + fn from_offset(_: &Self::Offset) -> Self { + DstTester + } + + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::LocalResult { + unimplemented!() + } + + fn offset_from_local_datetime( + &self, + local: &NaiveDateTime, + ) -> crate::LocalResult { + let local_to_winter_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_winter_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() - Duration::hours(1)); + + let local_to_summer_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_summer_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() + Duration::hours(1)); + + if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { + LocalResult::Single(DstTester::summer_offset()) + } else if *local >= local_to_winter_transition_start + && *local < local_to_summer_transition_start + { + LocalResult::Single(DstTester::winter_offset()) + } else if *local >= local_to_winter_transition_end + && *local < local_to_winter_transition_start + { + LocalResult::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) + } else if *local >= local_to_summer_transition_start + && *local < local_to_summer_transition_end + { + LocalResult::None + } else { + panic!("Unexpected local time {}", local) + } + } + + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset { + let utc_to_winter_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::summer_offset(); + + let utc_to_summer_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::winter_offset(); + + if *utc < utc_to_winter_transition || *utc >= utc_to_summer_transition { + DstTester::summer_offset() + } else if *utc >= utc_to_winter_transition && *utc < utc_to_summer_transition { + DstTester::winter_offset() + } else { + panic!("Unexpected utc time {}", utc) + } + } +} + +#[test] +fn test_datetime_add_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-04-11 07:08:09 +09:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-04-16 07:08:09 +08:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-09-11 07:08:09 +08:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-09-16 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_add_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 +09:00" + ); +} #[test] fn test_datetime_offset() { From e7d4402853c34bf1fbb7f59dfe257dae517dc7e2 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Wed, 23 Nov 2022 11:43:40 +1100 Subject: [PATCH 179/999] make NaiveTime::MIN public --- src/naive/time/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 260423a9d1..1d36583aa2 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -752,7 +752,8 @@ impl NaiveTime { (hour, min, sec) } - pub(super) const MIN: Self = Self { secs: 0, frac: 0 }; + /// The earliest possible `NaiveTime` + pub const MIN: Self = Self { secs: 0, frac: 0 }; pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; } From ff370ae656a52ada172bb7a0fd98d1051a998a93 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sun, 20 Nov 2022 17:39:10 +1100 Subject: [PATCH 180/999] fix bug and add more test cases --- src/naive/datetime/mod.rs | 37 ++++++++++++++++++++++++------------- src/naive/datetime/tests.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 2bf2be3b13..5f0f6b64ef 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -22,6 +22,7 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; +use core::cmp::Ordering; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -138,31 +139,41 @@ impl NaiveDateTime { /// /// ``` /// use chrono::NaiveDateTime; - /// let timestamp_millis: i64 = 1662921288; //Sunday, September 11, 2022 6:34:48 PM + /// let timestamp_millis: i64 = 1662921288000; //Sunday, September 11, 2022 6:34:48 PM /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); /// assert!(naive_datetime.is_some()); /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); /// /// // Negative timestamps (before the UNIX epoch) are supported as well. - /// let timestamp_millis: i64 = -2208936075; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let timestamp_millis: i64 = -2208936075000; //Mon Jan 01 1900 14:38:45 GMT+0000 /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); /// assert!(naive_datetime.is_some()); /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); /// ``` #[inline] pub fn from_timestamp_millis(millis: i64) -> Option { - let mut secs = millis / 1000; - if millis < 0 { - secs = secs.checked_sub(1)?; + let (secs, subsec_millis) = (millis / 1000, millis % 1000); + + match subsec_millis.cmp(&0) { + Ordering::Less => { + // in the case where our subsec part is negative, then we are actually in the earlier second + // hence we subtract one from the seconds part, and we then add a whole second worth of nanos + // to our nanos part. Due to the use of u32 datatype, it is more convenient to subtract + // the absolute value of the subsec nanos from a whole second worth of nanos + let nsecs = u32::try_from(subsec_millis.abs()).ok()? * NANOS_IN_MILLISECOND; + NaiveDateTime::from_timestamp_opt( + secs.checked_sub(1)?, + NANOS_IN_SECOND.checked_sub(nsecs)?, + ) + } + Ordering::Equal => NaiveDateTime::from_timestamp_opt(secs, 0), + Ordering::Greater => { + // convert the subsec millis into nanosecond scale so they can be supplied + // as the nanoseconds parameter + let nsecs = u32::try_from(subsec_millis).ok()? * NANOS_IN_MILLISECOND; + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } } - - let nsecs = (millis % 1000).abs(); - let mut nsecs = u32::try_from(nsecs).ok()? * NANOS_IN_MILLISECOND; - if secs < 0 { - nsecs = NANOS_IN_SECOND.checked_sub(nsecs)?; - } - - NaiveDateTime::from_timestamp_opt(secs, nsecs) } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 01c21f1004..3df475b9c1 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -4,6 +4,33 @@ use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; use std::i64; +#[test] +fn test_datetime_from_timestamp_millis() { + let valid_map = [ + (1662921288000, "2022-09-11 18:34:48.000000000"), + (1662921288123, "2022-09-11 18:34:48.123000000"), + (1662921287890, "2022-09-11 18:34:47.890000000"), + (-2208936075000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000, "1973-10-17 18:36:57.000000000"), + (1234567890000, "2009-02-13 23:31:30.000000000"), + (2034061609000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_millis, formatted) in valid_map.iter().cloned() { + let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); + assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_millis in invalid.iter().cloned() { + let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); + assert!(naive_datetime.is_none()); + } +} + #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); @@ -143,7 +170,7 @@ fn test_datetime_from_str() { assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", + `{:?}` does not match", s, d, d_ From 5312160ce2622f2dbed28a1b0c8868551e2526b1 Mon Sep 17 00:00:00 2001 From: Dan Dixey Date: Fri, 25 Nov 2022 21:48:17 +0000 Subject: [PATCH 181/999] Fix: Typ in docstrings --- src/naive/date.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 3fa2884406..3734460713 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -122,7 +122,7 @@ impl NaiveWeek { /// A duration in calendar days. /// -/// This is useful becuase when using `Duration` it is possible +/// This is useful because when using `Duration` it is possible /// that adding `Duration::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 `Duration::days(n)` and `Days::new(n)` are equivalent. @@ -2189,7 +2189,7 @@ mod tests { None ); - // sub with months exceeindg `i32::MIN` + // sub with months exceeding `i32::MIN` assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3) .unwrap() From 2f14b0c870dc3837157b10206bb8cf6ba37ee84b Mon Sep 17 00:00:00 2001 From: Brennan Vincent Date: Tue, 13 Dec 2022 09:06:36 -0500 Subject: [PATCH 182/999] Test that `from_timestamp_millis` corresponds to `from_timestamp_opt` --- src/naive/datetime/tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 3df475b9c1..f026643ae1 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -29,6 +29,16 @@ fn test_datetime_from_timestamp_millis() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert!(naive_datetime.is_none()); } + + // Test that the result of `from_timestamp_millis` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test { + assert_eq!( + NaiveDateTime::from_timestamp_millis(secs * 1000), + NaiveDateTime::from_timestamp_opt(secs, 0) + ); + } } #[test] From 918003e7ee7cd5c82b8e9e7e31e8aca2aff267e8 Mon Sep 17 00:00:00 2001 From: Brennan Vincent Date: Tue, 13 Dec 2022 16:08:04 -0500 Subject: [PATCH 183/999] Fix MSRV violation --- src/naive/datetime/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index f026643ae1..806268d817 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -33,7 +33,7 @@ fn test_datetime_from_timestamp_millis() { // Test that the result of `from_timestamp_millis` compares equal to // that of `from_timestamp_opt`. let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; - for secs in secs_test { + for secs in secs_test.iter().cloned() { assert_eq!( NaiveDateTime::from_timestamp_millis(secs * 1000), NaiveDateTime::from_timestamp_opt(secs, 0) From a1591e91f3a6af50e3d642039bbed1042f300d5b Mon Sep 17 00:00:00 2001 From: Brennan Vincent Date: Tue, 13 Dec 2022 14:21:16 -0500 Subject: [PATCH 184/999] Add from_timestamp_micros function --- src/naive/datetime/mod.rs | 103 ++++++++++++++++++++++++++++-------- src/naive/datetime/tests.rs | 41 +++++++++++++- 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 5f0f6b64ef..c34813f10b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -87,6 +87,32 @@ pub struct NaiveDateTime { time: NaiveTime, } +/// The unit of a timestamp expressed in fractions of a second. +/// Currently either milliseconds or microseconds. +/// +/// This is a private type, used in the implementation of +/// [NaiveDateTime::from_timestamp_millis] and [NaiveDateTime::from_timestamp_micros]. +#[derive(Clone, Copy, Debug)] +enum TimestampUnit { + Millis, + Micros, +} + +impl TimestampUnit { + fn per_second(self) -> u32 { + match self { + TimestampUnit::Millis => 1_000, + TimestampUnit::Micros => 1_000_000, + } + } + fn nanos_per(self) -> u32 { + match self { + TimestampUnit::Millis => 1_000_000, + TimestampUnit::Micros => 1_000, + } + } +} + impl NaiveDateTime { /// Makes a new `NaiveDateTime` from date and time components. /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) @@ -152,28 +178,33 @@ impl NaiveDateTime { /// ``` #[inline] pub fn from_timestamp_millis(millis: i64) -> Option { - let (secs, subsec_millis) = (millis / 1000, millis % 1000); + Self::from_timestamp_unit(millis, TimestampUnit::Millis) + } - match subsec_millis.cmp(&0) { - Ordering::Less => { - // in the case where our subsec part is negative, then we are actually in the earlier second - // hence we subtract one from the seconds part, and we then add a whole second worth of nanos - // to our nanos part. Due to the use of u32 datatype, it is more convenient to subtract - // the absolute value of the subsec nanos from a whole second worth of nanos - let nsecs = u32::try_from(subsec_millis.abs()).ok()? * NANOS_IN_MILLISECOND; - NaiveDateTime::from_timestamp_opt( - secs.checked_sub(1)?, - NANOS_IN_SECOND.checked_sub(nsecs)?, - ) - } - Ordering::Equal => NaiveDateTime::from_timestamp_opt(secs, 0), - Ordering::Greater => { - // convert the subsec millis into nanosecond scale so they can be supplied - // as the nanoseconds parameter - let nsecs = u32::try_from(subsec_millis).ok()? * NANOS_IN_MILLISECOND; - NaiveDateTime::from_timestamp_opt(secs, nsecs) - } - } + /// Creates a new [NaiveDateTime] from microseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// Returns `None` on an out-of-range number of microseconds. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDateTime; + /// let timestamp_micros: i64 = 1662921288000000; //Sunday, September 11, 2022 6:34:48 PM + /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_micros: i64 = -2208936075000000; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); + /// ``` + #[inline] + pub fn from_timestamp_micros(micros: i64) -> Option { + Self::from_timestamp_unit(micros, TimestampUnit::Micros) } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, @@ -866,6 +897,36 @@ impl NaiveDateTime { pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; /// The maximum possible `NaiveDateTime`. pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; + + /// Creates a new [NaiveDateTime] from milliseconds or microseconds since the UNIX epoch. + /// + /// This is a private function used by [from_timestamp_millis] and [from_timestamp_micros]. + #[inline] + fn from_timestamp_unit(value: i64, unit: TimestampUnit) -> Option { + let (secs, subsecs) = + (value / i64::from(unit.per_second()), value % i64::from(unit.per_second())); + + match subsecs.cmp(&0) { + Ordering::Less => { + // in the case where our subsec part is negative, then we are actually in the earlier second + // hence we subtract one from the seconds part, and we then add a whole second worth of nanos + // to our nanos part. Due to the use of u32 datatype, it is more convenient to subtract + // the absolute value of the subsec nanos from a whole second worth of nanos + let nsecs = u32::try_from(subsecs.abs()).ok()? * unit.nanos_per(); + NaiveDateTime::from_timestamp_opt( + secs.checked_sub(1)?, + NANOS_IN_SECOND.checked_sub(nsecs)?, + ) + } + Ordering::Equal => NaiveDateTime::from_timestamp_opt(secs, 0), + Ordering::Greater => { + // convert the subsec millis into nanosecond scale so they can be supplied + // as the nanoseconds parameter + let nsecs = u32::try_from(subsecs).ok()? * unit.nanos_per(); + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + } + } } impl Datelike for NaiveDateTime { diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 806268d817..202bdb34d3 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -17,7 +17,7 @@ fn test_datetime_from_timestamp_millis() { (2034061609000, "2034-06-16 09:06:49.000000000"), ]; - for (timestamp_millis, formatted) in valid_map.iter().cloned() { + for (timestamp_millis, formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); @@ -25,7 +25,7 @@ fn test_datetime_from_timestamp_millis() { let invalid = [i64::MAX, i64::MIN]; - for timestamp_millis in invalid.iter().cloned() { + for timestamp_millis in invalid.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert!(naive_datetime.is_none()); } @@ -41,6 +41,43 @@ fn test_datetime_from_timestamp_millis() { } } +#[test] +fn test_datetime_from_timestamp_micros() { + let valid_map = [ + (1662921288000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456, "2022-09-11 18:34:48.123456000"), + (1662921287890000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_micros, formatted) in valid_map.iter().copied() { + let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); + assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_micros in invalid.iter().copied() { + let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); + assert!(naive_datetime.is_none()); + } + + // Test that the result of `from_timestamp_micros` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + NaiveDateTime::from_timestamp_micros(secs * 1_000_000), + NaiveDateTime::from_timestamp_opt(secs, 0) + ); + } +} + #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); From e02cb3e3234470464447abf58b6ef6edff4e6a77 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 15 Dec 2022 09:59:32 +0100 Subject: [PATCH 185/999] Skip unsound version of iana-time-zone --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9c826c7c80..1a5c0d9d2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} -iana-time-zone = { version = "0.1.44", optional = true, features = ["fallback"] } +iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] From a05e7aaedeef5faaa3e2aa487f4e875056d5edcf Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 15 Dec 2022 09:59:48 +0100 Subject: [PATCH 186/999] Setup cargo-deny in CI --- .github/workflows/lint.yml | 7 +++++++ deny.toml | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 deny.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5ef8d54a77..e299586ef8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,6 +9,7 @@ on: - .github/** - .ci/** - Cargo.toml + - deny.toml jobs: lint: @@ -22,3 +23,9 @@ jobs: - run: cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive env: RUSTFLAGS: "-Dwarnings" + + cargo-deny: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000000..13b9fac5f7 --- /dev/null +++ b/deny.toml @@ -0,0 +1,13 @@ +[licenses] +allow-osi-fsf-free = "either" +copyleft = "deny" + +[advisories] +ignore = [ + "RUSTSEC-2020-0071", # time 0.1, doesn't affect the API we use + "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) + "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility +] +unmaintained = "deny" +unsound = "deny" +yanked = "deny" From 7c5e631fde8ff9344089006e63c2d28c8a18ae56 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 19 Dec 2022 09:46:34 +0100 Subject: [PATCH 187/999] Apply clippy suggestions for Rust 1.66 --- src/offset/utc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 812d7b062e..ee3e70a0bc 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -68,8 +68,7 @@ impl Utc { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); let naive = - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos() as u32) - .unwrap(); + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap(); DateTime::from_utc(naive, Utc) } From b244b83a649391db07106d0afbbf30bd4aa1ecec Mon Sep 17 00:00:00 2001 From: Greg Leonard <45019882+greg-el@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:46:12 +0000 Subject: [PATCH 188/999] Update deprecated methods in `from_utc` example `from_timestamp` and `Utc.timestamp` are deprecated, and have been replaced with `from_timestamp_opt` and `Utc.timestamp_opt` respectively, with unwrapping. --- src/datetime/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 05d0458e84..00450ce9c7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -107,8 +107,8 @@ impl DateTime { /// ``` /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; /// - /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - /// assert_eq!(Utc.timestamp(61, 0), dt); + /// 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. From cbd2c1f7269bdd4fd3b46cf795373a3023555000 Mon Sep 17 00:00:00 2001 From: Kurtis Nusbaum Date: Tue, 31 Jan 2023 21:06:14 +0000 Subject: [PATCH 189/999] point docs at non-deprecated functions --- src/offset/fixed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 0a8b26ebfc..4d336b9db6 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -20,8 +20,8 @@ use crate::Timelike; /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on a `FixedOffset` struct is the preferred way to construct -/// `DateTime` instances. See the [`east`](#method.east) and -/// [`west`](#method.west) methods for examples. +/// `DateTime` instances. See the [`east_opt`](#method.east_opt) and +/// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct FixedOffset { From a892fcc7c26a2a0e626cb027d3079419d2bbd8a0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 30 Jan 2023 16:54:44 +0100 Subject: [PATCH 190/999] Apply clippy suggestions for Rust 1.67 --- src/format/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 5e706db998..cfbc54ab7c 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -500,12 +500,12 @@ impl Locales { /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -pub fn format_item<'a>( +pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - item: &Item<'a>, + item: &Item<'_>, ) -> fmt::Result { let mut result = String::new(); format_inner(&mut result, date, time, off, item, None)?; @@ -513,12 +513,12 @@ pub fn format_item<'a>( } #[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner<'a>( +fn format_inner( result: &mut String, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - item: &Item<'a>, + item: &Item<'_>, locale: Option, ) -> fmt::Result { let locale = Locales::new(locale); @@ -674,7 +674,7 @@ fn format_inner<'a>( let nano = t.nanosecond() % 1_000_000_000; write!(result, "{:09}", nano) }), - TimezoneName => off.map(|&(ref name, _)| { + TimezoneName => off.map(|(name, _)| { result.push_str(name); Ok(()) }), From 366e7ea0c56c5a29d4bd8557ef3fa4dd4b272b6f Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:29:57 -0500 Subject: [PATCH 191/999] Add sign requirements for years to docs (cherry picked from commit 7dfd8ca8e6b11c79a9035ba3b3ec03117e7999a9) --- src/format/strftime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 6338194366..aafe01f736 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -11,7 +11,7 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).| | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | From 64c4c0149d2765f401e060656a20d8b86a60ea6a Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:59:17 -0500 Subject: [PATCH 192/999] Add (+/-) for consistency with strftime.rs (cherry picked from commit a2fb1212f383e6ab59c0dec7854c173e8e63ef34) --- src/format/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index cfbc54ab7c..e9d2d2f9c3 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -99,7 +99,7 @@ pub enum Pad { #[derive(Clone, PartialEq, Eq, Debug)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). - /// May accept years before 1 BCE or after 9999 CE, given an initial sign. + /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). Year, /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year. YearDiv100, From e63b7297c7793f73449ec3053ee26b976656fe4d Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 10:17:47 -0500 Subject: [PATCH 193/999] Add example of date conversion after 9999 CE (cherry picked from commit 05b865deffff478facd737a540aa62d1f061acb6) --- src/naive/datetime/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c34813f10b..68a1be20e9 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -308,6 +308,16 @@ impl NaiveDateTime { /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); /// ``` + /// + /// Years before 1 BCE or after 9999 CE, require an initial sign + /// + ///``` + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S"; + /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); + /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); + ///``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; From cc64f84ac85a3927c7be495f1e1dcf24e6399355 Mon Sep 17 00:00:00 2001 From: Bruce Guenter Date: Thu, 20 Oct 2022 11:47:02 -0600 Subject: [PATCH 194/999] derive `Hash` for most pub types that also derive `PartialEq` --- src/datetime/mod.rs | 2 +- src/format/mod.rs | 35 +++++++++++------------------------ src/format/parsed.rs | 2 +- src/naive/internals.rs | 2 +- src/naive/isoweek.rs | 2 +- src/offset/utc.rs | 2 +- src/oldtime.rs | 2 +- 7 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 00450ce9c7..38416cb6fa 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -52,7 +52,7 @@ mod tests; /// future, so exhaustive matching in external code is not recommended. /// /// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, diff --git a/src/format/mod.rs b/src/format/mod.rs index e9d2d2f9c3..d5c4212a40 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -69,11 +69,11 @@ pub use strftime::StrftimeItems; struct Locale; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] enum Void {} /// Padding characters for numeric items. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Pad { /// No padding. None, @@ -96,7 +96,7 @@ pub enum Pad { /// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). @@ -152,24 +152,11 @@ pub enum Numeric { } /// An opaque type representing numeric item types for internal uses only. +#[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } -impl Clone for InternalNumeric { - fn clone(&self) -> Self { - match self._dummy {} - } -} - -impl PartialEq for InternalNumeric { - fn eq(&self, _other: &InternalNumeric) -> bool { - match self._dummy {} - } -} - -impl Eq for InternalNumeric {} - impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") @@ -180,7 +167,7 @@ impl fmt::Debug for InternalNumeric { /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. /// @@ -264,12 +251,12 @@ pub enum Fixed { } /// An opaque type representing fixed-format item types for internal uses only. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InternalFixed { val: InternalInternal, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -289,7 +276,7 @@ enum InternalInternal { } #[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Colons { None, Single, @@ -298,7 +285,7 @@ enum Colons { } /// A single formatting item. This is used for both formatting and parsing. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), @@ -358,7 +345,7 @@ macro_rules! internal_fix { } /// An error from the `parse` function. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ParseError(ParseErrorKind); impl ParseError { @@ -369,7 +356,7 @@ impl ParseError { } /// The category of parse error -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 54679ccda1..fc4c4540bd 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -22,7 +22,7 @@ use crate::{Datelike, Timelike}; /// /// - `to_*` methods try to make a concrete date and time value out of set fields. /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[derive(Clone, PartialEq, Eq, Debug, Default)] +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { /// Year. /// diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1b113d51d3..cd87973712 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -35,7 +35,7 @@ pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; /// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year /// (simplifies the day of week calculation from the 1-based ordinal). #[allow(unreachable_pub)] // public as an alias for benchmarks only -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] pub struct YearFlags(pub(super) u8); pub(super) const A: YearFlags = YearFlags(0o15); diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 109535f547..501b08c769 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. diff --git a/src/offset/utc.rs b/src/offset/utc.rs index ee3e70a0bc..cfed754b2f 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -40,7 +40,7 @@ use crate::{Date, DateTime}; /// assert_eq!(Utc.timestamp(61, 0), dt); /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; diff --git a/src/oldtime.rs b/src/oldtime.rs index f935e9a4b1..f604524899 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -50,7 +50,7 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// /// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Duration { secs: i64, From 90c7e55738cab4fe9bb946ce2a0a913cf2677c9c Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 13:33:19 +0100 Subject: [PATCH 195/999] Run CI on changes to any file Currently CI is only run on changes to specific files. The list of these files incomplete and would likely get outdated again if it were expanded. Exluding specific files would be a shorter, more stable list. However, the changelog is only kept for posterity and the readme has seen all of two commits in the past three years that wouldn't have triggered CI by changing other files as well, so don't bother and just run CI for any changed file. --- .github/workflows/lint.yml | 6 ------ .github/workflows/test.yml | 5 ----- 2 files changed, 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e299586ef8..dc3394c87c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,12 +4,6 @@ on: push: branches: [main, 0.4.x] pull_request: - paths: - - "**.rs" - - .github/** - - .ci/** - - Cargo.toml - - deny.toml jobs: lint: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63e7c81c5c..ab968aac2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,11 +4,6 @@ on: push: branches: [main, 0.4.x] pull_request: - paths: - - "**.rs" - - .github/** - - .ci/** - - Cargo.toml jobs: timezones_linux: From d1043f39c8901bba8cf89081dcb2959aec0ddcfb Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:31:13 +0100 Subject: [PATCH 196/999] Add chrono-fuzz to CI Ensures future changes to break it again. --- .github/workflows/lint.yml | 8 ++++++-- .github/workflows/test.yml | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dc3394c87c..41145a30a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,8 +13,12 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v1 - - run: cargo fmt -- --check --color=always - - run: cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + - run: | + cargo fmt --check -- --color=always + cargo fmt --check --manifest-path fuzz/Cargo.toml + - run: | + cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings env: RUSTFLAGS: "-Dwarnings" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab968aac2f..851ef5cec7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,7 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v1 + - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests which are run elsewhere - run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always From cd0e3b008c1ea5c6835709ff72a1b76209f3831b Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:36:30 +0100 Subject: [PATCH 197/999] chrono-fuzz: Update libfuzzer-sys dependency from 0.3 to 0.4 --- fuzz/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bcbc3aad25..d15c9a9f7f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" +libfuzzer-sys = "0.4" [dependencies.chrono] path = ".." From f9f3c7857d31c1c2341a1ddee0e9447f81a61465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 22 Jan 2023 20:23:50 +0100 Subject: [PATCH 198/999] Fix panic in DateTime::checked_add_days This is a backport of #941, except it needs to work around the fact that we can't modify the `time` crate. --- src/naive/date.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 3734460713..79d0a98baa 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -646,6 +646,10 @@ impl NaiveDate { /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(1000000000000)), + /// None + /// ); /// ``` pub fn checked_add_days(self, days: Days) -> Option { if days.0 == 0 { @@ -665,6 +669,10 @@ impl NaiveDate { /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)), + /// None + /// ); /// ``` pub fn checked_sub_days(self, days: Days) -> Option { if days.0 == 0 { @@ -675,7 +683,11 @@ impl NaiveDate { } fn diff_days(self, days: i64) -> Option { - self.checked_add_signed(Duration::days(days)) + let secs = days.checked_mul(86400)?; // 86400 seconds in one day + if secs >= core::i64::MAX / 1000 || secs <= core::i64::MIN / 1000 { + return None; // See the `time` 0.1 crate. Outside these bounds, `Duration::seconds` will panic + } + self.checked_add_signed(Duration::seconds(secs)) } /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. From a9b1ec412a6224020a2e9664a45974dcf71fdace Mon Sep 17 00:00:00 2001 From: raphaelroosz <121112446+raphaelroosz@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:31:56 +0100 Subject: [PATCH 199/999] fix ordinal week calculation * math is 0 based while ordinal is 1 based => fix as 1 based logic * add extensive testing against the "date" command format * format: test sample instead of every day * 2007 starts with saturday * Last day of the year is thus the 52 on Monday weekly calendar, 53 on Sunday weekly calendar. * update %U expected value in test * Was the goal was to have a different value than with %W at next line ? another date to pick ? * update cfg("unix") into cfg(target_os = "linux") * format tests/dateutils.rs --- src/format/mod.rs | 4 +-- src/format/strftime.rs | 2 +- src/naive/date.rs | 2 +- tests/dateutils.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d5c4212a40..e3b9b51344 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -521,10 +521,10 @@ fn format_inner( use self::Numeric::*; let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7 + (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 6) / 7 }; let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7 + (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 6) / 7 }; let (width, v) = match *spec { diff --git a/src/format/strftime.rs b/src/format/strftime.rs index aafe01f736..dcaabe49f2 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -584,7 +584,7 @@ fn test_strftime_docs() { assert_eq!(dt.format("%A").to_string(), "Sunday"); assert_eq!(dt.format("%w").to_string(), "0"); assert_eq!(dt.format("%u").to_string(), "7"); - assert_eq!(dt.format("%U").to_string(), "28"); + assert_eq!(dt.format("%U").to_string(), "27"); assert_eq!(dt.format("%W").to_string(), "27"); assert_eq!(dt.format("%G").to_string(), "2001"); assert_eq!(dt.format("%g").to_string(), "01"); diff --git a/src/naive/date.rs b/src/naive/date.rs index 79d0a98baa..e3cc9b6c6a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2869,7 +2869,7 @@ mod tests { // corner cases assert_eq!( NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), - "2008,08,53,53,01" + "2008,08,52,53,01" ); assert_eq!( NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 130649a71f..dec6bfe117 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -74,3 +74,58 @@ fn try_verify_against_date_command() { date += chrono::Duration::hours(1); } } + +#[cfg(target_os = "linux")] +fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) { + let required_format = + "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; + // a%a - depends from localization + // A%A - depends from localization + // b%b - depends from localization + // B%B - depends from localization + // h%h - depends from localization + // c%c - depends from localization + // p%p - depends from localization + // r%r - depends from localization + // x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D + // Z%Z - too many ways to represent it, will most likely fail + + let output = process::Command::new(path) + .arg("-d") + .arg(format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + )) + .arg(format!("+{}", required_format)) + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + let ldt = Local + .from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap()) + .unwrap(); + let formated_date = format!("{}\n", ldt.format(required_format)); + assert_eq!(date_command_str, formated_date); +} + +#[test] +#[cfg(target_os = "linux")] +fn try_verify_against_date_command_format() { + let date_path = "/usr/bin/date"; + + if !path::Path::new(date_path).exists() { + // date command not found, skipping + return; + } + let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); + while date.year() < 2008 { + verify_against_date_command_format_local(date_path, date); + date += chrono::Duration::days(55); + } +} From 8197700ccdbb77a355e47ef8f4d9720ef41ff564 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 14 Feb 2023 11:41:14 +0000 Subject: [PATCH 200/999] apply same fix to parsing and add failing test cases as per issue #961 --- src/format/parsed.rs | 4 ++-- src/naive/date.rs | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index fc4c4540bd..eb697e12bf 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -380,8 +380,8 @@ impl Parsed { let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 7) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 7) / 7; + let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 6) / 7; + let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 6) / 7; self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon diff --git a/src/naive/date.rs b/src/naive/date.rs index e3cc9b6c6a..eccbcf2ff4 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2831,6 +2831,16 @@ mod tests { assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); } #[test] From cf2a2f95f7030860b0eda2b78eff968f1d7b4228 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 16 Feb 2023 12:33:39 +0000 Subject: [PATCH 201/999] factor calculations to weeks_from function and add tests tests for weeks_from and num_days_from fix array iter MSRV issue --- src/format/mod.rs | 8 +--- src/format/parsed.rs | 5 +-- src/naive/date.rs | 66 ++++++++++++++++++++++++++++++++ src/weekday.rs | 89 ++++++++++++++++++++++++++------------------ 4 files changed, 123 insertions(+), 45 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index e3b9b51344..c6b8eaee09 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -520,12 +520,8 @@ fn format_inner( Item::Numeric(ref spec, ref pad) => { use self::Numeric::*; - let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 6) / 7 - }; - let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 6) / 7 - }; + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index eb697e12bf..6cc29e9d40 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -379,9 +379,8 @@ impl Parsed { // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); - let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 6) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 6) / 7; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon diff --git a/src/naive/date.rs b/src/naive/date.rs index eccbcf2ff4..46c2092513 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -236,6 +236,9 @@ fn test_date_bounds() { } impl NaiveDate { + pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { + (self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7 + } /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. fn from_of(year: i32, of: Of) -> Option { if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() { @@ -2928,4 +2931,67 @@ mod tests { assert!(days.contains(&date)); } } + + #[test] + fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!([52, 53] + .contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } + } } diff --git a/src/weekday.rs b/src/weekday.rs index bd30e1934d..c12dd01c64 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -73,15 +73,7 @@ impl Weekday { /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } + self.num_days_from(Weekday::Mon) + 1 } /// Returns a day-of-week number starting from Sunday = 1. @@ -91,15 +83,7 @@ impl Weekday { /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } + self.num_days_from(Weekday::Sun) + 1 } /// Returns a day-of-week number starting from Monday = 0. @@ -109,15 +93,7 @@ impl Weekday { /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } + self.num_days_from(Weekday::Mon) } /// Returns a day-of-week number starting from Sunday = 0. @@ -127,15 +103,17 @@ impl Weekday { /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } + self.num_days_from(Weekday::Sun) + } + + /// Returns a day-of-week number starting from the parameter `day` (D) = 0. + /// + /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + #[inline] + pub(crate) fn num_days_from(&self, day: Weekday) -> u32 { + (*self as u32 + 7 - day as u32) % 7 } } @@ -208,6 +186,45 @@ impl fmt::Debug for ParseWeekdayError { } } +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::Weekday; + + #[test] + fn test_num_days_from() { + for i in 0..7 { + let base_day = Weekday::from_u64(i).unwrap(); + + assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun)); + + assert_eq!(base_day.num_days_from(base_day), 0); + + assert_eq!(base_day.num_days_from(base_day.pred()), 1); + assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!( + base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()), + 6 + ); + + assert_eq!(base_day.num_days_from(base_day.succ()), 6); + assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!( + base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()), + 1 + ); + } + } +} + // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")] From fb2f2596f56b690ddd5fc5e473fc8380418802a8 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Tue, 7 Mar 2023 14:14:36 +0100 Subject: [PATCH 202/999] Make iana-time-zone a target specific dependency Currently, iana-tiime-zone is only used on cfg(unix). This crate, and its windows code in particular, contains a lot of unsafe, so it seems prudent to limit its scope to where it is actually needed. --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1a5c0d9d2b..14ae4d0322 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} -iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] @@ -47,6 +46,9 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } +[target.'cfg(unix)'.dependencies] +iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } + [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } From f5c5ac452dc7095c6acec12a7e5278194132bc06 Mon Sep 17 00:00:00 2001 From: Tormod Gjeitnes Hellen Date: Wed, 8 Mar 2023 18:23:56 +0100 Subject: [PATCH 203/999] Make eligible functions const. --- src/format/mod.rs | 2 +- src/month.rs | 2 +- src/naive/date.rs | 12 ++++---- src/naive/datetime/mod.rs | 6 ++-- src/naive/internals.rs | 14 +++++----- src/naive/isoweek.rs | 6 ++-- src/offset/fixed.rs | 4 +-- src/offset/local/tz_info/parser.rs | 4 +-- src/offset/local/tz_info/timezone.rs | 12 ++++---- src/oldtime.rs | 42 ++++++++++++++-------------- src/weekday.rs | 10 +++---- 11 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index c6b8eaee09..c05ba4d044 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -350,7 +350,7 @@ pub struct ParseError(ParseErrorKind); impl ParseError { /// The category of parse error - pub fn kind(&self) -> ParseErrorKind { + pub const fn kind(&self) -> ParseErrorKind { self.0 } } diff --git a/src/month.rs b/src/month.rs index f444dc0270..46f09d0fb8 100644 --- a/src/month.rs +++ b/src/month.rs @@ -197,7 +197,7 @@ pub struct Months(pub(crate) u32); impl Months { /// Construct a new `Months` from a number of months - pub fn new(num: u32) -> Self { + pub const fn new(num: u32) -> Self { Self(num) } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 46c2092513..64af978f3d 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -131,7 +131,7 @@ pub struct Days(pub(crate) u64); impl Days { /// Construct a new `Days` from a number of days - pub fn new(num: u64) -> Self { + pub const fn new(num: u64) -> Self { Self(num) } } @@ -708,7 +708,7 @@ impl NaiveDate { /// assert_eq!(dt.time(), t); /// ``` #[inline] - pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime { + pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { NaiveDateTime::new(*self, time) } @@ -898,7 +898,7 @@ impl NaiveDate { /// Returns the packed ordinal-flags. #[inline] - fn of(&self) -> Of { + const fn of(&self) -> Of { Of((self.ymdf & 0b1_1111_1111_1111) as u32) } @@ -1221,7 +1221,7 @@ impl NaiveDate { /// } /// ``` #[inline] - pub fn iter_days(&self) -> NaiveDateDaysIterator { + pub const fn iter_days(&self) -> NaiveDateDaysIterator { NaiveDateDaysIterator { value: *self } } @@ -1252,14 +1252,14 @@ impl NaiveDate { /// } /// ``` #[inline] - pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { + pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator { NaiveDateWeeksIterator { value: *self } } /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] /// specified. #[inline] - pub fn week(&self, start: Weekday) -> NaiveWeek { + pub const fn week(&self, start: Weekday) -> NaiveWeek { NaiveWeek { date: *self, start } } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 68a1be20e9..ec0d842c06 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -131,7 +131,7 @@ impl NaiveDateTime { /// assert_eq!(dt.time(), t); /// ``` #[inline] - pub fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { + pub const fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { NaiveDateTime { date, time } } @@ -335,7 +335,7 @@ impl NaiveDateTime { /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); /// ``` #[inline] - pub fn date(&self) -> NaiveDate { + pub const fn date(&self) -> NaiveDate { self.date } @@ -350,7 +350,7 @@ impl NaiveDateTime { /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); /// ``` #[inline] - pub fn time(&self) -> NaiveTime { + pub const fn time(&self) -> NaiveTime { self.time } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index cd87973712..05305b506f 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -141,7 +141,7 @@ impl YearFlags { } #[inline] - pub(super) fn nisoweeks(&self) -> u32 { + pub(super) const fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } @@ -294,7 +294,7 @@ impl Of { } #[inline] - pub(super) fn ordinal(&self) -> u32 { + pub(super) const fn ordinal(&self) -> u32 { let Of(of) = *self; of >> 4 } @@ -310,7 +310,7 @@ impl Of { } #[inline] - pub(super) fn flags(&self) -> YearFlags { + pub(super) const fn flags(&self) -> YearFlags { let Of(of) = *self; YearFlags((of & 0b1111) as u8) } @@ -336,13 +336,13 @@ impl Of { } #[inline] - pub(super) fn succ(&self) -> Of { + pub(super) const fn succ(&self) -> Of { let Of(of) = *self; Of(of + (1 << 4)) } #[inline] - pub(super) fn pred(&self) -> Of { + pub(super) const fn pred(&self) -> Of { let Of(of) = *self; Of(of - (1 << 4)) } @@ -398,7 +398,7 @@ impl Mdf { } #[inline] - pub(super) fn month(&self) -> u32 { + pub(super) const fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } @@ -414,7 +414,7 @@ impl Mdf { } #[inline] - pub(super) fn day(&self) -> u32 { + pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 501b08c769..6a4fcfd110 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -71,7 +71,7 @@ impl IsoWeek { /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` #[inline] - pub fn year(&self) -> i32 { + pub const fn year(&self) -> i32 { self.ywf >> 10 } @@ -88,7 +88,7 @@ impl IsoWeek { /// assert_eq!(d.iso_week().week(), 15); /// ``` #[inline] - pub fn week(&self) -> u32 { + pub const fn week(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 } @@ -105,7 +105,7 @@ impl IsoWeek { /// assert_eq!(d.iso_week().week0(), 14); /// ``` #[inline] - pub fn week0(&self) -> u32 { + pub const fn week0(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 - 1 } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 4d336b9db6..0989dfa5ba 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -93,13 +93,13 @@ impl FixedOffset { /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] - pub fn local_minus_utc(&self) -> i32 { + pub const fn local_minus_utc(&self) -> i32 { self.local_minus_utc } /// Returns the number of seconds to add to convert from the local time to UTC. #[inline] - pub fn utc_minus_local(&self) -> i32 { + pub const fn utc_minus_local(&self) -> i32 { -self.local_minus_utc } } diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 77f8e481b5..5652a0ea95 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -224,7 +224,7 @@ pub(crate) struct Cursor<'a> { impl<'a> Cursor<'a> { /// Construct a new `Cursor` from remaining data - pub(crate) fn new(remaining: &'a [u8]) -> Self { + pub(crate) const fn new(remaining: &'a [u8]) -> Self { Self { remaining, read_count: 0 } } @@ -233,7 +233,7 @@ impl<'a> Cursor<'a> { } /// Returns remaining data - pub(crate) fn remaining(&self) -> &'a [u8] { + pub(crate) const fn remaining(&self) -> &'a [u8] { self.remaining } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index ae6fb5f848..8572825a89 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -438,12 +438,12 @@ pub(super) struct Transition { impl Transition { /// Construct a TZif file transition - pub(super) fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { + pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { Self { unix_leap_time, local_time_type_index } } /// Returns Unix leap time - fn unix_leap_time(&self) -> i64 { + const fn unix_leap_time(&self) -> i64 { self.unix_leap_time } } @@ -459,12 +459,12 @@ pub(super) struct LeapSecond { impl LeapSecond { /// Construct a TZif file leap second - pub(super) fn new(unix_leap_time: i64, correction: i32) -> Self { + pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self { Self { unix_leap_time, correction } } /// Returns Unix leap time - fn unix_leap_time(&self) -> i64 { + const fn unix_leap_time(&self) -> i64 { self.unix_leap_time } } @@ -572,12 +572,12 @@ impl LocalTimeType { } /// Returns offset from UTC in seconds - pub(crate) fn offset(&self) -> i32 { + pub(crate) const fn offset(&self) -> i32 { self.ut_offset } /// Returns daylight saving time indicator - pub(super) fn is_dst(&self) -> bool { + pub(super) const fn is_dst(&self) -> bool { self.is_dst } diff --git a/src/oldtime.rs b/src/oldtime.rs index f604524899..8e2b3d2c09 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -120,7 +120,7 @@ impl Duration { /// Makes a new `Duration` with given number of milliseconds. #[inline] - pub fn milliseconds(milliseconds: i64) -> Duration { + pub const fn milliseconds(milliseconds: i64) -> Duration { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); let nanos = millis as i32 * NANOS_PER_MILLI; Duration { secs: secs, nanos: nanos } @@ -128,7 +128,7 @@ impl Duration { /// Makes a new `Duration` with given number of microseconds. #[inline] - pub fn microseconds(microseconds: i64) -> Duration { + pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); let nanos = micros as i32 * NANOS_PER_MICRO; Duration { secs: secs, nanos: nanos } @@ -136,36 +136,36 @@ impl Duration { /// Makes a new `Duration` with given number of nanoseconds. #[inline] - pub fn nanoseconds(nanos: i64) -> Duration { + pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); Duration { secs: secs, nanos: nanos as i32 } } /// Returns the total number of whole weeks in the duration. #[inline] - pub fn num_weeks(&self) -> i64 { + pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } /// Returns the total number of whole days in the duration. - pub fn num_days(&self) -> i64 { + pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } /// Returns the total number of whole hours in the duration. #[inline] - pub fn num_hours(&self) -> i64 { + pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } /// Returns the total number of whole minutes in the duration. #[inline] - pub fn num_minutes(&self) -> i64 { + pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } /// Returns the total number of whole seconds in the duration. - pub fn num_seconds(&self) -> i64 { + pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. if self.secs < 0 && self.nanos > 0 { self.secs + 1 @@ -177,7 +177,7 @@ impl Duration { /// Returns the number of nanoseconds such that /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. - fn nanos_mod_sec(&self) -> i32 { + const fn nanos_mod_sec(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { @@ -186,7 +186,7 @@ impl Duration { } /// Returns the total number of whole milliseconds in the duration, - pub fn num_milliseconds(&self) -> i64 { + pub const fn num_milliseconds(&self) -> i64 { // A proper Duration will not overflow, because MIN and MAX are defined // such that the range is exactly i64 milliseconds. let secs_part = self.num_seconds() * MILLIS_PER_SEC; @@ -196,7 +196,7 @@ impl Duration { /// Returns the total number of whole microseconds in the duration, /// or `None` on overflow (exceeding 2^63 microseconds in either direction). - pub fn num_microseconds(&self) -> Option { + pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; secs_part.checked_add(nanos_part as i64) @@ -204,7 +204,7 @@ impl Duration { /// Returns the total number of whole nanoseconds in the duration, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). - pub fn num_nanoseconds(&self) -> Option { + pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); let nanos_part = self.nanos_mod_sec(); secs_part.checked_add(nanos_part as i64) @@ -248,7 +248,7 @@ impl Duration { /// Returns the duration as an absolute (non-negative) value. #[inline] - pub fn abs(&self) -> Duration { + pub const fn abs(&self) -> Duration { if self.secs < 0 && self.nanos != 0 { Duration { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } } else { @@ -258,25 +258,25 @@ impl Duration { /// The minimum possible `Duration`: `i64::MIN` milliseconds. #[inline] - pub fn min_value() -> Duration { + pub const fn min_value() -> Duration { MIN } /// The maximum possible `Duration`: `i64::MAX` milliseconds. #[inline] - pub fn max_value() -> Duration { + pub const fn max_value() -> Duration { MAX } /// A duration where the stored seconds and nanoseconds are equal to zero. #[inline] - pub fn zero() -> Duration { + pub const fn zero() -> Duration { Duration { secs: 0, nanos: 0 } } /// Returns `true` if the duration equals `Duration::zero()`. #[inline] - pub fn is_zero(&self) -> bool { + pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 } @@ -457,12 +457,12 @@ impl Error for OutOfRangeError { // Copied from libnum #[inline] -fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { +const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { (div_floor_64(this, other), mod_floor_64(this, other)) } #[inline] -fn div_floor_64(this: i64, other: i64) -> i64 { +const fn div_floor_64(this: i64, other: i64) -> i64 { match div_rem_64(this, other) { (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, (d, _) => d, @@ -470,7 +470,7 @@ fn div_floor_64(this: i64, other: i64) -> i64 { } #[inline] -fn mod_floor_64(this: i64, other: i64) -> i64 { +const fn mod_floor_64(this: i64, other: i64) -> i64 { match this % other { r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, r => r, @@ -478,7 +478,7 @@ fn mod_floor_64(this: i64, other: i64) -> i64 { } #[inline] -fn div_rem_64(this: i64, other: i64) -> (i64, i64) { +const fn div_rem_64(this: i64, other: i64) -> (i64, i64) { (this / other, this % other) } diff --git a/src/weekday.rs b/src/weekday.rs index c12dd01c64..72e384673f 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -72,7 +72,7 @@ impl Weekday { /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] - pub fn number_from_monday(&self) -> u32 { + pub const fn number_from_monday(&self) -> u32 { self.num_days_from(Weekday::Mon) + 1 } @@ -82,7 +82,7 @@ impl Weekday { /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] - pub fn number_from_sunday(&self) -> u32 { + pub const fn number_from_sunday(&self) -> u32 { self.num_days_from(Weekday::Sun) + 1 } @@ -92,7 +92,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] - pub fn num_days_from_monday(&self) -> u32 { + pub const fn num_days_from_monday(&self) -> u32 { self.num_days_from(Weekday::Mon) } @@ -102,7 +102,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] - pub fn num_days_from_sunday(&self) -> u32 { + pub const fn num_days_from_sunday(&self) -> u32 { self.num_days_from(Weekday::Sun) } @@ -112,7 +112,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] - pub(crate) fn num_days_from(&self, day: Weekday) -> u32 { + pub(crate) const fn num_days_from(&self, day: Weekday) -> u32 { (*self as u32 + 7 - day as u32) % 7 } } From 64c5d7793a1a3e6ccbb2acc606b85f95830b7f83 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:53:20 +0100 Subject: [PATCH 204/999] Bump version to 0.4.24 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 14ae4d0322..e69847a44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.23" +version = "0.4.24" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From b1e0963efc9544dee9e5b708e3abfea3e7eaa3d9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:53:58 +0100 Subject: [PATCH 205/999] Bump rust-cache action to v2 --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 41145a30a9..61bf164c8b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: | cargo fmt --check -- --color=always cargo fmt --check --manifest-path fuzz/Cargo.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 851ef5cec7..ffeed66e1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo test --all-features --color=always -- --color=always timezones_other: @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always @@ -45,7 +45,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: 1.38.0 - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests which are run elsewhere - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always @@ -61,7 +61,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests which are run elsewhere - run: cargo test --lib --all-features --color=always -- --color=always @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales no_std: @@ -90,7 +90,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test @@ -105,7 +105,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v1 with: node-version: "12" @@ -114,7 +114,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh wasm-pack --version - run: cargo build --target ${{ matrix.target }} --color=always - + features_check_wasm: strategy: matrix: @@ -126,7 +126,7 @@ jobs: with: targets: wasm32-unknown-unknown - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales cross-targets: @@ -138,7 +138,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: cargo install cross - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} check-docs: From daa86a77d36d74f474913fd3b560a40f1424bd77 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:55:02 +0100 Subject: [PATCH 206/999] Check benchmarks in CI --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffeed66e1c..89754956a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,7 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v2 + - run: cargo check --benches - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests which are run elsewhere - run: cargo test --lib --all-features --color=always -- --color=always From 9d376499b1deee94430937a96c78f8f997c32642 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 16 Mar 2023 10:59:23 +0100 Subject: [PATCH 207/999] Apply suggestions for clippy 1.68 --- src/format/scan.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 263fec556d..68e294c834 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -48,7 +48,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if !(b'0'..=b'9').contains(&c) { + if !c.is_ascii_digit() { if i < min { return Err(INVALID); } else { @@ -79,7 +79,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| ('0'..='9').contains(&c)); + let s = s.trim_left_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } From dc9ea3ab455bf705df7711a0bbdd73e4b7eadc12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B4she=20van=20der=20Sterre?= Date: Wed, 15 Mar 2023 16:46:46 +0100 Subject: [PATCH 208/999] fix IsoWeek so that its flags are always correct This fixes #295. PartialEq and PartialOrd are implemented by directly comparing the internal integer field, which includes the flags value. --- src/naive/isoweek.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 6a4fcfd110..45c2a81ede 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -46,7 +46,8 @@ pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { (year, rawweek) } }; - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) } + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) } } impl IsoWeek { @@ -164,4 +165,38 @@ mod tests { assert_eq!(maxweek.week0(), 0); assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } + + #[test] + fn test_iso_week_equivalence_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_equivalence_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } } From a6e7a3e268c8d7e56695e2acbdff8dc7423a0e80 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 20 Mar 2023 10:37:03 +0100 Subject: [PATCH 209/999] Don't check serde support against our MSRV serde now has a 1.56 MSRV via syn 2. --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89754956a2..1250867787 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,8 +47,8 @@ jobs: toolchain: 1.38.0 - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests which are run elsewhere - - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always - - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always + - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi --color=always -- --color=always + - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi --color=always -- --color=always rust_versions: strategy: From 1f1e2f8ff0e166ffd80ae95218a80b54fe26e003 Mon Sep 17 00:00:00 2001 From: Kevin <46825870+nekevss@users.noreply.github.com> Date: Tue, 21 Mar 2023 05:05:16 -0400 Subject: [PATCH 210/999] Refactor windows module in `Local` (#992) --- Cargo.toml | 2 +- src/datetime/tests.rs | 35 ++++ src/offset/local/windows.rs | 345 ++++++++++++------------------------ 3 files changed, 152 insertions(+), 230 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e69847a44e..8920d6ba21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } +winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi", "sysinfoapi"], optional = true } [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index ebd32cae93..db32a0d625 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -950,3 +950,38 @@ fn test_datetime_sub_assign_local() { assert_eq!(datetime_sub, datetime - Duration::days(i)) } } + +#[test] +#[cfg(target_os = "windows")] +fn test_from_naive_date_time_windows() { + let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let max_year = NaiveDate::from_ymd_opt(30827, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); + + let too_low_year = + NaiveDate::from_ymd_opt(1600, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); + + let too_high_year = NaiveDate::from_ymd_opt(30829, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let _ = Local.from_utc_datetime(&min_year); + let _ = Local.from_utc_datetime(&max_year); + + let _ = Local.from_local_datetime(&min_year); + let _ = Local.from_local_datetime(&max_year); + + let local_too_low = Local.from_local_datetime(&too_low_year); + let local_too_high = Local.from_local_datetime(&too_high_year); + + assert_eq!(local_too_low, LocalResult::None); + assert_eq!(local_too_high, LocalResult::None); + + let err = std::panic::catch_unwind(|| { + Local.from_utc_datetime(&too_low_year); + }); + assert!(err.is_err()); + + let err = std::panic::catch_unwind(|| { + Local.from_utc_datetime(&too_high_year); + }); + assert!(err.is_err()); +} diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index e6415015c1..7d9f4515e9 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -8,271 +8,158 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::io; -use std::mem; -use std::time::{SystemTime, UNIX_EPOCH}; +use core::mem::MaybeUninit; +use std::io::Error; +use std::ptr; +use std::result::Result; -use winapi::shared::minwindef::*; +use winapi::shared::minwindef::FILETIME; use winapi::um::minwinbase::SYSTEMTIME; -use winapi::um::timezoneapi::*; +use winapi::um::sysinfoapi::GetLocalTime; +use winapi::um::timezoneapi::{ + SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, +}; use super::{FixedOffset, Local}; use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; -pub(super) fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - let spec = Timespec { - sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), - }, - nsec: tm.tm_nsec, - }; - - // Adjust for leap seconds - let mut tm = spec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - // #TODO - there should be ambiguous cases, investigate? - LocalResult::Single(tm_to_datetime(tm)) -} - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -fn tm_to_datetime(mut tm: Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - let date = NaiveDate::from_ymd_opt(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) - .unwrap(); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - - let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. +/// This macro calls a Windows API FFI and checks whether the function errored with the provided error_id. If an error returns, +/// the macro will return an `Error::last_os_error()`. /// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - - /// Converts this timespec into the system's local time. - fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm +/// # Safety +/// +/// The provided error ID must align with the provided Windows API, providing the wrong ID could lead to UB. +macro_rules! windows_sys_call { + ($name:ident($($arg:expr),*), $error_id:expr) => { + if $name($($arg),*) == $error_id { + return Err(Error::last_os_error()); + } } } -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[repr(C)] -struct Tm { - /// Seconds after the minute - [0, 60] - tm_sec: i32, - - /// Minutes after the hour - [0, 59] - tm_min: i32, - - /// Hours after midnight - [0, 23] - tm_hour: i32, - - /// Day of the month - [1, 31] - tm_mday: i32, - - /// Months since January - [0, 11] - tm_mon: i32, - - /// Years since 1900 - tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - tm_wday: i32, - - /// Days since January 1 - [0, 365] - tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - tm_nsec: i32, -} - const HECTONANOSECS_IN_SEC: i64 = 10_000_000; const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; -fn time_to_file_time(sec: i64) -> FILETIME { - let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64; - FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD } +pub(super) fn now() -> DateTime { + LocalSysTime::local().datetime() } -fn file_time_as_u64(ft: &FILETIME) -> u64 { - ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64) -} +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { + let naive_sys_time = system_time_from_naive_date_time(d); -fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 { - let t = file_time_as_u64(ft) as i64; - ((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64 -} + let local_sys_time = match local { + false => LocalSysTime::from_utc_time(naive_sys_time), + true => LocalSysTime::from_local_time(naive_sys_time), + }; -fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME { - unsafe { - let mut ft = mem::zeroed(); - SystemTimeToFileTime(sys, &mut ft); - ft + if let Ok(local) = local_sys_time { + return LocalResult::Single(local.datetime()); } + LocalResult::None } -fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME { - let mut sys: SYSTEMTIME = unsafe { mem::zeroed() }; - sys.wSecond = tm.tm_sec as WORD; - sys.wMinute = tm.tm_min as WORD; - sys.wHour = tm.tm_hour as WORD; - sys.wDay = tm.tm_mday as WORD; - sys.wDayOfWeek = tm.tm_wday as WORD; - sys.wMonth = (tm.tm_mon + 1) as WORD; - sys.wYear = (tm.tm_year + 1900) as WORD; - sys +struct LocalSysTime { + inner: SYSTEMTIME, + offset: i32, } -fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) { - tm.tm_sec = sys.wSecond as i32; - tm.tm_min = sys.wMinute as i32; - tm.tm_hour = sys.wHour as i32; - tm.tm_mday = sys.wDay as i32; - tm.tm_wday = sys.wDayOfWeek as i32; - tm.tm_mon = (sys.wMonth - 1) as i32; - tm.tm_year = (sys.wYear - 1900) as i32; - tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday); +impl LocalSysTime { + fn local() -> Self { + let mut now = MaybeUninit::::uninit(); + unsafe { GetLocalTime(now.as_mut_ptr()) } + // SAFETY: GetLocalTime cannot fail according to spec, so we can assume the value + // is initialized. + let st = unsafe { now.assume_init() }; - fn yday(year: i32, month: i32, day: i32) -> i32 { - let leap = if month > 2 { - if year % 4 == 0 { - 1 - } else { - 2 - } - } else { - 0 - }; - let july = if month > 7 { 1 } else { 0 }; + Self::from_local_time(st).expect("Current local time must exist") + } - (month - 1) * 30 + month / 2 + (day - 1) - leap + july + fn from_utc_time(utc_time: SYSTEMTIME) -> Result { + let local_time = utc_to_local_time(&utc_time)?; + let utc_secs = system_time_as_unix_seconds(&utc_time)?; + let local_secs = system_time_as_unix_seconds(&local_time)?; + let offset = (local_secs - utc_secs) as i32; + Ok(Self { inner: local_time, offset }) } -} -macro_rules! call { - ($name:ident($($arg:expr),*)) => { - if $name($($arg),*) == 0 { - panic!(concat!(stringify!($name), " failed with: {}"), - io::Error::last_os_error()); - } + fn from_local_time(local_time: SYSTEMTIME) -> Result { + let utc_time = local_to_utc_time(&local_time)?; + let utc_secs = system_time_as_unix_seconds(&utc_time)?; + let local_secs = system_time_as_unix_seconds(&local_time)?; + let offset = (local_secs - utc_secs) as i32; + Ok(Self { inner: local_time, offset }) } -} -fn time_to_local_tm(sec: i64, tm: &mut Tm) { - let ft = time_to_file_time(sec); - unsafe { - let mut utc = mem::zeroed(); - let mut local = mem::zeroed(); - call!(FileTimeToSystemTime(&ft, &mut utc)); - call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local)); - system_time_to_tm(&local, tm); + fn datetime(self) -> DateTime { + let st = self.inner; - let local = system_time_to_file_time(&local); - let local_sec = file_time_to_unix_seconds(&local); + let date = + NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).unwrap(); + let time = NaiveTime::from_hms(st.wHour as u32, st.wMinute as u32, st.wSecond as u32); - let mut tz = mem::zeroed(); - GetTimeZoneInformation(&mut tz); + let offset = FixedOffset::east_opt(self.offset).unwrap(); + DateTime::from_utc(date.and_time(time) - offset, offset) + } +} - // SystemTimeToTzSpecificLocalTime already applied the biases so - // check if it non standard - tm.tm_utcoff = (local_sec - sec) as i32; - tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 }; +fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { + SYSTEMTIME { + // Valid values: 1601-30827 + wYear: dt.year() as u16, + // Valid values:1-12 + wMonth: dt.month() as u16, + // Valid values: 0-6, starting Sunday. + // NOTE: enum returns 1-7, starting Monday, so we are + // off here, but this is not currently used in local. + wDayOfWeek: dt.weekday() as u16, + // Valid values: 1-31 + wDay: dt.day() as u16, + // Valid values: 0-23 + wHour: dt.hour() as u16, + // Valid values: 0-59 + wMinute: dt.minute() as u16, + // Valid values: 0-59 + wSecond: dt.second() as u16, + // Valid values: 0-999 + wMilliseconds: 0, } } -fn utc_tm_to_time(tm: &Tm) -> i64 { +pub(crate) fn local_to_utc_time(local: &SYSTEMTIME) -> Result { + let mut sys_time = MaybeUninit::::uninit(); unsafe { - let mut ft = mem::zeroed(); - let sys_time = tm_to_system_time(tm); - call!(SystemTimeToFileTime(&sys_time, &mut ft)); - file_time_to_unix_seconds(&ft) - } + windows_sys_call!( + TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()), + 0 + ) + }; + // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can + // assume the value is initialized. + Ok(unsafe { sys_time.assume_init() }) } -fn local_tm_to_time(tm: &Tm) -> i64 { +pub(crate) fn utc_to_local_time(utc_time: &SYSTEMTIME) -> Result { + let mut local = MaybeUninit::::uninit(); unsafe { - let mut ft = mem::zeroed(); - let mut utc = mem::zeroed(); - let mut sys_time = tm_to_system_time(tm); - call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc)); - call!(SystemTimeToFileTime(&utc, &mut ft)); - file_time_to_unix_seconds(&ft) - } + windows_sys_call!( + SystemTimeToTzSpecificLocalTime(ptr::null(), utc_time, local.as_mut_ptr()), + 0 + ) + }; + // SAFETY: SystemTimeToTzSpecificLocalTime must have succeeded at this point, so we can + // assume the value is initialized. + Ok(unsafe { local.assume_init() }) +} + +/// Returns a i64 value representing the unix seconds conversion of the current `WinSystemTime`. +pub(crate) fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> Result { + let mut init = MaybeUninit::::uninit(); + unsafe { windows_sys_call!(SystemTimeToFileTime(st, init.as_mut_ptr()), 0) } + // SystemTimeToFileTime must have succeeded at this point, so we can assum the value is + // initalized. + let filetime = unsafe { init.assume_init() }; + let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); + let unix_secs = (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC; + Ok(unix_secs) } From 4e2c2b4ad23d6822445718ee536b45d5d649040f Mon Sep 17 00:00:00 2001 From: Philip Munksgaard Date: Fri, 24 Mar 2023 15:48:11 +0100 Subject: [PATCH 211/999] Add PartialOrd derivation for Month Also add a few tests to make sure it behaves as expected. --- src/month.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/month.rs b/src/month.rs index 46f09d0fb8..944b276037 100644 --- a/src/month.rs +++ b/src/month.rs @@ -27,7 +27,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// 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)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -352,4 +352,13 @@ mod tests { 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); + } } From 311d52eb91d360bf8877e37a074ec7693f797daa Mon Sep 17 00:00:00 2001 From: lukas Date: Fri, 31 Mar 2023 14:52:11 +0200 Subject: [PATCH 212/999] Add #[must_use] to some methods --- src/date.rs | 27 ++++++++++++++++++++++++++ src/datetime/mod.rs | 32 +++++++++++++++++++++++++++++++ src/datetime/serde.rs | 16 ++++++++++++++++ src/datetime/tests.rs | 2 +- src/format/mod.rs | 4 ++++ src/format/parsed.rs | 1 + src/format/strftime.rs | 2 ++ src/month.rs | 4 ++++ src/naive/date.rs | 38 +++++++++++++++++++++++++++++++++++++ src/naive/datetime/mod.rs | 23 +++++++++++++++++++++- src/naive/datetime/serde.rs | 16 ++++++++++++++++ src/naive/internals.rs | 1 + src/naive/time/mod.rs | 15 +++++++++++++++ src/offset/fixed.rs | 4 ++++ src/offset/local/mod.rs | 3 +++ src/offset/mod.rs | 10 ++++++++++ src/offset/utc.rs | 3 +++ src/oldtime.rs | 7 +++++++ src/weekday.rs | 2 ++ 19 files changed, 208 insertions(+), 2 deletions(-) diff --git a/src/date.rs b/src/date.rs index bad4bfbb8a..9625556519 100644 --- a/src/date.rs +++ b/src/date.rs @@ -77,6 +77,7 @@ impl Date { // // 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 { Date { date, offset } } @@ -86,6 +87,7 @@ impl Date { /// /// Panics on invalid datetime. #[inline] + #[must_use] pub fn and_time(&self, time: NaiveTime) -> Option> { let localdt = self.naive_local().and_time(time); self.timezone().from_local_datetime(&localdt).single() @@ -97,6 +99,7 @@ impl Date { /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")] #[inline] + #[must_use] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") } @@ -106,6 +109,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute and/or second. #[inline] + #[must_use] pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option> { NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time)) } @@ -117,6 +121,7 @@ impl Date { /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")] #[inline] + #[must_use] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } @@ -127,6 +132,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or millisecond. #[inline] + #[must_use] pub fn and_hms_milli_opt( &self, hour: u32, @@ -144,6 +150,7 @@ impl Date { /// Panics on invalid hour, minute, second and/or microsecond. #[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")] #[inline] + #[must_use] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } @@ -154,6 +161,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or microsecond. #[inline] + #[must_use] pub fn and_hms_micro_opt( &self, hour: u32, @@ -171,6 +179,7 @@ impl Date { /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")] #[inline] + #[must_use] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } @@ -181,6 +190,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. #[inline] + #[must_use] pub fn and_hms_nano_opt( &self, hour: u32, @@ -196,6 +206,7 @@ impl Date { /// Panics when `self` is the last representable date. #[deprecated(since = "0.4.23", note = "Use succ_opt() instead")] #[inline] + #[must_use] pub fn succ(&self) -> Date { self.succ_opt().expect("out of bound") } @@ -204,6 +215,7 @@ impl Date { /// /// Returns `None` when `self` is the last representable date. #[inline] + #[must_use] pub fn succ_opt(&self) -> Option> { self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone())) } @@ -213,6 +225,7 @@ impl Date { /// Panics when `self` is the first representable date. #[deprecated(since = "0.4.23", note = "Use pred_opt() instead")] #[inline] + #[must_use] pub fn pred(&self) -> Date { self.pred_opt().expect("out of bound") } @@ -221,18 +234,21 @@ impl Date { /// /// Returns `None` when `self` is the first representable date. #[inline] + #[must_use] pub fn pred_opt(&self) -> Option> { self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) } /// Retrieves an associated offset from UTC. #[inline] + #[must_use] pub fn offset(&self) -> &Tz::Offset { &self.offset } /// Retrieves an associated time zone. #[inline] + #[must_use] pub fn timezone(&self) -> Tz { TimeZone::from_offset(&self.offset) } @@ -240,6 +256,7 @@ impl Date { /// Changes the associated time zone. /// This does not change the actual `Date` (but will change the string representation). #[inline] + #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> Date { tz.from_utc_date(&self.date) } @@ -248,6 +265,7 @@ impl Date { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { let date = self.date.checked_add_signed(rhs)?; Some(Date { date, offset: self.offset }) @@ -257,6 +275,7 @@ impl Date { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { let date = self.date.checked_sub_signed(rhs)?; Some(Date { date, offset: self.offset }) @@ -268,12 +287,14 @@ impl Date { /// This does not overflow or underflow at all, /// as all possible output fits in the range of `Duration`. #[inline] + #[must_use] pub fn signed_duration_since(self, rhs: Date) -> OldDuration { self.date.signed_duration_since(rhs.date) } /// Returns a view to the naive UTC date. #[inline] + #[must_use] pub fn naive_utc(&self) -> NaiveDate { self.date } @@ -284,11 +305,13 @@ impl Date { /// because the offset is restricted to never exceed one day, /// but provided for the consistency. #[inline] + #[must_use] pub fn naive_local(&self) -> NaiveDate { self.date } /// Returns the number of whole years from the given `base` until `self`. + #[must_use] pub fn years_since(&self, base: Self) -> Option { self.date.years_since(base.date) } @@ -315,6 +338,7 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -329,6 +353,7 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } @@ -337,6 +362,7 @@ where #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, @@ -361,6 +387,7 @@ where #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 38416cb6fa..dc9349472a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -113,6 +113,7 @@ impl DateTime { // // 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 { DateTime { datetime, offset } } @@ -142,6 +143,7 @@ impl DateTime { /// assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); /// ``` #[inline] + #[must_use] pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { let datetime_utc = datetime - offset.fix(); @@ -156,6 +158,7 @@ impl DateTime { #[inline] #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] #[allow(deprecated)] + #[must_use] pub fn date(&self) -> Date { Date::from_utc(self.naive_local().date(), self.offset.clone()) } @@ -173,6 +176,7 @@ impl DateTime { /// assert_eq!(date.date_naive(), other.date_naive()); /// ``` #[inline] + #[must_use] pub fn date_naive(&self) -> NaiveDate { let local = self.naive_local(); NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap() @@ -181,6 +185,7 @@ impl DateTime { /// Retrieves a time component. /// Unlike `date`, this is not associated to the time zone. #[inline] + #[must_use] pub fn time(&self) -> NaiveTime { self.datetime.time() + self.offset.fix() } @@ -188,6 +193,7 @@ impl DateTime { /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC /// (aka "UNIX timestamp"). #[inline] + #[must_use] pub fn timestamp(&self) -> i64 { self.datetime.timestamp() } @@ -211,6 +217,7 @@ impl DateTime { /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// ``` #[inline] + #[must_use] pub fn timestamp_millis(&self) -> i64 { self.datetime.timestamp_millis() } @@ -234,6 +241,7 @@ impl DateTime { /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] + #[must_use] pub fn timestamp_micros(&self) -> i64 { self.datetime.timestamp_micros() } @@ -257,6 +265,7 @@ impl DateTime { /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); /// ``` #[inline] + #[must_use] pub fn timestamp_nanos(&self) -> i64 { self.datetime.timestamp_nanos() } @@ -267,6 +276,7 @@ impl DateTime { /// /// note: this is not the number of milliseconds since January 1, 1970 0:00:00 UTC #[inline] + #[must_use] pub fn timestamp_subsec_millis(&self) -> u32 { self.datetime.timestamp_subsec_millis() } @@ -277,6 +287,7 @@ impl DateTime { /// /// note: this is not the number of microseconds since January 1, 1970 0:00:00 UTC #[inline] + #[must_use] pub fn timestamp_subsec_micros(&self) -> u32 { self.datetime.timestamp_subsec_micros() } @@ -287,18 +298,21 @@ impl DateTime { /// /// note: this is not the number of nanoseconds since January 1, 1970 0:00:00 UTC #[inline] + #[must_use] pub fn timestamp_subsec_nanos(&self) -> u32 { self.datetime.timestamp_subsec_nanos() } /// Retrieves an associated offset from UTC. #[inline] + #[must_use] pub fn offset(&self) -> &Tz::Offset { &self.offset } /// Retrieves an associated time zone. #[inline] + #[must_use] pub fn timezone(&self) -> Tz { TimeZone::from_offset(&self.offset) } @@ -306,6 +320,7 @@ impl DateTime { /// Changes the associated 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 { tz.from_utc_datetime(&self.datetime) } @@ -314,6 +329,7 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); @@ -326,6 +342,7 @@ impl DateTime { /// local time is not valid on the newly calculated date. /// /// See [`NaiveDate::checked_add_months`] for more details on behavior + #[must_use] pub fn checked_add_months(self, rhs: Months) -> Option> { self.naive_local() .checked_add_months(rhs)? @@ -337,6 +354,7 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); @@ -349,6 +367,7 @@ impl DateTime { /// local time is not valid on the newly calculated date. /// /// See [`NaiveDate::checked_sub_months`] for more details on behavior + #[must_use] pub fn checked_sub_months(self, rhs: Months) -> Option> { self.naive_local() .checked_sub_months(rhs)? @@ -359,6 +378,7 @@ impl DateTime { /// Add a duration in [`Days`] to the date part of the `DateTime` /// /// Returns `None` if the resulting date would be out of range. + #[must_use] pub fn checked_add_days(self, days: Days) -> Option { self.naive_local() .checked_add_days(days)? @@ -369,6 +389,7 @@ impl DateTime { /// Subtract a duration in [`Days`] from the date part of the `DateTime` /// /// Returns `None` if the resulting date would be out of range. + #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { self.naive_local() .checked_sub_days(days)? @@ -379,23 +400,27 @@ impl DateTime { /// Subtracts another `DateTime` from the current date and time. /// This does not overflow or underflow at all. #[inline] + #[must_use] pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { self.datetime.signed_duration_since(rhs.datetime) } /// Returns a view to the naive UTC datetime. #[inline] + #[must_use] pub fn naive_utc(&self) -> NaiveDateTime { self.datetime } /// Returns a view to the naive local datetime. #[inline] + #[must_use] pub fn naive_local(&self) -> NaiveDateTime { self.datetime + self.offset.fix() } /// Retrieve the elapsed years from now to the given [`DateTime`]. + #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); let earlier_time = @@ -588,6 +613,7 @@ where /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) @@ -598,6 +624,7 @@ where /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] pub fn to_rfc3339(&self) -> String { let mut result = String::with_capacity(32); crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) @@ -631,6 +658,7 @@ where /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { use crate::format::Numeric::*; use crate::format::Pad::Zero; @@ -677,6 +705,7 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -701,6 +730,7 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } @@ -709,6 +739,7 @@ where #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, @@ -736,6 +767,7 @@ where #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index ab0126e2b4..d30f805198 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -174,6 +174,7 @@ 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, @@ -200,6 +201,7 @@ pub mod ts_nanoseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -296,6 +298,7 @@ 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, @@ -325,6 +328,7 @@ pub mod ts_nanoseconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -425,6 +429,7 @@ 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, @@ -451,6 +456,7 @@ pub mod ts_microseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -546,6 +552,7 @@ 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, @@ -575,6 +582,7 @@ pub mod ts_microseconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -675,6 +683,7 @@ 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, @@ -701,6 +710,7 @@ pub mod ts_milliseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -793,6 +803,7 @@ 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, @@ -834,6 +845,7 @@ 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>, @@ -935,6 +947,7 @@ 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, @@ -961,6 +974,7 @@ pub mod ts_seconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -1050,6 +1064,7 @@ 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, @@ -1079,6 +1094,7 @@ pub mod ts_seconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index db32a0d625..aff9d93763 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -472,7 +472,7 @@ fn test_rfc3339_opts() { fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); - dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); + let _ = dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); } #[test] diff --git a/src/format/mod.rs b/src/format/mod.rs index c05ba4d044..5685860c7b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -867,6 +867,7 @@ pub struct DelayedFormat { #[cfg(any(feature = "alloc", feature = "std", test))] impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. + #[must_use] pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { DelayedFormat { date, @@ -879,6 +880,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + #[must_use] pub fn new_with_offset( date: Option, time: Option, @@ -902,6 +904,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] pub fn new_with_locale( date: Option, time: Option, @@ -914,6 +917,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] pub fn new_with_offset_and_locale( date: Option, time: Option, diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 6cc29e9d40..682dd6f250 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -127,6 +127,7 @@ fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<( impl Parsed { /// Returns the initial value of parsed parts. + #[must_use] pub fn new() -> Parsed { Parsed::default() } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index dcaabe49f2..5a113fa43c 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -227,6 +227,7 @@ pub struct StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. + #[must_use] pub fn new(s: &'a str) -> StrftimeItems<'a> { Self::with_remainer(s) } @@ -234,6 +235,7 @@ impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); diff --git a/src/month.rs b/src/month.rs index 944b276037..6a104bd415 100644 --- a/src/month.rs +++ b/src/month.rs @@ -65,6 +65,7 @@ impl Month { /// ----------- | --------- | ---------- | --- | --------- /// `m.succ()`: | `February` | `March` | `...` | `January` #[inline] + #[must_use] pub fn succ(&self) -> Month { match *self { Month::January => Month::February, @@ -88,6 +89,7 @@ impl Month { /// ----------- | --------- | ---------- | --- | --------- /// `m.pred()`: | `December` | `January` | `...` | `November` #[inline] + #[must_use] pub fn pred(&self) -> Month { match *self { Month::January => Month::December, @@ -111,6 +113,7 @@ impl Month { /// -------------------------| --------- | ---------- | --- | ----- /// `m.number_from_month()`: | 1 | 2 | `...` | 12 #[inline] + #[must_use] pub fn number_from_month(&self) -> u32 { match *self { Month::January => 1, @@ -135,6 +138,7 @@ impl Month { /// /// assert_eq!(Month::January.name(), "January") /// ``` + #[must_use] pub fn name(&self) -> &'static str { match *self { Month::January => "January", diff --git a/src/naive/date.rs b/src/naive/date.rs index 64af978f3d..4fabd73809 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -77,6 +77,7 @@ impl NaiveWeek { /// assert!(week.first_day() <= date); /// ``` #[inline] + #[must_use] pub fn first_day(&self) -> NaiveDate { let start = self.start.num_days_from_monday(); let end = self.date.weekday().num_days_from_monday(); @@ -96,6 +97,7 @@ impl NaiveWeek { /// assert!(week.last_day() >= date); /// ``` #[inline] + #[must_use] pub fn last_day(&self) -> NaiveDate { self.first_day() + Duration::days(6) } @@ -115,6 +117,7 @@ impl NaiveWeek { /// assert!(days.contains(&date)); /// ``` #[inline] + #[must_use] pub fn days(&self) -> RangeInclusive { self.first_day()..=self.last_day() } @@ -259,6 +262,7 @@ impl NaiveDate { /// /// Panics on the out-of-range date, invalid month and/or day. #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] + #[must_use] pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { NaiveDate::from_ymd_opt(year, month, day).expect("invalid or out-of-range date") } @@ -282,6 +286,7 @@ impl NaiveDate { /// assert!(from_ymd_opt(400000, 1, 1).is_none()); /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); /// ``` + #[must_use] pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { let flags = YearFlags::from_year(year); NaiveDate::from_mdf(year, Mdf::new(month, day, flags)?) @@ -292,6 +297,7 @@ impl NaiveDate { /// /// Panics on the out-of-range date and/or invalid day of year. #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] + #[must_use] pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate { NaiveDate::from_yo_opt(year, ordinal).expect("invalid or out-of-range date") } @@ -316,6 +322,7 @@ impl NaiveDate { /// assert!(from_yo_opt(400000, 1).is_none()); /// assert!(from_yo_opt(-400000, 1).is_none()); /// ``` + #[must_use] pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { let flags = YearFlags::from_year(year); NaiveDate::from_of(year, Of::new(ordinal, flags)?) @@ -327,6 +334,7 @@ impl NaiveDate { /// /// Panics on the out-of-range date and/or invalid week number. #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] + #[must_use] pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { NaiveDate::from_isoywd_opt(year, week, weekday).expect("invalid or out-of-range date") } @@ -375,6 +383,7 @@ impl NaiveDate { /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); /// ``` + #[must_use] pub fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { let flags = YearFlags::from_year(year); let nweeks = flags.nisoweeks(); @@ -412,6 +421,7 @@ impl NaiveDate { /// Panics if the date is out of range. #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] #[inline] + #[must_use] pub fn from_num_days_from_ce(days: i32) -> NaiveDate { NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") } @@ -436,6 +446,7 @@ impl NaiveDate { /// assert_eq!(from_ndays_opt(100_000_000), None); /// assert_eq!(from_ndays_opt(-100_000_000), None); /// ``` + #[must_use] pub fn from_num_days_from_ce_opt(days: i32) -> Option { let days = days + 365; // make December 31, 1 BCE equal to day 0 let (year_div_400, cycle) = div_mod_floor(days, 146_097); @@ -455,6 +466,7 @@ impl NaiveDate { /// /// `n` is 1-indexed. Passing `n=0` will cause a panic. #[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 { NaiveDate::from_weekday_of_month_opt(year, month, weekday, n).expect("out-of-range date") } @@ -471,6 +483,7 @@ impl NaiveDate { /// /// 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, month: u32, @@ -551,6 +564,7 @@ impl NaiveDate { /// Some(NaiveDate::from_ymd_opt(2022, 9, 30).unwrap()) /// ); /// ``` + #[must_use] pub fn checked_add_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); @@ -581,6 +595,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_sub_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); @@ -654,6 +669,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_add_days(self, days: Days) -> Option { if days.0 == 0 { return Some(self); @@ -677,6 +693,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { if days.0 == 0 { return Some(self); @@ -708,6 +725,7 @@ impl NaiveDate { /// assert_eq!(dt.time(), t); /// ``` #[inline] + #[must_use] pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { NaiveDateTime::new(*self, time) } @@ -720,6 +738,7 @@ impl NaiveDate { /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] + #[must_use] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") } @@ -743,6 +762,7 @@ impl NaiveDate { /// assert!(d.and_hms_opt(24, 34, 56).is_none()); /// ``` #[inline] + #[must_use] pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { NaiveTime::from_hms_opt(hour, min, sec).map(|time| self.and_time(time)) } @@ -755,6 +775,7 @@ impl NaiveDate { /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] + #[must_use] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } @@ -780,6 +801,7 @@ impl NaiveDate { /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); /// ``` #[inline] + #[must_use] pub fn and_hms_milli_opt( &self, hour: u32, @@ -812,6 +834,7 @@ impl NaiveDate { /// ``` #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] #[inline] + #[must_use] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } @@ -837,6 +860,7 @@ impl NaiveDate { /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); /// ``` #[inline] + #[must_use] pub fn and_hms_micro_opt( &self, hour: u32, @@ -855,6 +879,7 @@ impl NaiveDate { /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] #[inline] + #[must_use] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } @@ -880,6 +905,7 @@ impl NaiveDate { /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); /// ``` #[inline] + #[must_use] pub fn and_hms_nano_opt( &self, hour: u32, @@ -928,6 +954,7 @@ impl NaiveDate { /// Panics when `self` is the last representable date. #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] + #[must_use] pub fn succ(&self) -> NaiveDate { self.succ_opt().expect("out of bound") } @@ -946,6 +973,7 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MAX.succ_opt(), None); /// ``` #[inline] + #[must_use] pub fn succ_opt(&self) -> Option { self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1)) } @@ -955,6 +983,7 @@ impl NaiveDate { /// Panics when `self` is the first representable date. #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] + #[must_use] pub fn pred(&self) -> NaiveDate { self.pred_opt().expect("out of bound") } @@ -973,6 +1002,7 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MIN.pred_opt(), None); /// ``` #[inline] + #[must_use] pub fn pred_opt(&self) -> Option { self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31)) } @@ -995,6 +1025,7 @@ impl NaiveDate { /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` + #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); @@ -1026,6 +1057,7 @@ impl NaiveDate { /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` + #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); @@ -1061,6 +1093,7 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), Duration::days(365*4 + 1)); /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); /// ``` + #[must_use] pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { let year1 = self.year(); let year2 = rhs.year(); @@ -1074,6 +1107,7 @@ impl NaiveDate { } /// Returns the number of whole years from the given `base` until `self`. + #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); if (self.month(), self.day()) < (base.month(), base.day()) { @@ -1116,6 +1150,7 @@ impl NaiveDate { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -1159,6 +1194,7 @@ impl NaiveDate { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } @@ -1167,6 +1203,7 @@ impl NaiveDate { #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, @@ -1186,6 +1223,7 @@ impl NaiveDate { #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] + #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index ec0d842c06..671e9eddf2 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -150,6 +150,7 @@ impl NaiveDateTime { /// Panics on the out-of-range number of seconds and/or invalid nanosecond. #[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")] #[inline] + #[must_use] pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); datetime.expect("invalid or out-of-range datetime") @@ -177,6 +178,7 @@ impl NaiveDateTime { /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); /// ``` #[inline] + #[must_use] pub fn from_timestamp_millis(millis: i64) -> Option { Self::from_timestamp_unit(millis, TimestampUnit::Millis) } @@ -203,6 +205,7 @@ impl NaiveDateTime { /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); /// ``` #[inline] + #[must_use] pub fn from_timestamp_micros(micros: i64) -> Option { Self::from_timestamp_unit(micros, TimestampUnit::Micros) } @@ -234,6 +237,7 @@ impl NaiveDateTime { /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); /// ``` #[inline] + #[must_use] pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { let (days, secs) = div_mod_floor(secs, 86_400); let date = days @@ -317,7 +321,7 @@ impl NaiveDateTime { /// let fmt = "%Y-%m-%d %H:%M:%S"; /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); - ///``` + ///``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; @@ -377,6 +381,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp(), -62198755200); /// ``` #[inline] + #[must_use] pub fn timestamp(&self) -> i64 { const UNIX_EPOCH_DAY: i64 = 719_163; let gregorian_day = i64::from(self.date.num_days_from_ce()); @@ -409,6 +414,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp_millis(), -900); /// ``` #[inline] + #[must_use] pub fn timestamp_millis(&self) -> i64 { let as_ms = self.timestamp() * 1000; as_ms + i64::from(self.timestamp_subsec_millis()) @@ -436,6 +442,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] + #[must_use] pub fn timestamp_micros(&self) -> i64 { let as_us = self.timestamp() * 1_000_000; as_us + i64::from(self.timestamp_subsec_micros()) @@ -475,6 +482,7 @@ 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()) @@ -497,6 +505,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); /// ``` #[inline] + #[must_use] pub fn timestamp_subsec_millis(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000_000 } @@ -518,6 +527,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); /// ``` #[inline] + #[must_use] pub fn timestamp_subsec_micros(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000 } @@ -539,6 +549,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); /// ``` #[inline] + #[must_use] pub fn timestamp_subsec_nanos(&self) -> u32 { self.time.nanosecond() } @@ -608,6 +619,7 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_add_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` + #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); @@ -644,6 +656,7 @@ impl NaiveDateTime { /// None /// ); /// ``` + #[must_use] pub fn checked_add_months(self, rhs: Months) -> Option { Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) } @@ -709,6 +722,7 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` + #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); @@ -745,6 +759,7 @@ impl NaiveDateTime { /// None /// ); /// ``` + #[must_use] pub fn checked_sub_months(self, rhs: Months) -> Option { Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.time }) } @@ -752,6 +767,7 @@ impl NaiveDateTime { /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. + #[must_use] pub fn checked_add_days(self, days: Days) -> Option { Some(Self { date: self.date.checked_add_days(days)?, ..self }) } @@ -759,6 +775,7 @@ impl NaiveDateTime { /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. + #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { Some(Self { date: self.date.checked_sub_days(days)?, ..self }) } @@ -801,6 +818,7 @@ impl NaiveDateTime { /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` + #[must_use] pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) } @@ -835,6 +853,7 @@ impl NaiveDateTime { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -878,6 +897,7 @@ impl NaiveDateTime { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } @@ -899,6 +919,7 @@ impl NaiveDateTime { /// use chrono::{NaiveDate, Utc}; /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timezone(), Utc); + #[must_use] pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { tz.from_local_datetime(self) } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 40695fa74b..79509f0277 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -110,6 +110,7 @@ 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, @@ -136,6 +137,7 @@ pub mod ts_nanoseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -230,6 +232,7 @@ 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, @@ -259,6 +262,7 @@ pub mod ts_nanoseconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -356,6 +360,7 @@ 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, @@ -382,6 +387,7 @@ pub mod ts_microseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -479,6 +485,7 @@ 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, @@ -508,6 +515,7 @@ pub mod ts_microseconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -605,6 +613,7 @@ 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, @@ -631,6 +640,7 @@ pub mod ts_milliseconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -725,6 +735,7 @@ 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, @@ -754,6 +765,7 @@ pub mod ts_milliseconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -851,6 +863,7 @@ 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, @@ -877,6 +890,7 @@ pub mod ts_seconds { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -968,6 +982,7 @@ 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, @@ -997,6 +1012,7 @@ pub mod ts_seconds_option { /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; /// # Ok::<(), serde_json::Error>(()) /// ``` + #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 05305b506f..2c954f7abb 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -114,6 +114,7 @@ impl YearFlags { #[allow(unreachable_pub)] // public as an alias for benchmarks only #[doc(hidden)] // for benchmarks only #[inline] + #[must_use] pub fn from_year(year: i32) -> YearFlags { let year = mod_floor(year, 400); YearFlags::from_year_mod_400(year) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 1d36583aa2..14e22fe61b 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -214,6 +214,7 @@ impl NaiveTime { /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] #[inline] + #[must_use] pub fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { NaiveTime::from_hms_opt(hour, min, sec).expect("invalid time") } @@ -239,6 +240,7 @@ impl NaiveTime { /// assert!(from_hms_opt(23, 59, 60).is_none()); /// ``` #[inline] + #[must_use] pub fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { NaiveTime::from_hms_nano_opt(hour, min, sec, 0) } @@ -251,6 +253,7 @@ impl NaiveTime { /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] #[inline] + #[must_use] pub fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { NaiveTime::from_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } @@ -278,6 +281,7 @@ impl NaiveTime { /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); /// ``` #[inline] + #[must_use] pub fn from_hms_milli_opt(hour: u32, min: u32, sec: u32, milli: u32) -> Option { milli .checked_mul(1_000_000) @@ -292,6 +296,7 @@ impl NaiveTime { /// Panics on invalid hour, minute, second and/or microsecond. #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] #[inline] + #[must_use] pub fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { NaiveTime::from_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } @@ -319,6 +324,7 @@ impl NaiveTime { /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); /// ``` #[inline] + #[must_use] pub fn from_hms_micro_opt(hour: u32, min: u32, sec: u32, micro: u32) -> Option { micro.checked_mul(1_000).and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) } @@ -331,6 +337,7 @@ impl NaiveTime { /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] #[inline] + #[must_use] pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { NaiveTime::from_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } @@ -358,6 +365,7 @@ impl NaiveTime { /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); /// ``` #[inline] + #[must_use] pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { return None; @@ -374,6 +382,7 @@ impl NaiveTime { /// Panics on invalid number of seconds and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] #[inline] + #[must_use] pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") } @@ -399,6 +408,7 @@ impl NaiveTime { /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); /// ``` #[inline] + #[must_use] pub fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { if secs >= 86_400 || nano >= 2_000_000_000 { return None; @@ -492,6 +502,7 @@ impl NaiveTime { /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), /// (from_hms(20, 4, 5), -86_400)); /// ``` + #[must_use] pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { let mut secs = self.secs; let mut frac = self.frac; @@ -575,6 +586,7 @@ impl NaiveTime { /// (from_hms(1, 4, 5), -86_400)); /// ``` #[inline] + #[must_use] pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { let (time, rhs) = self.overflowing_add_signed(-rhs); (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) @@ -634,6 +646,7 @@ impl NaiveTime { /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), /// Duration::seconds(61)); /// ``` + #[must_use] pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { // | | :leap| | | | | | | :leap| | // | | : | | | | | | | : | | @@ -696,6 +709,7 @@ impl NaiveTime { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -741,6 +755,7 @@ impl NaiveTime { #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 0989dfa5ba..04ffa3b989 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -34,6 +34,7 @@ impl FixedOffset { /// /// Panics on the out-of-bound `secs`. #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")] + #[must_use] pub fn east(secs: i32) -> FixedOffset { FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") } @@ -52,6 +53,7 @@ impl FixedOffset { /// .and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` + #[must_use] pub fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) @@ -65,6 +67,7 @@ impl FixedOffset { /// /// Panics on the out-of-bound `secs`. #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")] + #[must_use] pub fn west(secs: i32) -> FixedOffset { FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") } @@ -83,6 +86,7 @@ impl FixedOffset { /// .and_hms_opt(0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` + #[must_use] pub fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index e280c78002..041a913b8a 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -60,6 +60,7 @@ impl Local { /// Returns a `Date` which corresponds to the current date. #[deprecated(since = "0.4.23", note = "use `Local::now()` instead")] #[allow(deprecated)] + #[must_use] pub fn today() -> Date { Local::now().date() } @@ -70,6 +71,7 @@ impl Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] + #[must_use] pub fn now() -> DateTime { inner::now() } @@ -80,6 +82,7 @@ impl Local { 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(); diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 09d0714e98..5267ae4ce2 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -52,6 +52,7 @@ pub enum LocalResult { impl LocalResult { /// Returns `Some` only when the conversion result is unique, or `None` otherwise. + #[must_use] pub fn single(self) -> Option { match self { LocalResult::Single(t) => Some(t), @@ -60,6 +61,7 @@ impl LocalResult { } /// Returns `Some` for the earliest possible conversion result, or `None` if none. + #[must_use] pub fn earliest(self) -> Option { match self { LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), @@ -68,6 +70,7 @@ impl LocalResult { } /// Returns `Some` for the latest possible conversion result, or `None` if none. + #[must_use] pub fn latest(self) -> Option { match self { LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), @@ -76,6 +79,7 @@ impl LocalResult { } /// Maps a `LocalResult` into `LocalResult` with given function. + #[must_use] pub fn map U>(self, mut f: F) -> LocalResult { match self { LocalResult::None => LocalResult::None, @@ -92,6 +96,7 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_time(self, time: NaiveTime) -> LocalResult> { match self { LocalResult::Single(d) => { @@ -106,6 +111,7 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { match self { LocalResult::Single(d) => { @@ -121,6 +127,7 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_milli_opt( self, hour: u32, @@ -142,6 +149,7 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_micro_opt( self, hour: u32, @@ -163,6 +171,7 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_nano_opt( self, hour: u32, @@ -181,6 +190,7 @@ impl LocalResult> { impl LocalResult { /// Returns the single unique conversion result, or panics accordingly. + #[must_use] pub fn unwrap(self) -> T { match self { LocalResult::None => panic!("No such local time"), diff --git a/src/offset/utc.rs b/src/offset/utc.rs index cfed754b2f..aeaeb672dc 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -54,6 +54,7 @@ impl Utc { note = "use `Utc::now()` instead, potentially with `.date_naive()`" )] #[allow(deprecated)] + #[must_use] pub fn today() -> Date { Utc::now().date() } @@ -64,6 +65,7 @@ impl Utc { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] + #[must_use] pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); @@ -78,6 +80,7 @@ impl Utc { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] + #[must_use] pub fn now() -> DateTime { let now = js_sys::Date::new_0(); DateTime::::from(now) diff --git a/src/oldtime.rs b/src/oldtime.rs index 8e2b3d2c09..8fde78670d 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -74,6 +74,7 @@ impl Duration { /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. /// Panics when the duration is out of bounds. #[inline] + #[must_use] pub fn weeks(weeks: i64) -> Duration { let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); Duration::seconds(secs) @@ -83,6 +84,7 @@ impl Duration { /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. /// Panics when the duration is out of bounds. #[inline] + #[must_use] pub fn days(days: i64) -> Duration { let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); Duration::seconds(secs) @@ -92,6 +94,7 @@ impl Duration { /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. /// Panics when the duration is out of bounds. #[inline] + #[must_use] pub fn hours(hours: i64) -> Duration { let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); Duration::seconds(secs) @@ -101,6 +104,7 @@ impl Duration { /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. /// Panics when the duration is out of bounds. #[inline] + #[must_use] pub fn minutes(minutes: i64) -> Duration { let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); Duration::seconds(secs) @@ -110,6 +114,7 @@ impl Duration { /// Panics when the duration is more than `i64::MAX` seconds /// or less than `i64::MIN` seconds. #[inline] + #[must_use] pub fn seconds(seconds: i64) -> Duration { let d = Duration { secs: seconds, nanos: 0 }; if d < MIN || d > MAX { @@ -211,6 +216,7 @@ impl Duration { } /// Add two durations, returning `None` if overflow occurred. + #[must_use] pub fn checked_add(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); let mut nanos = self.nanos + rhs.nanos; @@ -229,6 +235,7 @@ impl Duration { } /// Subtract two durations, returning `None` if overflow occurred. + #[must_use] pub fn checked_sub(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); let mut nanos = self.nanos - rhs.nanos; diff --git a/src/weekday.rs b/src/weekday.rs index 72e384673f..7062b35eeb 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -36,6 +36,7 @@ impl Weekday { /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` #[inline] + #[must_use] pub fn succ(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Tue, @@ -54,6 +55,7 @@ impl Weekday { /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` #[inline] + #[must_use] pub fn pred(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Sun, From 5ecf30b36e6b72e95ccc09a47d0253f6b703a1bb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 11 Apr 2023 16:37:28 +0200 Subject: [PATCH 213/999] Fix military timezones in RFC 2822 parsing --- src/format/parse.rs | 14 ++++++++++++++ src/format/scan.rs | 8 +++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 69204d2e95..d18c52dec3 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -854,6 +854,20 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone + // named timezones + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { diff --git a/src/format/scan.rs b/src/format/scan.rs index 68e294c834..ecdc2b7741 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -334,8 +334,14 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> offset_hours(-7) } else if equals(name, "pst") { offset_hours(-8) + } else if name.len() == 1 { + match name.as_bytes()[0] { + // recommended by RFC 2822: consume but treat it as -0000 + b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), + _ => Ok((s, None)), + } } else { - Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000 + Ok((s, None)) } } else { let (s_, offset) = timezone_offset(s, |s| Ok(s))?; From 5bc022ca811a81d0dd79e3254214ae05a74898b6 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Wed, 12 Apr 2023 20:24:46 -0700 Subject: [PATCH 214/999] RFC 2822 single-letter obsolete tests Add follow-up tests to PR #1013 Link to RFC 2822 section. --- src/format/parse.rs | 10 +++++++++- src/format/scan.rs | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index d18c52dec3..0f50b4a4be 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -854,9 +854,11 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone - // named timezones + // named timezones that have specific timezone offsets + // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), @@ -865,9 +867,15 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + // named single-letter military timezones must fallback to +0000 ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + // named single-letter timezone "J" is specifically not valid + ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { diff --git a/src/format/scan.rs b/src/format/scan.rs index ecdc2b7741..233a0ccec7 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -308,6 +308,9 @@ where /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). +/// See [RFC 2822 Section 4.3]. +/// +/// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { // tries to parse legacy time zone names let upto = s From 35eaac344607743669a27e4faa6d208c889bdfc8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 22:10:23 +0200 Subject: [PATCH 215/999] Add test for issue 551 --- src/format/parsed.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 682dd6f250..27bc0cdb41 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -1287,4 +1287,18 @@ mod tests { // TODO test with a variable time zone (for None and Ambiguous cases) } + + #[test] + fn issue_551() { + use crate::Weekday; + let mut parsed = Parsed::new(); + + parsed.year = Some(2002); + parsed.week_from_mon = Some(22); + parsed.weekday = Some(Weekday::Mon); + assert_eq!(NaiveDate::from_ymd_opt(2002, 6, 3).unwrap(), parsed.to_naive_date().unwrap()); + + parsed.year = Some(2001); + assert_eq!(NaiveDate::from_ymd_opt(2001, 5, 28).unwrap(), parsed.to_naive_date().unwrap()); + } } From e51430f3e1a86d4e51a5598c157b322ed9e21c96 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 21:44:51 +0200 Subject: [PATCH 216/999] Add an example to Weekday --- src/weekday.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/weekday.rs b/src/weekday.rs index 7062b35eeb..db6d7e03c4 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -8,6 +8,26 @@ use rkyv::{Archive, Deserialize, Serialize}; /// The order of the days of week depends on the context. /// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) /// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. +/// +/// # Example +/// ``` +/// use chrono::Weekday; +/// use num_traits::cast::FromPrimitive; +/// +/// let monday = "Monday".parse::().unwrap(); +/// assert_eq!(monday, Weekday::Mon); +/// +/// let sunday = Weekday::from_u8(6).unwrap(); +/// assert_eq!(sunday, Weekday::Sun); +/// +/// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 +/// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1 +/// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0 +/// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1 +/// +/// assert_eq!(sunday.succ(), monday); +/// assert_eq!(sunday.pred(), Weekday::Sat); +/// ``` #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] @@ -110,7 +130,7 @@ impl Weekday { /// Returns a day-of-week number starting from the parameter `day` (D) = 0. /// - /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` + /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] From c83f32e6ba06f116577f8c4f79668a54d5a963d1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 07:50:26 +0200 Subject: [PATCH 217/999] Guard against overflow in NaiveDate::with_*0 methods --- src/naive/date.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4fabd73809..85db44fa06 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1559,7 +1559,8 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { - self.with_mdf(self.mdf().with_month(month0 + 1)?) + let month = month0.checked_add(1)?; + self.with_mdf(self.mdf().with_month(month)?) } /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. @@ -1597,7 +1598,8 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_day0(&self, day0: u32) -> Option { - self.with_mdf(self.mdf().with_day(day0 + 1)?) + let day = day0.checked_add(1)?; + self.with_mdf(self.mdf().with_day(day)?) } /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. @@ -1645,7 +1647,8 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal0 + 1)?) + let ordinal = ordinal0.checked_add(1)?; + self.with_of(self.of().with_ordinal(ordinal)?) } } @@ -3032,4 +3035,12 @@ mod tests { } } } + + #[test] + fn test_with_0_overflow() { + let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap(); + assert!(dt.with_month0(4294967295).is_none()); + assert!(dt.with_day0(4294967295).is_none()); + assert!(dt.with_ordinal0(4294967295).is_none()); + } } From e9067a5a3e684f1069f9d627e57ea7d536398e2e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 22:46:27 +0200 Subject: [PATCH 218/999] Add with_timezone to NaiveDateTime --- src/naive/datetime/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 671e9eddf2..d13ff99889 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -554,6 +554,28 @@ impl NaiveDateTime { self.time.nanosecond() } + /// Associated this `NaiveDateTime` with a [`TimeZone`], turning it into a [`DateTime`]. + /// + /// Note that not all datetimes may be valid in the target timezone, or a datetime may be + /// ambiguous. Use the methods on [`LocalResult`] to handle such cases. + /// + /// # Example + /// + /// ``` + /// use chrono::{Local, NaiveDate}; + /// + /// let d = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(3, 5, 7).unwrap(); + /// let timezone = Local; + /// let d_local = d.with_timezone(&timezone).latest().unwrap(); + /// + /// assert_eq!(d_local.naive_local(), d) + /// ``` + #[inline] + #[must_use] + pub fn with_timezone(&self, tz: &Tz) -> LocalResult> { + tz.from_local_datetime(self) + } + /// Adds given `Duration` to the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), From a50648daf024bf933511767a072fc234065fab4a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 08:17:22 +0200 Subject: [PATCH 219/999] Prevent string slicing inside char boundaries --- src/format/scan.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 233a0ccec7..399a50c30f 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -12,8 +12,8 @@ use crate::Weekday; /// Returns true when two slices are equal case-insensitively (in ASCII). /// Assumes that the `pattern` is already converted to lower case. -fn equals(s: &str, pattern: &str) -> bool { - let mut xs = s.as_bytes().iter().map(|&c| match c { +fn equals(s: &[u8], pattern: &str) -> bool { + let mut xs = s.iter().map(|&c| match c { b'A'..=b'Z' => c + 32, _ => c, }); @@ -152,7 +152,7 @@ pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // tries to consume the suffix if possible let suffix = LONG_MONTH_SUFFIXES[month0 as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { s = &s[suffix.len()..]; } @@ -170,7 +170,7 @@ pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // tries to consume the suffix if possible let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { s = &s[suffix.len()..]; } @@ -322,7 +322,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> }) .unwrap_or(s.len()); if upto > 0 { - let name = &s[..upto]; + let name = &s.as_bytes()[..upto]; let s = &s[upto..]; let offset_hours = |o| Ok((s, Some(o * 3600))); if equals(name, "gmt") || equals(name, "ut") { @@ -338,7 +338,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> } else if equals(name, "pst") { offset_hours(-8) } else if name.len() == 1 { - match name.as_bytes()[0] { + match name[0] { // recommended by RFC 2822: consume but treat it as -0000 b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), _ => Ok((s, None)), From 93e2a91618f4cac218a2ce0df9788d357f1cd7d8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 08:24:35 +0200 Subject: [PATCH 220/999] Add test for string slicing panic from issue 1010 --- src/format/parse.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/format/parse.rs b/src/format/parse.rs index 0f50b4a4be..e894f710b9 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -997,3 +997,11 @@ fn test_rfc3339() { } } } + +#[cfg(test)] +#[test] +fn test_issue_1010() { + let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); + assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); +} From 6045377c755c309766907dcd240ad466be8f04a8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 13:49:58 +0200 Subject: [PATCH 221/999] Use rem_euclid from std (MSRV: 1.38) --- src/offset/local/tz_info/mod.rs | 15 --------------- src/offset/local/tz_info/rule.rs | 6 +++--- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/offset/local/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs index bd2693b6bb..780e15ace9 100644 --- a/src/offset/local/tz_info/mod.rs +++ b/src/offset/local/tz_info/mod.rs @@ -100,21 +100,6 @@ impl From for Error { } } -// MSRV: 1.38 -#[inline] -fn rem_euclid(v: i64, rhs: i64) -> i64 { - let r = v % rhs; - if r < 0 { - if rhs < 0 { - r - rhs - } else { - r + rhs - } - } else { - r - } -} - /// Number of hours in one day const HOURS_PER_DAY: i64 = 24; /// Number of seconds in one hour diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 7befddb5cf..c2596265eb 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use super::parser::Cursor; use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; use super::{ - rem_euclid, Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, + Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, SECONDS_PER_DAY, }; @@ -589,9 +589,9 @@ impl RuleDay { } let week_day_of_first_month_day = - rem_euclid(4 + days_since_unix_epoch(year, month, 1), DAYS_PER_WEEK); + (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK); let first_week_day_occurence_in_month = - 1 + rem_euclid(week_day as i64 - week_day_of_first_month_day, DAYS_PER_WEEK); + 1 + (week_day as i64 - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK); let mut month_day = first_week_day_occurence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; From 42943b7077f9e8e45dce061ebe54e53afa0cd04b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 14:59:05 +0200 Subject: [PATCH 222/999] use iter::Copied (MSRV: 1.36) --- src/offset/local/tz_info/parser.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 5652a0ea95..7bcf307e8c 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -7,7 +7,6 @@ use super::rule::TransitionRule; use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition}; use super::Error; -#[allow(clippy::map_clone)] // MSRV: 1.36 pub(super) fn parse(bytes: &[u8]) -> Result { let mut cursor = Cursor::new(bytes); let state = State::new(&mut cursor, true)?; @@ -66,8 +65,8 @@ pub(super) fn parse(bytes: &[u8]) -> Result { leap_seconds.push(LeapSecond::new(unix_leap_time, correction)); } - let std_walls_iter = state.std_walls.iter().map(|&i| i).chain(iter::repeat(0)); - let ut_locals_iter = state.ut_locals.iter().map(|&i| i).chain(iter::repeat(0)); + let std_walls_iter = state.std_walls.iter().copied().chain(iter::repeat(0)); + let ut_locals_iter = state.ut_locals.iter().copied().chain(iter::repeat(0)); if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) { return Err(Error::InvalidTzFile( "invalid couple of standard/wall and UT/local indicators", From 84bbea51313b0c705fcf0ba82196234fe940050f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Apr 2023 15:01:27 +0200 Subject: [PATCH 223/999] Add comment about non_exhaustive attributes --- src/format/mod.rs | 2 +- src/format/parsed.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 5685860c7b..768ed76815 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -386,7 +386,7 @@ pub enum ParseErrorKind { /// There was an error on the formatting string, or there were non-supported formating items. BadFormat, - // TODO: Change this to `#[non_exhaustive]` (on the enum) when MSRV is increased + // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. #[doc(hidden)] __Nonexhaustive, } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 27bc0cdb41..1ed5dcbf1d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -106,6 +106,7 @@ pub struct Parsed { pub offset: Option, /// A dummy field to make this type not fully destructible (required for API stability). + // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. _dummy: (), } From 895d3b867ff318b5866cad7684d26279a45bc114 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Apr 2023 15:22:48 +0200 Subject: [PATCH 224/999] Fix warnings when running tests on Windows --- tests/dateutils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index dec6bfe117..1eb0941011 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -1,7 +1,11 @@ +#[cfg(unix)] use chrono::offset::TimeZone; +#[cfg(unix)] use chrono::Local; +#[cfg(unix)] use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; +#[cfg(unix)] use std::{path, process}; #[cfg(unix)] From 35b145bd0ca721b5926e36f047eec8e1060372c0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 May 2023 09:01:39 +0200 Subject: [PATCH 225/999] Add `#[track_caller]` to `LocalResult::unwrap` --- src/offset/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 5267ae4ce2..ee1fe7e233 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -191,6 +191,7 @@ impl LocalResult> { impl LocalResult { /// Returns the single unique conversion result, or panics accordingly. #[must_use] + #[track_caller] pub fn unwrap(self) -> T { match self { LocalResult::None => panic!("No such local time"), From e3bca27a016dfbc24e298fb63c162de4fb067081 Mon Sep 17 00:00:00 2001 From: campbellcole <10430178+campbellcole@users.noreply.github.com> Date: Sat, 6 May 2023 21:26:21 -0700 Subject: [PATCH 226/999] add documentation for %Z quirk --- src/format/strftime.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 5a113fa43c..24bae20c0a 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -68,7 +68,7 @@ The following specifiers are available both to formatting and parsing. | `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | | | | | | | | **TIME ZONE SPECIFIERS:** | -| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^8] | +| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] | | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | | `%:z` | `+09:30` | Same as `%z` but with a colon. | |`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds. | @@ -164,6 +164,12 @@ Notes: Note that they can read nothing if the fractional part is zero. [^8]: `%Z`: + Since `chrono` is not aware of timezones beyond their offsets, this specifier + **only prints the offset** when used for formatting. The timezone abbreviation + will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960) + for more information. +
+
Offset will not be populated from the parsed data, nor will it be validated. Timezone is completely ignored. Similar to the glibc `strptime` treatment of this format code. From a20a9b7b90d2e545211538477c2822b62c76ac71 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 19 Apr 2023 18:08:00 +0200 Subject: [PATCH 227/999] Revert "Add with_timezone to NaiveDateTime" This reverts commit e9067a5a3e684f1069f9d627e57ea7d536398e2e. --- src/naive/datetime/mod.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index d13ff99889..671e9eddf2 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -554,28 +554,6 @@ impl NaiveDateTime { self.time.nanosecond() } - /// Associated this `NaiveDateTime` with a [`TimeZone`], turning it into a [`DateTime`]. - /// - /// Note that not all datetimes may be valid in the target timezone, or a datetime may be - /// ambiguous. Use the methods on [`LocalResult`] to handle such cases. - /// - /// # Example - /// - /// ``` - /// use chrono::{Local, NaiveDate}; - /// - /// let d = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(3, 5, 7).unwrap(); - /// let timezone = Local; - /// let d_local = d.with_timezone(&timezone).latest().unwrap(); - /// - /// assert_eq!(d_local.naive_local(), d) - /// ``` - #[inline] - #[must_use] - pub fn with_timezone(&self, tz: &Tz) -> LocalResult> { - tz.from_local_datetime(self) - } - /// Adds given `Duration` to the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), From f6597197cd8a0230291a478bee2b9b8c696ad80e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 7 May 2023 14:52:16 +0200 Subject: [PATCH 228/999] Fix panic in from_num_days_from_ce_opt --- src/naive/date.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 85db44fa06..ef0d0320ce 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -448,7 +448,7 @@ impl NaiveDate { /// ``` #[must_use] pub fn from_num_days_from_ce_opt(days: i32) -> Option { - let days = days + 365; // make December 31, 1 BCE equal to day 0 + let days = days.checked_add(365)?; // make December 31, 1 BCE equal to day 0 let (year_div_400, cycle) = div_mod_floor(days, 146_097); let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); @@ -2500,6 +2500,9 @@ mod tests { assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); + + assert_eq!(from_ndays_from_ce(i32::MIN), None); + assert_eq!(from_ndays_from_ce(i32::MAX), None); } #[test] From 816f5bd48c42ab8c746cbdd78c5de0946aa0e1e0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 May 2023 09:32:24 +0200 Subject: [PATCH 229/999] Bump MSRV to 1.56 --- .github/workflows/test.yml | 11 +++++------ Cargo.toml | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1250867787..106157c14f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,9 +32,8 @@ jobs: - run: cargo test --doc --all-features --color=always -- --color=always # later this may be able to be included with the below - # kept seperate for now as the following don't compile on 1.38.0 - # * rkyv - # * criterion + # kept separate for now as the following don't compile on 1.56.1 + # * arbitrary rust_msrv: strategy: matrix: @@ -44,11 +43,11 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.38.0 + toolchain: 1.56.1 - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests which are run elsewhere - - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi --color=always -- --color=always - - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi --color=always -- --color=always + - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde --color=always -- --color=always + - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde --color=always -- --color=always rust_versions: strategy: diff --git a/Cargo.toml b/Cargo.toml index 8920d6ba21..7c3a02409d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" license = "MIT/Apache-2.0" exclude = ["/ci/*"] edition = "2018" +rust-version = "1.56.0" [lib] name = "chrono" From 64c3c2ba86ceb89ee301943961b2271462368012 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 May 2023 09:34:10 +0200 Subject: [PATCH 230/999] Allow clippy to take advantage of new MSRV --- clippy.toml | 1 - src/format/mod.rs | 1 + src/format/parsed.rs | 1 + src/format/scan.rs | 9 +-------- src/naive/date.rs | 2 +- src/offset/local/unix.rs | 4 ++-- 6 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index 749c3b58a5..0000000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.38" diff --git a/src/format/mod.rs b/src/format/mod.rs index 768ed76815..c3db7ade92 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -356,6 +356,7 @@ impl ParseError { } /// The category of parse error +#[allow(clippy::manual_non_exhaustive)] #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub enum ParseErrorKind { /// Given field is out of permitted range. diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 1ed5dcbf1d..b5fb8e699f 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -22,6 +22,7 @@ use crate::{Datelike, Timelike}; /// /// - `to_*` methods try to make a concrete date and time value out of set fields. /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. +#[allow(clippy::manual_non_exhaustive)] #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { /// Year. diff --git a/src/format/scan.rs b/src/format/scan.rs index 399a50c30f..705ccf9095 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -313,14 +313,7 @@ where /// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { // tries to parse legacy time zone names - let upto = s - .as_bytes() - .iter() - .position(|&c| match c { - b'a'..=b'z' | b'A'..=b'Z' => false, - _ => true, - }) - .unwrap_or(s.len()); + let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len()); if upto > 0 { let name = &s.as_bytes()[..upto]; let s = &s[upto..]; diff --git a/src/naive/date.rs b/src/naive/date.rs index ef0d0320ce..492b9e4549 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2249,7 +2249,7 @@ mod tests { assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3) .unwrap() - .checked_sub_months(Months::new((i32::MIN as i64).abs() as u32 + 1)), + .checked_sub_months(Months::new((i32::MIN as i64).unsigned_abs() as u32 + 1)), None ); diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 32aa31618b..22114f21ef 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -88,7 +88,7 @@ impl Default for Cache { fn default() -> Cache { // default to UTC if no local timezone can be found let env_tz = env::var("TZ").ok(); - let env_ref = env_tz.as_ref().map(|s| s.as_str()); + let env_ref = env_tz.as_deref(); Cache { last_checked: SystemTime::now(), source: Source::new(env_ref), @@ -114,7 +114,7 @@ impl Cache { Ok(d) if d.as_secs() < 1 => (), Ok(_) | Err(_) => { let env_tz = env::var("TZ").ok(); - let env_ref = env_tz.as_ref().map(|s| s.as_str()); + let env_ref = env_tz.as_deref(); let new_source = Source::new(env_ref); let out_of_date = match (&self.source, &new_source) { From 1ab234fe5a6794fca48e53cd176cb046777aebab Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 May 2023 13:41:04 +0200 Subject: [PATCH 231/999] Remove custom matches!() macro --- src/lib.rs | 12 ------------ src/offset/local/tz_info/rule.rs | 1 - src/offset/local/tz_info/timezone.rs | 1 - 3 files changed, 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 861ee10593..acd4f4a0f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -513,15 +513,3 @@ pub use naive::__BenchYearFlags; pub mod serde { pub use super::datetime::serde::*; } - -/// MSRV 1.42 -#[cfg(test)] -#[macro_export] -macro_rules! matches { - ($expression:expr, $(|)? $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => { - match $expression { - $( $pattern )|+ $( if $guard )? => true, - _ => false - } - } -} diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index c2596265eb..5b8634eece 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -777,7 +777,6 @@ mod tests { use super::super::timezone::Transition; use super::super::{Error, TimeZone}; use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule}; - use crate::matches; #[test] fn test_quoted() -> Result<(), Error> { diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 8572825a89..866cc011b2 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -632,7 +632,6 @@ const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; mod tests { use super::super::Error; use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; - use crate::matches; #[test] fn test_no_dst() -> Result<(), Error> { From f87d5486016c49e46d3453ec721274df1dad3e14 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 May 2023 13:41:19 +0200 Subject: [PATCH 232/999] Remove unnecessary cast --- src/naive/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 492b9e4549..c2bf145ea7 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2249,7 +2249,7 @@ mod tests { assert_eq!( NaiveDate::from_ymd_opt(2022, 8, 3) .unwrap() - .checked_sub_months(Months::new((i32::MIN as i64).unsigned_abs() as u32 + 1)), + .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), None ); From 8df1ba108ff6c305adb3325ccf42ea38429e415e Mon Sep 17 00:00:00 2001 From: Qiu Chaofan Date: Wed, 19 Apr 2023 17:05:21 +0800 Subject: [PATCH 233/999] Fix tests on AIX - Add AIX timezone database path to search list. - Use date program from freeware to do the testing. --- src/offset/local/tz_info/timezone.rs | 4 ++-- tests/dateutils.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 866cc011b2..352250e0bd 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -620,8 +620,8 @@ fn saturating_abs(v: i32) -> i32 { // Possible system timezone directories #[cfg(unix)] -const ZONE_INFO_DIRECTORIES: [&str; 3] = - ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo"]; +const ZONE_INFO_DIRECTORIES: [&str; 4] = + ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"]; /// Number of seconds in one week pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK; diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 1eb0941011..fb1e3e47a7 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -55,7 +55,10 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { #[test] #[cfg(unix)] fn try_verify_against_date_command() { + #[cfg(not(target_os = "aix"))] let date_path = "/usr/bin/date"; + #[cfg(target_os = "aix")] + let date_path = "/opt/freeware/bin/date"; if !path::Path::new(date_path).exists() { // date command not found, skipping From 005ec07f45e814cc02487293b769ca49d5841ebc Mon Sep 17 00:00:00 2001 From: pezcore Date: Thu, 6 Apr 2023 18:42:55 -0400 Subject: [PATCH 234/999] add impl From for NaiveDate --- src/naive/date.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index c2bf145ea7..c5b6fe0d74 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1831,6 +1831,12 @@ impl Sub for NaiveDate { } } +impl From for NaiveDate { + fn from(naive_datetime: NaiveDateTime) -> Self { + naive_datetime.date() + } +} + /// Iterator over `NaiveDate` with a step size of one day. #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateDaysIterator { From 4ce9e830d82b09e94d7213846b4c05f5dcd88483 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 23 Apr 2023 21:32:23 +0200 Subject: [PATCH 235/999] Prevent bench_year_flags_from_year from being optimized out --- benches/chrono.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index 246271b81d..216616b824 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -64,7 +64,7 @@ fn bench_year_flags_from_year(c: &mut Criterion) { c.bench_function("bench_year_flags_from_year", |b| { b.iter(|| { for year in -999i32..1000 { - __BenchYearFlags::from_year(year); + let _ = __BenchYearFlags::from_year(black_box(year)); } }) }); From e0c49268415af4f461962f0ca896d12f1e494785 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 07:09:47 +0200 Subject: [PATCH 236/999] Fix import --- src/datetime/tests.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index aff9d93763..00989fb313 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -6,9 +6,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::Local; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::oldtime::Duration; -#[cfg(feature = "clock")] -use crate::Datelike; -use crate::{Days, LocalResult, Months, NaiveDateTime}; +use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; #[derive(Clone)] struct DstTester; From 28b2200f4f44af637d22892367b733c0b9051881 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Mon, 12 Sep 2022 15:03:44 -0500 Subject: [PATCH 237/999] Ignore unsupported doc tests under --no-default-features There's no pretty way of gating doctests contingent on cfg presence/absense so this is kind of ugly. First, a doc comment `///` is converted into its equivalent `#[doc(...)]`, which is then converted into a conditional rust attribute via `#[cfg_attr(...)]`. We need two of these, one to ignore the tests if the cfg predicate isn't met and another to indicate the start of a code block and enable testing + syntax highlighting. --- src/format/mod.rs | 3 ++- src/lib.rs | 12 ++++++++---- src/naive/time/mod.rs | 6 ++++-- src/offset/fixed.rs | 6 ++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index c3db7ade92..af7eb4cf15 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -16,7 +16,8 @@ //! C's `strftime` format. The available options can be found [here](./strftime/index.html). //! //! # Example -//! ```rust +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +#![cfg_attr(feature = "std", doc = "```rust")] //! # use std::error::Error; //! use chrono::prelude::*; //! diff --git a/src/lib.rs b/src/lib.rs index acd4f4a0f7..8472a6fc3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,8 @@ //! or in the local time zone //! ([`Local::now()`](./offset/struct.Local.html#method.now)). //! -//! ```rust +#![cfg_attr(not(feature = "clock"), doc = "```ignore")] +#![cfg_attr(feature = "clock", doc = "```rust")] //! use chrono::prelude::*; //! //! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` @@ -122,7 +123,8 @@ //! This is a bit verbose due to Rust's lack of function and method overloading, //! but in turn we get a rich combination of initialization methods. //! -//! ```rust +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +#![cfg_attr(feature = "std", doc = "```rust")] //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! @@ -314,7 +316,8 @@ //! [`DateTime.timestamp_subsec_nanos`](./struct.DateTime.html#method.timestamp_subsec_nanos) //! to get the number of additional number of nanoseconds. //! -//! ```rust +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +#![cfg_attr(feature = "std", doc = "```rust")] //! // We need the trait in scope to use Utc::timestamp(). //! use chrono::{DateTime, TimeZone, Utc}; //! @@ -333,7 +336,8 @@ //! It also has time zones attached, and have to be constructed via time zones. //! Most operations available to `DateTime` are also available to `Date` whenever appropriate. //! -//! ```rust +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +#![cfg_attr(feature = "std", doc = "```rust")] //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 14e22fe61b..444c8dc1ff 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -818,7 +818,8 @@ impl Timelike for NaiveTime { /// ([Why?](#leap-second-handling)) /// Use the proper [formatting method](#method.format) to get a human-readable representation. /// - /// ``` + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + #[cfg_attr(feature = "std", doc = "```")] /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.second(), 59); @@ -846,7 +847,8 @@ impl Timelike for NaiveTime { /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or /// use the proper [formatting method](#method.format) to get a human-readable representation. /// - /// ``` + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + #[cfg_attr(feature = "std", doc = "```")] /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.nanosecond(), 1_000_000_000); diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 04ffa3b989..02691da7ab 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -46,7 +46,8 @@ impl FixedOffset { /// /// # Example /// - /// ``` + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = FixedOffset::east_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() @@ -79,7 +80,8 @@ impl FixedOffset { /// /// # Example /// - /// ``` + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = FixedOffset::west_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() From 386f32705e473aa96b492199108a44e76cd1158b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 07:15:06 +0200 Subject: [PATCH 238/999] [CI] Run tests with --no-default-features --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 106157c14f..7569e3260e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,8 @@ jobs: - 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 + - run: cargo test --lib --no-default-features + - run: cargo test --doc --no-default-features no_std: strategy: From 38d9132beaa247e3732a3a3fff702989d3ec58c3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 08:16:27 +0200 Subject: [PATCH 239/999] Only run test on windows with clock feature --- src/datetime/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 00989fb313..08d94afc8c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -950,7 +950,7 @@ fn test_datetime_sub_assign_local() { } #[test] -#[cfg(target_os = "windows")] +#[cfg(all(target_os = "windows", feature = "clock"))] fn test_from_naive_date_time_windows() { let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); From 595955c4821a9450c28355d5825d8813fa82f566 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 06:50:03 +0200 Subject: [PATCH 240/999] Add test for `impl From> for DateTime` --- src/datetime/tests.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 08d94afc8c..d35a56ff5c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,7 +4,7 @@ use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, TimeZone, Utc}; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::oldtime::Duration; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; @@ -983,3 +983,14 @@ fn test_from_naive_date_time_windows() { }); assert!(err.is_err()); } + +#[test] +fn test_datetime_local_from_preserves_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let offset = datetime.offset().fix(); + + let datetime_fixed: DateTime = datetime.into(); + assert_eq!(&offset, datetime_fixed.offset()); +} From 6a37b4c91e7f777c7d0d97f13c122cf79b1458ec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 27 Apr 2023 21:36:58 +0200 Subject: [PATCH 241/999] Use correct offset in conversion from Local to FixedOffset --- src/datetime/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index dc9349472a..e05625c31f 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -529,10 +529,9 @@ impl From> for DateTime { impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// - /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned - /// by this will be created with a fixed timezone offset of 0. + /// Conversion is performed via [`DateTime::with_timezone`]. fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east_opt(0).unwrap()) + src.with_timezone(&src.offset().fix()) } } From 6655649e861b717cd1d86193cb0bd5eec3e2cc75 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 09:44:19 +0200 Subject: [PATCH 242/999] Gate test on `clock` feature --- src/datetime/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index d35a56ff5c..0f03fca57c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -2,9 +2,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; +use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] -use crate::offset::Local; -use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::offset::{Local, Offset}; use crate::oldtime::Duration; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; @@ -985,6 +985,7 @@ fn test_from_naive_date_time_windows() { } #[test] +#[cfg(feature = "clock")] fn test_datetime_local_from_preserves_offset() { let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); From bc58a7db521e73348cdf3be79786fc77aa519fbc Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:25:18 -0700 Subject: [PATCH 243/999] Add various tests for current parsing Add more varying testing for most parsing functions. Tests emphasize whitespace, literals, timezones, and timezone delimiters (colons and whitespace). Add tests for multiple-byte characters and combining characters in and around data and parsing formats. These tests are added to aid humans verifying the next commit that changes parsing behavior. Issue #660 --- src/datetime/mod.rs | 5 + src/datetime/tests.rs | 897 +++++++++++++++++++++++++++++++++++- src/format/parse.rs | 603 +++++++++++++++++++++--- src/format/strftime.rs | 82 ++++ src/naive/date.rs | 33 +- src/naive/datetime/tests.rs | 45 +- src/naive/time/tests.rs | 70 ++- 7 files changed, 1653 insertions(+), 82 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e05625c31f..06d7852e95 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -550,6 +550,9 @@ impl DateTime { /// RFC 2822 is the internet message standard that specifies the /// representation of times in HTTP and email headers. /// + /// The RFC 2822 standard allows arbitrary intermixed whitespace. + /// See [RFC 2822 Appendix A.5] + /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; /// assert_eq!( @@ -557,6 +560,8 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` + /// + /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 0f03fca57c..c212ac71fb 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -344,8 +344,10 @@ fn test_datetime_with_timezone() { } #[test] -fn test_datetime_rfc2822_and_rfc3339() { +fn test_datetime_rfc2822() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 assert_eq!( Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" @@ -354,6 +356,7 @@ fn test_datetime_rfc2822_and_rfc3339() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -376,6 +379,7 @@ fn test_datetime_rfc2822_and_rfc3339() { .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + // seconds 60 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -408,7 +412,130 @@ fn test_datetime_rfc2822_and_rfc3339() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + ), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap() + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap()) + ); + + // bad year + assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() + ); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") + .is_err()); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") + .is_err()); + // wrong day of week separator '.' + assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() + ); +} + +#[test] +fn test_datetime_rfc3339() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) + ); + + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( @@ -434,6 +561,44 @@ fn test_datetime_rfc2822_and_rfc3339() { ) .unwrap()) ); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() + ); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() + ); } #[test] @@ -575,7 +740,94 @@ fn test_datetime_from_str() { } #[test] -fn test_datetime_parse_from_str() { +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {:?}", s); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12t12:12:12Z", // wrong divider 't' + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {:?}", s); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_utc_datetime_from_str() { let ymdhms = |y, m, d, h, n, s, off| { FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() }; @@ -590,6 +842,645 @@ fn test_datetime_parse_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!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_utc_datetime_from_str_with_spaces() { + let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!( + Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), + Ok(dt), + ); + assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + // with varying spaces - should fail + // leading space in data + assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = FixedOffset::east_opt(-9 * 60 * 60) + .unwrap() + .ymd_opt(2013, 8, 9) + .unwrap() + .and_hms_opt(23, 54, 35) + .unwrap(); + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data? + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 PST", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 XXXXX", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + + // + // %z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 --0900", + "%b %d %Y %H:%M:%S -%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 +-0900", + "%b %d %Y %H:%M:%S +%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing newline after timezone + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing colon + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + // trailing colon with space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00: ", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // trailing colon, mismatch space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900::", + "%b %d %Y %H:%M:%S %z::" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %z:00" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %z:00 " + ), + Ok(dt), + ); + + // + // %:z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00:", + "%b %d %Y %H:%M:%S %:z:" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z::" + ), + Ok(dt), + ); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // mismatching colon expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %::z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900\t\n", + "%b %d %Y %H:%M:%S %::z\t\n" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900:", + "%b %d %Y %H:%M:%S %::z:" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900:0", + "%b %d %Y %H:%M:%S :%::z:0" + ), + Ok(dt), + ); + // mismatching colons and spaces + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900: ", + "%b %d %Y %H:%M:%S :%::z::" + ) + .is_err()); + // mismatching colons expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900: 23:54:35", + "%b %d %Y %::z: %H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 :-0900:0 23:54:35", + "%b %d %Y :%::z:0 %H:%M:%S" + ), + Ok(dt), + ); + // mismatching colons expectations mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 :-0900: 23:54:35", + "%b %d %Y :%::z %H:%M:%S" + ) + .is_err()); + // mismatching colons expectations, before end + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %::z " + ) + .is_err()); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %:::z " + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:::z" + ) + .is_err()); + + // + // %::::z + // + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + + // + // %#z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09: ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35+-09", + "%b %d %Y %H:%M:%S+%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35--09", + "%b %d %Y %H:%M:%S-%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -090023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:0023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + // timezone with partial minutes adjacent hours + assert_ne!( + DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), + Ok(dt), + ); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090 ", + "%b %d %Y %H:%M:%S %#z " + ) + .is_err()); + // bad timezone data (partial minutes) mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -090 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:00 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // timezone data ambiguous with hours + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); } #[test] diff --git a/src/format/parse.rs b/src/format/parse.rs index e894f710b9..7d4f56656b 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -536,9 +536,11 @@ fn test_parse() { macro_rules! check { ($fmt:expr, $items:expr; $err:tt) => ( + eprintln!("test_parse: format {:?}", $fmt); assert_eq!(parse_all($fmt, &$items), Err($err)) ); ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + eprintln!("test_parse: format {:?}", $fmt); let mut expected = Parsed::new(); $(expected.$k = Some($v);)* assert_eq!(parse_all($fmt, &$items), Ok(expected)) @@ -549,35 +551,114 @@ fn test_parse() { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); + check!("abc", []; TOO_LONG); + check!("🤠", []; TOO_LONG); // whitespaces check!("", [sp!("")]; ); check!(" ", [sp!("")]; ); check!("\t", [sp!("")]; ); check!(" \n\r \n", [sp!("")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" "), sp!(" ")]; ); + check!("\t", [sp!("\t")]; ); + check!("\t\r", [sp!("\t\r")]; ); + check!("\t\r ", [sp!("\t\r ")]; ); + check!(" \n\r \n", [sp!(" \n\r \n")]; ); + check!("\u{2002}", [sp!("\u{2002}")]; ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [sp!("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; + ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [ + sp!("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + sp!("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") + ]; + ); check!("a", [sp!("")]; TOO_LONG); + check!("a", [sp!(" ")]; TOO_LONG); + // a Space containing a literal cannot match a literal + check!("a", [sp!("a")]; TOO_LONG); + check!("abc", [sp!("")]; TOO_LONG); + check!(" ", [sp!(" ")]; ); + check!(" \t\n", [sp!(" \t")]; ); + check!("", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal + check!("", [lit!("")]; ); check!("", [lit!("a")]; TOO_SHORT); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); + check!("🤠", [lit!("a")]; INVALID); check!("A", [lit!("a")]; INVALID); + check!("a", [lit!("z")]; INVALID); + check!("a", [lit!("🤠")]; TOO_SHORT); + check!("a", [lit!("\u{0363}a")]; TOO_SHORT); + check!("\u{0363}a", [lit!("a")]; INVALID); + check!("\u{0363}a", [lit!("\u{0363}a")]; ); + check!("a", [lit!("ab")]; TOO_SHORT); + check!("xy", [lit!("xy")]; ); + check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("PST", [lit!("PST")]; ); + check!("🤠", [lit!("🤠")]; ); + check!("🤠a", [lit!("🤠"), lit!("a")]; ); + check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); + check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); + // literals can be together check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [lit!("x"), sp!(" "), lit!("y")]; ); + + // whitespaces + literals + check!("a\n", [lit!("a"), sp!("\n")]; ); + check!("\tab\n", [sp!("\t"), lit!("ab"), sp!("\n")]; ); + check!("ab\tcd\ne", [lit!("ab"), sp!("\t"), lit!("cd"), sp!("\n"), lit!("e")]; ); + check!("+1ab\tcd\r\n+,.", [lit!("+1ab"), sp!("\t"), lit!("cd"), sp!("\r\n"), lit!("+,.")]; ); + // whitespace and literals can be intermixed + check!("a\tb", [lit!("a\tb")]; ); + check!("a\tb", [lit!("a"), sp!("\t"), lit!("b")]; ); // numeric check!("1987", [num!(Year)]; year: 1987); check!("1987 ", [num!(Year)]; TOO_LONG); check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed check!("x123", [num!(Year)]; INVALID); + check!("o123", [num!(Year)]; INVALID); check!("2015", [num!(Year)]; year: 2015); check!("0000", [num!(Year)]; year: 0); check!("9999", [num!(Year)]; year: 9999); check!(" \t987", [num!(Year)]; year: 987); + check!(" \t987", [sp!(" \t"), num!(Year)]; year: 987); + check!(" \t987🤠", [sp!(" \t"), num!(Year), lit!("🤠")]; year: 987); + check!("987🤠", [num!(Year), lit!("🤠")]; year: 987); check!("5", [num!(Year)]; year: 5); check!("5\0", [num!(Year)]; TOO_LONG); check!("\x005", [num!(Year)]; INVALID); @@ -587,11 +668,15 @@ fn test_parse() { check!("12345", [num0!(Year), lit!("5")]; year: 1234); check!("12341234", [num!(Year), num!(Year)]; year: 1234); check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1234", [num!(Year), sp!(" "), num!(Year)]; year: 1234); check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234xx1234", [num!(Year), lit!("xx"), num!(Year)]; year: 1234); + check!("1234 x 1234", [num!(Year), sp!(" "), lit!("x"), sp!(" "), num!(Year)]; year: 1234); + check!("1234 x 1235", [num!(Year), sp!(" "), lit!("x"), sp!(" "), lit!("1235")]; year: 1234); // signed numeric check!("-42", [num!(Year)]; year: -42); @@ -602,8 +687,14 @@ fn test_parse() { check!("+42195", [num!(Year)]; year: 42195); check!(" -42195", [num!(Year)]; year: -42195); check!(" +42195", [num!(Year)]; year: 42195); + check!("-42195 ", [num!(Year)]; TOO_LONG); + check!("+42195 ", [num!(Year)]; TOO_LONG); check!(" - 42", [num!(Year)]; INVALID); check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); check!("-", [num!(Year)]; TOO_SHORT); check!("+", [num!(Year)]; TOO_SHORT); @@ -612,8 +703,16 @@ fn test_parse() { check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); + check!("345🤠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); check!(" +345", [num!(Ordinal)]; INVALID); check!(" -345", [num!(Ordinal)]; INVALID); + check!("\t345", [sp!("\t"), num!(Ordinal)]; ordinal: 345); + check!(" +345", [sp!(" "), num!(Ordinal)]; INVALID); + check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields check!("1234 5678", @@ -639,6 +738,7 @@ fn test_parse() { check!("Apr", [fix!(ShortMonthName)]; month: 4); check!("APR", [fix!(ShortMonthName)]; month: 4); check!("ApR", [fix!(ShortMonthName)]; month: 4); + check!("\u{0363}APR", [fix!(ShortMonthName)]; INVALID); check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed check!("A", [fix!(ShortMonthName)]; TOO_SHORT); check!("Sol", [fix!(ShortMonthName)]; INVALID); @@ -676,7 +776,15 @@ fn test_parse() { check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [sp!(" "), fix!(LowerAmPm)]; hour_div_12: 0); + check!("Am🤠", [fix!(LowerAmPm), lit!("🤠")]; hour_div_12: 0); + check!("🤠Am", [lit!("🤠"), fix!(LowerAmPm)]; hour_div_12: 0); + check!("\u{0363}am", [fix!(LowerAmPm)]; INVALID); + check!("\u{0360}am", [fix!(LowerAmPm)]; INVALID); check!(" Am", [fix!(LowerAmPm)]; INVALID); + check!("Am ", [fix!(LowerAmPm)]; TOO_LONG); + check!("a.m.", [fix!(LowerAmPm)]; INVALID); + check!("A.M.", [fix!(LowerAmPm)]; INVALID); check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fix!(LowerAmPm)]; TOO_SHORT); check!("p", [fix!(LowerAmPm)]; TOO_SHORT); @@ -693,10 +801,21 @@ fn test_parse() { check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); + check!(".421951", [fix!(Nanosecond)]; nanosecond: 421_951_000); + check!(".4219512", [fix!(Nanosecond)]; nanosecond: 421_951_200); + check!(".42195123", [fix!(Nanosecond)]; nanosecond: 421_951_230); check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".4219508035", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".42195080354", [fix!(Nanosecond)]; nanosecond: 421_950_803); check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000031", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000035", [fix!(Nanosecond)]; nanosecond: 3); check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000009", [fix!(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); + check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); + check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000); check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(". 4", [fix!(Nanosecond)]; INVALID); @@ -708,76 +827,400 @@ fn test_parse() { check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("4210", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("421🤠", [internal_fix!(Nanosecond3NoDot), lit!("🤠")]; nanosecond: 421_000_000); + check!("🤠421", [lit!("🤠"), internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("123456789", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("1234567", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); + check!("123456789", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("1234567890", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); + check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); + check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -790,10 +1233,31 @@ fn test_parse() { num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", + check!("20150204143705.567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); + check!("20150204143705.567891", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891000); + check!("20150204143705.567891023", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891023); + check!("Mon, 10 Jun 2013 09:32:37 GMT", [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", + [lit!("🤠"), fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), + fix!(ShortMonthName), lit!("🤠"), num!(Year), sp!(" "), num!(Hour), lit!(":"), + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT"), lit!("🤠")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", @@ -814,6 +1278,22 @@ fn test_parse() { check!("12345678901234.56789", [num!(Timestamp), fix!(Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + + // docstring examples from `impl str::FromStr` + check!("2000-01-02T03:04:05Z", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); + check!("2000-01-02 03:04:05Z", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), sp!(" "), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); } #[cfg(test)] @@ -830,6 +1310,8 @@ fn test_rfc2822() { ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace + ("Tue, 20 Jan 2015\t17:35:20\t-0800\t\t(UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok("Tue, 20 Jan 2015 17:35:20 -0800"), @@ -876,6 +1358,7 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -963,11 +1446,26 @@ fn test_rfc3339() { ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { @@ -982,6 +1480,7 @@ fn test_rfc3339() { // Test against test data above for &(date, checkdate) in testdates.iter() { + eprintln!("test_rfc3339: date {:?}, expect {:?}", date, checkdate); let d = rfc3339_to_datetime(date); // parse a date let dt = match d { // did we get a value? diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 24bae20c0a..fc97da3c0f 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -513,12 +513,32 @@ impl<'a> Iterator for StrftimeItems<'a> { 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!( @@ -532,12 +552,63 @@ fn test_strftime_items() { 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]); @@ -557,6 +628,10 @@ fn test_strftime_items() { 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]); } @@ -666,6 +741,13 @@ fn test_strftime_docs() { assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); + + // 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")] diff --git a/src/naive/date.rs b/src/naive/date.rs index c5b6fe0d74..a94985d816 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2828,16 +2828,20 @@ mod tests { "360-02-29", "0360-02-29", "2015-2 -18", + "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; + eprintln!("d {:?} (NaiveDate)", d); let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, @@ -2845,6 +2849,7 @@ mod tests { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; + eprintln!("d_ {:?} (NaiveDate)", d_); assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ @@ -2857,13 +2862,27 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 202bdb34d3..da36481f27 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -197,11 +197,16 @@ fn test_datetime_timestamp() { fn test_datetime_from_str() { // valid cases let valid = [ - "2015-2-18T23:16:9.15", + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { + eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -226,16 +231,34 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 62c46a247e..9ebf174948 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -235,9 +235,37 @@ fn test_date_from_str() { " 4 : 3 : 2.1 ", " 09:08:07 ", " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", "23:59:60.373929310237", ]; for &s in &valid { + eprintln!("test_time_parse_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -262,15 +290,30 @@ fn test_date_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8", // missing data + "15:8:x", // missing data, invalid data + "15:8:9x", // missing data, invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] @@ -281,6 +324,15 @@ fn test_time_parse_from_str() { Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } From 1f45d8ed3fd001f97719d23ec71194b90cd64f6f Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sat, 10 Sep 2022 00:14:59 -0700 Subject: [PATCH 244/999] Exact whitespace parsing Be exact about allowed whitespace around and between data and parsing formats for all parsing. Except RFC 2822 which explicitly allows arbitrary whitespace. Issue #660 --- src/datetime/tests.rs | 372 ++++++++++++++++++++---------------- src/format/mod.rs | 12 +- src/format/parse.rs | 154 +++++++++------ src/format/scan.rs | 59 ++++++ src/naive/date.rs | 48 +++-- src/naive/datetime/tests.rs | 29 ++- src/naive/time/mod.rs | 3 - src/naive/time/tests.rs | 21 +- 8 files changed, 432 insertions(+), 266 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index c212ac71fb..3995daa675 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -240,6 +240,94 @@ fn test_datetime_sub_months() { ); } +// local helper function to easily create a DateTime +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::microseconds(micro)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::nanoseconds(nano)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -352,10 +440,6 @@ fn test_datetime_rfc2822() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - assert_eq!( - Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); // timezone +05 assert_eq!( edt.from_local_datetime( @@ -368,17 +452,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); // seconds 60 assert_eq!( edt.from_local_datetime( @@ -391,17 +464,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), @@ -412,36 +474,35 @@ fn test_datetime_rfc2822() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" + ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), + "Thu, 19 Feb 2015 00:20:32 +0500" ); assert_eq!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) ); // many varying whitespace intermixed assert_eq!( DateTime::::parse_from_rfc2822( - "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" ), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) ); // example from RFC 2822 Appendix A.5. assert_eq!( DateTime::::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap() + Ok( + ymdhms( + &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), + 1969, 2, 13, 23, 32, 0, + ) ) ); // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" @@ -449,12 +510,9 @@ fn test_datetime_rfc2822() { DateTime::::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap()) + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) ); // bad year @@ -479,68 +537,72 @@ fn test_datetime_rfc2822() { #[test] fn test_datetime_rfc3339() { - let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) ); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) ); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) ); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + DateTime::::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) ); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -551,8 +613,8 @@ fn test_datetime_rfc3339() { ); assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -561,10 +623,7 @@ fn test_datetime_rfc3339() { ) .unwrap()) ); - assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() @@ -748,18 +807,6 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06-00:00", "2001-02-03T04:05:06-01:00", "2012-12-12T12:12:12Z", - "2012 -12-12T12:12:12Z", - "2012 -12-12T12:12:12Z", - "2012- 12-12T12:12:12Z", - "2012- 12-12T12:12:12Z", - "2012-12-12T 12:12:12Z", - "2012-12-12T12 :12:12Z", - "2012-12-12T12 :12:12Z", - "2012-12-12T12: 12:12Z", - "2012-12-12T12: 12:12Z", - "2012-12-12T12 : 12:12Z", - "2012-12-12T12:12:12Z ", - " 2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", "+2015-2-18T23:16:09.153Z", @@ -818,9 +865,21 @@ fn test_parse_datetime_utc() { "2012-12-12T12:12:12ZZ", // trailing literal 'Z' "+802701-12-12T12:12:12Z", // invalid year (out of bounds) "+ 2012-12-12T12:12:12Z", // invalid space before year + "2012 -12-12T12:12:12Z", // space after year + "2012 -12-12T12:12:12Z", // multi space after year + "2012- 12-12T12:12:12Z", // space after year divider + "2012- 12-12T12:12:12Z", // multi space after year divider + "2012-12-12T 12:12:12Z", // space after date-time divider + "2012-12-12T12 :12:12Z", // space after hour + "2012-12-12T12 :12:12Z", // multi space after hour + "2012-12-12T12: 12:12Z", // space before minute + "2012-12-12T12: 12:12Z", // multi space before minute + "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider + "2012-12-12T12:12:12Z ", // trailing space + " 2012-12-12T12:12:12Z", // leading space " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; - for &s in &invalid { + for &s in invalid.iter() { eprintln!("test_parse_datetime_utc invalid {:?}", s); assert!(s.parse::>().is_err()); } @@ -828,12 +887,12 @@ fn test_parse_datetime_utc() { #[test] fn test_utc_datetime_from_str() { - let ymdhms = |y, m, d, h, n, s, off| { - FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() - }; + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); assert_eq!( - DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) + DateTime::::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") @@ -845,53 +904,38 @@ fn test_utc_datetime_from_str() { assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west_opt(10 * 3600) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(13, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -900,7 +944,7 @@ fn test_utc_datetime_from_str() { #[test] fn test_utc_datetime_from_str_with_spaces() { - let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); // with varying spaces - should succeed assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); @@ -916,12 +960,23 @@ fn test_utc_datetime_from_str_with_spaces() { assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); // with varying spaces - should fail + // leading whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err()); + // trailing whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); + // extra mid-string whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // mismatched leading whitespace + assert!(Utc.datetime_from_str("\tAug 09 2013 23:54:35", "\n%b %d %Y %H:%M:%S").is_err()); + // mismatched trailing whitespace + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err()); + // mismatched mid-string whitespace + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err()); + // trailing whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); + // trailing whitespace (newline) in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); // leading space in data assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); // trailing space in data @@ -936,12 +991,7 @@ fn test_utc_datetime_from_str_with_spaces() { #[test] fn test_datetime_parse_from_str() { - let dt = FixedOffset::east_opt(-9 * 60 * 60) - .unwrap() - .ymd_opt(2013, 8, 9) - .unwrap() - .and_hms_opt(23, 54, 35) - .unwrap(); + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); // timezone variations @@ -1025,13 +1075,11 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); // trailing colon assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00:", @@ -1391,13 +1439,11 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09: ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09: ", + "%b %d %Y %H:%M:%S %#z " + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 23:54:35+-09", @@ -1412,20 +1458,16 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:00 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 -090023:54:35", diff --git a/src/format/mod.rs b/src/format/mod.rs index af7eb4cf15..4ab96ed98e 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -208,27 +208,27 @@ pub enum Fixed { /// /// It does not support parsing, its use in the parser is an immediate failure. TimezoneName, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `+00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00:00` to `+24:00:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetDoubleColon, /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24` to `+24`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetTripleColon, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `Z`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, + /// In the parser, the colon may be omitted, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. diff --git a/src/format/parse.rs b/src/format/parse.rs index 7d4f56656b..c65afa36cd 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -42,6 +42,10 @@ fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<() }) } +/// Parse an RFC 2822 format datetime +/// e.g. `Fri, 21 Nov 1997 09:55:06 -0600` +/// +/// This function allows arbitrary intermixed whitespace per RFC 2822 appendix A.5 fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ @@ -237,7 +241,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st /// /// - Padding-agnostic (for numeric items). /// The [`Pad`](./enum.Pad.html) field is completely ignored, -/// so one can prepend any number of whitespace then any number of zeroes before numbers. +/// so one can prepend any number of zeroes before numbers. /// /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> @@ -292,13 +296,37 @@ where s = &s[prefix.len()..]; } - Item::Space(_) => { - s = s.trim_left(); + Item::Space(item_space) => { + for expect in item_space.chars() { + let actual = match s.chars().next() { + Some(c) => c, + None => { + return Err((s, TOO_SHORT)); + } + }; + if expect != actual { + return Err((s, INVALID)); + } + // advance `s` forward 1 char + s = scan::s_next(s); + } } #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedSpace(_) => { - s = s.trim_left(); + Item::OwnedSpace(ref item_space) => { + for expect in item_space.chars() { + let actual = match s.chars().next() { + Some(c) => c, + None => { + return Err((s, TOO_SHORT)); + } + }; + if expect != actual { + return Err((s, INVALID)); + } + // advance `s` forward 1 char + s = scan::s_next(s); + } } Item::Numeric(ref spec, ref _pad) => { @@ -331,7 +359,6 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -424,27 +451,24 @@ where | &TimezoneOffsetDoubleColon | &TimezoneOffsetTripleColon | &TimezoneOffset => { - let offset = try_consume!(scan::timezone_offset( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = try_consume!(scan::timezone_offset(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = + try_consume!(scan::timezone_offset_zulu(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } + &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = + try_consume!(scan::timezone_offset_permissive(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -468,15 +492,13 @@ where } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. /// -/// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2000-01-02T03:04:05Z".parse::>(); +/// "2000-01-02 03:04:05Z".parse::>(); /// ``` impl str::FromStr for DateTime { type Err = ParseError; @@ -484,25 +506,19 @@ impl str::FromStr for DateTime { fn from_str(s: &str) -> ParseResult> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -524,7 +540,6 @@ impl str::FromStr for DateTime { #[cfg(test)] #[test] fn test_parse() { - use super::IMPOSSIBLE; use super::*; // workaround for Rust issue #22255 @@ -556,19 +571,34 @@ fn test_parse() { // whitespaces check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); check!(" ", [sp!(" ")]; ); check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!("")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!("", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" "), sp!(" ")]; ); + check!("\t", [sp!("")]; TOO_LONG); + check!(" \n\r \n", [sp!("")]; TOO_LONG); check!("\t", [sp!("\t")]; ); + check!("\t", [sp!(" ")]; INVALID); + check!(" ", [sp!("\t")]; INVALID); check!("\t\r", [sp!("\t\r")]; ); check!("\t\r ", [sp!("\t\r ")]; ); + check!("\t \r", [sp!("\t \r")]; ); + check!(" \t\r", [sp!(" \t\r")]; ); check!(" \n\r \n", [sp!(" \n\r \n")]; ); + check!(" \t\n", [sp!(" \t")]; TOO_LONG); + check!(" \n\t", [sp!(" \t\n")]; INVALID); check!("\u{2002}", [sp!("\u{2002}")]; ); // most unicode whitespace characters check!( @@ -584,16 +614,13 @@ fn test_parse() { ]; ); check!("a", [sp!("")]; TOO_LONG); - check!("a", [sp!(" ")]; TOO_LONG); - // a Space containing a literal cannot match a literal - check!("a", [sp!("a")]; TOO_LONG); + check!("a", [sp!(" ")]; INVALID); + // a Space containing a literal can match a literal, but this should not be done + check!("a", [sp!("a")]; ); check!("abc", [sp!("")]; TOO_LONG); - check!(" ", [sp!(" ")]; ); - check!(" \t\n", [sp!(" \t")]; ); - check!("", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" "), sp!(" ")]; ); + check!("abc", [sp!(" ")]; INVALID); + check!(" abc", [sp!("")]; TOO_LONG); + check!(" abc", [sp!(" ")]; TOO_LONG); // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" @@ -634,7 +661,7 @@ fn test_parse() { // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [lit!("x"), sp!(""), lit!("y")]; INVALID); check!("x y", [lit!("x"), sp!(" "), lit!("y")]; ); // whitespaces + literals @@ -655,7 +682,7 @@ fn test_parse() { check!("2015", [num!(Year)]; year: 2015); check!("0000", [num!(Year)]; year: 0); check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); + check!(" \t987", [num!(Year)]; INVALID); check!(" \t987", [sp!(" \t"), num!(Year)]; year: 987); check!(" \t987🤠", [sp!(" \t"), num!(Year), lit!("🤠")]; year: 987); check!("987🤠", [num!(Year), lit!("🤠")]; year: 987); @@ -667,9 +694,9 @@ fn test_parse() { check!("12345", [nums!(Year), lit!("5")]; year: 1234); check!("12345", [num0!(Year), lit!("5")]; year: 1234); check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1234", [num!(Year), num!(Year)]; INVALID); check!("1234 1234", [num!(Year), sp!(" "), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); + check!("1234 1235", [num!(Year), num!(Year)]; INVALID); check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); @@ -685,8 +712,10 @@ fn test_parse() { check!("+0042", [num!(Year)]; year: 42); check!("-42195", [num!(Year)]; year: -42195); check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); + check!(" -42195", [num!(Year)]; INVALID); + check!(" +42195", [num!(Year)]; INVALID); + check!(" -42195", [num!(Year)]; INVALID); + check!(" +42195", [num!(Year)]; INVALID); check!("-42195 ", [num!(Year)]; TOO_LONG); check!("+42195 ", [num!(Year)]; TOO_LONG); check!(" - 42", [num!(Year)]; INVALID); @@ -702,7 +731,8 @@ fn test_parse() { check!("345", [num!(Ordinal)]; ordinal: 345); check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!(" 345", [num!(Ordinal)]; INVALID); + check!("345 ", [num!(Ordinal)]; TOO_LONG); check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); @@ -715,21 +745,27 @@ fn test_parse() { check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields + check!("1234 5678", [num!(Year), num!(IsoYear)]; INVALID); check!("1234 5678", - [num!(Year), num!(IsoYear)]; + [num!(Year), sp!(" "), num!(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + INVALID); + check!("12 34🤠56 78", + [num!(YearDiv100), sp!(" "), num!(YearMod100), + lit!("🤠"), num!(IsoYearDiv100), sp!(" "), num!(IsoYearMod100)]; year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; + [num!(Month), sp!(" "), num!(Day), sp!(" "), num!(WeekFromSun), sp!(" "), + num!(WeekFromMon), sp!(" "), num!(IsoWeek), sp!(" "), num!(NumDaysFromSun)]; month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + [num!(WeekdayFromMon), sp!(" "), num!(Ordinal), sp!(" "), num!(Hour12)]; weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + [num!(Hour), sp!(" "), num!(Minute), sp!(" "), num!(Second), sp!(" "), + num!(Nanosecond), sp!(" "), num!(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); @@ -918,9 +954,9 @@ fn test_parse() { check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" +12:34", [fix!(TimezoneOffset)]; INVALID); + check!(" -12:34", [fix!(TimezoneOffset)]; INVALID); + check!("\t -12:34", [fix!(TimezoneOffset)]; INVALID); check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); @@ -1013,7 +1049,7 @@ fn test_parse() { check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); diff --git a/src/format/scan.rs b/src/format/scan.rs index 705ccf9095..f25ce9fde2 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -198,6 +198,27 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } } +/// Returns slice remaining after first char. +/// If <=1 chars in `s` then return an empty slice +pub(super) fn s_next(s: &str) -> &str { + match s.char_indices().nth(1) { + Some((offset, _)) => &s[offset..], + None => { + // one or zero chars in `s`, return empty string + &s[s.len()..] + } + } +} + +/// If the first `char` is whitespace then consume it and return `s`. +/// Else return `s`. +pub(super) fn trim1(s: &str) -> &str { + match s.chars().next() { + Some(c) if c.is_whitespace() => s_next(s), + Some(_) | None => s, + } +} + /// Consumes any number (including zero) of colon or spaces. pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) @@ -415,3 +436,41 @@ fn test_rfc2822_comments() { ); } } + +#[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"); +} diff --git a/src/naive/date.rs b/src/naive/date.rs index a94985d816..718b27f787 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2009,13 +2009,10 @@ impl str::FromStr for NaiveDate { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -2820,14 +2817,14 @@ mod tests { // valid cases let valid = [ "-0000000123456-1-2", - " -123456 - 1 - 2 ", + "-123456-1-2", "-12345-1-2", "-1234-12-31", "-7-6-5", "350-2-28", "360-02-29", "0360-02-29", - "2015-2 -18", + "2015-2-18", "2015-02-18", "+70-2-18", "+70000-2-18", @@ -2863,21 +2860,30 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ - "", // empty - "x", // invalid - "Fri, 09 Aug 2013 GMT", // valid date, wrong format - "Sat Jun 30 2012", // valid date, wrong format - "1441497364.649", // valid datetime, wrong format - "+1441497364.649", // valid datetime, wrong format - "+1441497364", // valid datetime, wrong format - "2014/02/03", // valid date, wrong format - "2014", // datetime missing data - "2014-01", // datetime missing data - "2014-01-00", // invalid day - "2014-11-32", // invalid day - "2014-13-01", // invalid month - "2014-13-57", // invalid month, day - "9999999-9-9", // invalid year (out of bounds) + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "2001 -02-03", // space after year + "2001- 02-03", // space before month + "2001 - 02-03", // space around year-month divider + "2001-02 -03", // space after month + "2001-02- 03", // space before day + "2001-02 - 03", // space around month-day divider + "2001-02-03 ", // trailing space + " 2001-02-03", // leading space + " -123456 - 1 - 2 ", // many spaces + "9999999-9-9", // invalid year (out of bounds) ]; for &s in &invalid { eprintln!("test_date_from_str invalid {:?}", s); @@ -2893,7 +2899,7 @@ mod tests { Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), + NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u=%Y-%j"), Ok(ymd(2015, 2, 2)) ); assert_eq!( diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index da36481f27..810da47015 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -203,7 +203,6 @@ fn test_datetime_from_str() { "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", "+82701-05-6T15:9:60.898989898989", - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { eprintln!("test_parse_naivedatetime valid {:?}", s); @@ -232,9 +231,9 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ - "", // empty - "x", // invalid / missing data - "15", // missing data + "", // empty + "x", // invalid / missing data + "15", // missing data "15:8:9", // looks like a time (invalid date) "15-8-9", // looks like a date (invalid) "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format @@ -249,13 +248,27 @@ fn test_datetime_from_str() { "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal "2012-123-12T12:12:12", // invalid month + "2012 -12-12T12:12:12", // space after year + "2012 -12-12T12:12:12", // multi space after year + "2012- 12-12T12:12:12", // space before month + "2012- 12-12T12:12:12", // multi space before month + "2012-12-12 T12:12:12", // space after day + "2012-12-12T 12:12:12", // space after date-time divider + "2012-12-12T12 :12:12", // space after hour + "2012-12-12T12 :12:12", // multi space after hour + "2012-12-12T12: 12:12", // space before minute + "2012-12-12T12: 12:12", // multi space before minute + "2012-12-12T12 : 12:12", // space around hour-minute divider + "2012-12-12T12:12:12 ", // trailing space + " 2012-12-12T12:12:12", // leading space "2012-12-12t12:12:12", // bad divider 't' "2012-12-12 12:12:12", // missing divider 'T' "2012-12-12T12:12:12Z", // trailing char 'Z' "+ 82701-123-12T12:12:12", // strange year, invalid month "+802701-123-12T12:12:12", // out-of-bound year, invalid month + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", // many spaces ]; - for &s in &invalid { + for &s in invalid.iter() { eprintln!("test_datetime_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } @@ -272,8 +285,12 @@ fn test_datetime_parse_from_str() { NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) ); // ignore offset + assert!( + // intermixed whitespace + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S").is_err() + ); assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u %H%M%S"), Ok(ymdhms(2015, 2, 2, 0, 0, 0)) ); assert_eq!( diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 444c8dc1ff..e9e6de2142 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1278,14 +1278,11 @@ impl str::FromStr for NaiveTime { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), ]; let mut parsed = Parsed::new(); diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 9ebf174948..c14c02fb72 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -232,9 +232,6 @@ fn test_date_from_str() { "0:0:0", "0:0:0.0000000", "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", "01:02:03", "4:3:2.1", "9:8:7", @@ -303,6 +300,18 @@ fn test_date_from_str() { "1441497364.649", // valid datetime, not a NaiveTime "+1441497364.649", // valid datetime, not a NaiveTime "+1441497364", // valid datetime, not a NaiveTime + "01 :02:03", // space after hour + "01: 02:03", // space before minute + "01 : 02:03", // space around hour-minute divider + "01:02 :03", // space after minute + "01:02: 03", // space before second + "01:02 : 03", // space around minute-second divider + "01:02:03 .456", // space after second + "01:02:03. 456", // space before fraction + "01:02:03 ", // trailing space + "01:02:03.456 ", // trailing space + " 01:02:03", // leading space + " 4 : 3 : 2.1 ", // spaces intermixed throughout "001:02:03", // invalid hour "01:002:03", // invalid minute "01:02:003", // invalid second @@ -330,9 +339,9 @@ fn test_time_parse_from_str() { NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), Ok(hms(12, 59, 0)) ); - assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); - assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); - assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_err()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_err()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_err()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } From eb94e813d2f73dfc5563ec9533815d241ec1124c Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:39:18 -0700 Subject: [PATCH 245/999] Add tests for timezone parsing whitespace and colon Add tests for timezone parsing infinite whitespace and colons. Prepatory tests for next commit around constraining timezone and colons. --- src/datetime/tests.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3995daa675..2a49652721 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -806,6 +806,11 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", "2001-02-03T04:05:06-01:00", + "2001-02-03T04:05:06-01: 00", + "2001-02-03T04:05:06-01 :00", + "2001-02-03T04:05:06-01 : 00", + "2001-02-03T04:05:06-01 : 00", + "2001-02-03T04:05:06-01 : :00", "2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", From f90d4037f08967fb2b43af67c299a46a7a63f77f Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Tue, 18 Oct 2022 14:34:34 -0700 Subject: [PATCH 246/999] Constrain timezone separator colon strings Constrain timezone middle-colon separator string from infinite intermixed whitespace and colons to possible patterns `":"`, `" "`, `" :"`, `": "`, or `" : "`. A reasonable trade-off of previous extreme flexibility for a little flexbility and concise input. Issue #660 --- src/datetime/tests.rs | 46 +++++++++++----------- src/format/parse.rs | 85 ++++++++++++++++++++-------------------- src/format/scan.rs | 90 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 154 insertions(+), 67 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2a49652721..71d4bed436 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -805,12 +805,11 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06Z", "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-00 00", "2001-02-03T04:05:06-01:00", "2001-02-03T04:05:06-01: 00", "2001-02-03T04:05:06-01 :00", "2001-02-03T04:05:06-01 : 00", - "2001-02-03T04:05:06-01 : 00", - "2001-02-03T04:05:06-01 : :00", "2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", @@ -882,6 +881,8 @@ fn test_parse_datetime_utc() { "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider "2012-12-12T12:12:12Z ", // trailing space " 2012-12-12T12:12:12Z", // leading space + "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing + "2001-02-03T04:05:06-01 : :00", // invalid timezone spacing " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; for &s in invalid.iter() { @@ -1109,13 +1110,11 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -0900::", @@ -1182,13 +1181,11 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %:z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); // timezone data hs too many colons assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00:", @@ -1239,13 +1236,16 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %::z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); // wrong timezone data assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09", diff --git a/src/format/parse.rs b/src/format/parse.rs index c65afa36cd..56e7bb7bc6 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -452,14 +452,15 @@ where | &TimezoneOffsetTripleColon | &TimezoneOffset => { s = scan::trim1(s); - let offset = try_consume!(scan::timezone_offset(s, scan::colon_or_space)); + let offset = + try_consume!(scan::timezone_offset(s, scan::maybe_colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { s = scan::trim1(s); let offset = - try_consume!(scan::timezone_offset_zulu(s, scan::colon_or_space)); + try_consume!(scan::timezone_offset_zulu(s, scan::maybe_colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -467,8 +468,10 @@ where val: InternalInternal::TimezoneOffsetPermissive, }) => { s = scan::trim1(s); - let offset = - try_consume!(scan::timezone_offset_permissive(s, scan::colon_or_space)); + let offset = try_consume!(scan::timezone_offset_permissive( + s, + scan::maybe_colon_or_space + )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -925,14 +928,14 @@ fn test_parse() { check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; INVALID); check!("12:34", [fix!(TimezoneOffset)]; INVALID); check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12: :34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12::::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; INVALID); check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); @@ -960,11 +963,11 @@ fn test_parse() { check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 :34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); check!("", [fix!(TimezoneOffset)]; TOO_SHORT); @@ -1036,14 +1039,14 @@ fn test_parse() { check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -1111,17 +1114,17 @@ fn test_parse() { check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1198,22 +1201,22 @@ fn test_parse() { check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); diff --git a/src/format/scan.rs b/src/format/scan.rs index f25ce9fde2..f1b5fafe87 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -219,9 +219,46 @@ pub(super) fn trim1(s: &str) -> &str { } } -/// Consumes any number (including zero) of colon or spaces. -pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) +/// Is `s.next()` whitespace? +/// Helper function to `maybe_colon_or_space`. +fn next_is_whitespace(s: &str) -> bool { + s.chars().next().map(|c| c.is_whitespace()).unwrap_or_default() +} + +/// Allow a colon with possible one-character whitespace padding. +/// Consumes zero or one of these leading patterns: +/// `":"`, `" "`, `" :"`, `": "`, or `" : "`. +/// Always returns `Ok(s)`. +pub(super) fn maybe_colon_or_space(mut s: &str) -> ParseResult<&str> { + if s.is_empty() { + // nothing consumed + return Ok(s); + } + + if s.starts_with(':') { + s = s_next(s); + if next_is_whitespace(s) { + s = s_next(s); + } + // consumed `":"` or `": "` + return Ok(s); + } else if !next_is_whitespace(s) { + return Ok(s); + } + + s = s_next(s); + if s.starts_with(':') { + s = s_next(s); + } else { + // consumed `" "` + return Ok(s); + } + if next_is_whitespace(s) { + s = s_next(s); + } + + // consumed `" :"` or `" : "` + Ok(s) } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -474,3 +511,50 @@ fn test_trim1() { assert_eq!(trim1("😼"), "😼"); assert_eq!(trim1("😼b"), "😼b"); } + +#[test] +fn test_next_is_whitespace() { + assert!(!next_is_whitespace("")); + assert!(!next_is_whitespace("a")); + assert!(!next_is_whitespace("😼😼")); + assert!(next_is_whitespace(" ")); + assert!(next_is_whitespace("\t\t")); + assert!(next_is_whitespace("\ta\t")); + assert!(next_is_whitespace("\t😼\t")); +} + +#[test] +fn test_maybe_colon_or_space() { + assert_eq!(maybe_colon_or_space(""), Ok("")); + assert_eq!(maybe_colon_or_space(" "), Ok("")); + assert_eq!(maybe_colon_or_space("\n"), Ok("")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space("\t\t\t\t"), Ok("\t\t\t")); + assert_eq!(maybe_colon_or_space(":"), Ok("")); + assert_eq!(maybe_colon_or_space(" :"), Ok("")); + assert_eq!(maybe_colon_or_space(": "), Ok("")); + assert_eq!(maybe_colon_or_space(" : "), Ok("")); + assert_eq!(maybe_colon_or_space(" : "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" :"), Ok(" :")); + assert_eq!(maybe_colon_or_space(" : "), Ok(" : ")); + assert_eq!(maybe_colon_or_space(" :: "), Ok(": ")); + assert_eq!(maybe_colon_or_space(" : : "), Ok(": ")); + assert_eq!(maybe_colon_or_space("😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space("😸😸"), Ok("😸😸")); + assert_eq!(maybe_colon_or_space("😸:"), Ok("😸:")); + assert_eq!(maybe_colon_or_space("😸 "), Ok("😸 ")); + assert_eq!(maybe_colon_or_space(" 😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space(":😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space(":😸 "), Ok("😸 ")); + assert_eq!(maybe_colon_or_space(" :😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space(" :😸 "), Ok("😸 ")); + assert_eq!(maybe_colon_or_space(" :😸:"), Ok("😸:")); + assert_eq!(maybe_colon_or_space(": 😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space(": 😸"), Ok(" 😸")); + assert_eq!(maybe_colon_or_space(": :😸"), Ok(":😸")); + assert_eq!(maybe_colon_or_space(" : 😸"), Ok("😸")); + assert_eq!(maybe_colon_or_space(" ::😸"), Ok(":😸")); + assert_eq!(maybe_colon_or_space(" :: 😸"), Ok(": 😸")); +} From 1a233aa75de3663307690b35cb9cf2f69c598b3f Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:31:21 -0700 Subject: [PATCH 247/999] Constrain timezone parsing Constrain timezone parsing further. Only allow optional colon char `:` between timezone hour offset and timezone minute offset. Issue #660 --- src/datetime/tests.rs | 80 +++++++++++++++-------------------- src/format/parse.rs | 42 +++++++++---------- src/format/scan.rs | 97 +++++++++++-------------------------------- 3 files changed, 79 insertions(+), 140 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 71d4bed436..5c34973c30 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -805,11 +805,7 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06Z", "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", - "2001-02-03T04:05:06-00 00", "2001-02-03T04:05:06-01:00", - "2001-02-03T04:05:06-01: 00", - "2001-02-03T04:05:06-01 :00", - "2001-02-03T04:05:06-01 : 00", "2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", @@ -881,6 +877,10 @@ fn test_parse_datetime_utc() { "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider "2012-12-12T12:12:12Z ", // trailing space " 2012-12-12T12:12:12Z", // leading space + "2001-02-03T04:05:06-00 00", // invalid timezone spacing + "2001-02-03T04:05:06-01: 00", // invalid timezone spacing + "2001-02-03T04:05:06-01 :00", // invalid timezone spacing + "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing "2001-02-03T04:05:06-01 : :00", // invalid timezone spacing " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format @@ -1033,13 +1033,11 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00", @@ -1047,13 +1045,11 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 23:54:35 --0900", @@ -1154,27 +1150,21 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00:", - "%b %d %Y %H:%M:%S %:z:" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00:", + "%b %d %Y %H:%M:%S %:z:" + ) + .is_err()); // wrong timezone data assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09", @@ -1223,13 +1213,11 @@ fn test_datetime_parse_from_str() { ), Ok(dt), ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); // mismatching colon expectations assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00:00", diff --git a/src/format/parse.rs b/src/format/parse.rs index 56e7bb7bc6..c68d725447 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -453,14 +453,14 @@ where | &TimezoneOffset => { s = scan::trim1(s); let offset = - try_consume!(scan::timezone_offset(s, scan::maybe_colon_or_space)); + try_consume!(scan::timezone_offset(s, scan::consume_colon_maybe)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { s = scan::trim1(s); let offset = - try_consume!(scan::timezone_offset_zulu(s, scan::maybe_colon_or_space)); + try_consume!(scan::timezone_offset_zulu(s, scan::consume_colon_maybe)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -470,7 +470,7 @@ where s = scan::trim1(s); let offset = try_consume!(scan::timezone_offset_permissive( s, - scan::maybe_colon_or_space + scan::consume_colon_maybe )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -927,7 +927,7 @@ fn test_parse() { check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; INVALID); check!("+12 34", [fix!(TimezoneOffset)]; INVALID); check!("12:34", [fix!(TimezoneOffset)]; INVALID); check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); @@ -954,15 +954,15 @@ fn test_parse() { check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); check!("#12:34", [fix!(TimezoneOffset)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; INVALID); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); check!(" +12:34", [fix!(TimezoneOffset)]; INVALID); check!(" -12:34", [fix!(TimezoneOffset)]; INVALID); check!("\t -12:34", [fix!(TimezoneOffset)]; INVALID); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 :34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); @@ -1035,10 +1035,10 @@ fn test_parse() { check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); @@ -1117,18 +1117,18 @@ fn test_parse() { check!("+12::34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; INVALID); check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); @@ -1200,11 +1200,11 @@ fn test_parse() { check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); diff --git a/src/format/scan.rs b/src/format/scan.rs index f1b5fafe87..2debb5a4e4 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -219,17 +219,9 @@ pub(super) fn trim1(s: &str) -> &str { } } -/// Is `s.next()` whitespace? -/// Helper function to `maybe_colon_or_space`. -fn next_is_whitespace(s: &str) -> bool { - s.chars().next().map(|c| c.is_whitespace()).unwrap_or_default() -} - -/// Allow a colon with possible one-character whitespace padding. -/// Consumes zero or one of these leading patterns: -/// `":"`, `" "`, `" :"`, `": "`, or `" : "`. +/// Consumes one colon char `:` if it is at the front of `s`. /// Always returns `Ok(s)`. -pub(super) fn maybe_colon_or_space(mut s: &str) -> ParseResult<&str> { +pub(super) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> { if s.is_empty() { // nothing consumed return Ok(s); @@ -237,27 +229,9 @@ pub(super) fn maybe_colon_or_space(mut s: &str) -> ParseResult<&str> { if s.starts_with(':') { s = s_next(s); - if next_is_whitespace(s) { - s = s_next(s); - } - // consumed `":"` or `": "` - return Ok(s); - } else if !next_is_whitespace(s) { - return Ok(s); - } - - s = s_next(s); - if s.starts_with(':') { - s = s_next(s); - } else { - // consumed `" "` - return Ok(s); - } - if next_is_whitespace(s) { - s = s_next(s); + // consumed `':'` } - // consumed `" :"` or `" : "` Ok(s) } @@ -513,48 +487,25 @@ fn test_trim1() { } #[test] -fn test_next_is_whitespace() { - assert!(!next_is_whitespace("")); - assert!(!next_is_whitespace("a")); - assert!(!next_is_whitespace("😼😼")); - assert!(next_is_whitespace(" ")); - assert!(next_is_whitespace("\t\t")); - assert!(next_is_whitespace("\ta\t")); - assert!(next_is_whitespace("\t😼\t")); -} - -#[test] -fn test_maybe_colon_or_space() { - assert_eq!(maybe_colon_or_space(""), Ok("")); - assert_eq!(maybe_colon_or_space(" "), Ok("")); - assert_eq!(maybe_colon_or_space("\n"), Ok("")); - assert_eq!(maybe_colon_or_space(" "), Ok(" ")); - assert_eq!(maybe_colon_or_space(" "), Ok(" ")); - assert_eq!(maybe_colon_or_space(" "), Ok(" ")); - assert_eq!(maybe_colon_or_space("\t\t\t\t"), Ok("\t\t\t")); - assert_eq!(maybe_colon_or_space(":"), Ok("")); - assert_eq!(maybe_colon_or_space(" :"), Ok("")); - assert_eq!(maybe_colon_or_space(": "), Ok("")); - assert_eq!(maybe_colon_or_space(" : "), Ok("")); - assert_eq!(maybe_colon_or_space(" : "), Ok(" ")); - assert_eq!(maybe_colon_or_space(" :"), Ok(" :")); - assert_eq!(maybe_colon_or_space(" : "), Ok(" : ")); - assert_eq!(maybe_colon_or_space(" :: "), Ok(": ")); - assert_eq!(maybe_colon_or_space(" : : "), Ok(": ")); - assert_eq!(maybe_colon_or_space("😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space("😸😸"), Ok("😸😸")); - assert_eq!(maybe_colon_or_space("😸:"), Ok("😸:")); - assert_eq!(maybe_colon_or_space("😸 "), Ok("😸 ")); - assert_eq!(maybe_colon_or_space(" 😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space(":😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space(":😸 "), Ok("😸 ")); - assert_eq!(maybe_colon_or_space(" :😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space(" :😸 "), Ok("😸 ")); - assert_eq!(maybe_colon_or_space(" :😸:"), Ok("😸:")); - assert_eq!(maybe_colon_or_space(": 😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space(": 😸"), Ok(" 😸")); - assert_eq!(maybe_colon_or_space(": :😸"), Ok(":😸")); - assert_eq!(maybe_colon_or_space(" : 😸"), Ok("😸")); - assert_eq!(maybe_colon_or_space(" ::😸"), Ok(":😸")); - assert_eq!(maybe_colon_or_space(" :: 😸"), Ok(": 😸")); +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(" :😸")); } From 8fd7ca24bb4b87b7156a97670016efd0c94ff3b7 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:38:50 -0700 Subject: [PATCH 248/999] cargo fmt updates Run `cargo fmt`. Changes to `src/datetime/tests.rs`. --- src/datetime/tests.rs | 58 +++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 5c34973c30..cf54a76775 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -852,35 +852,35 @@ fn test_parse_datetime_utc() { "+1441497364", // valid datetime, wrong format "+1441497364Z", // valid datetime, wrong format "2014/02/03 04:05:06Z", // valid datetime, wrong format - "2001-02-03T04:05:0600:00", // valid datetime, timezone too close - "2015-15-15T15:15:15Z", // invalid datetime - "2012-12-12T12:12:12x", // invalid timezone - "2012-123-12T12:12:12Z", // invalid month - "2012-12-77T12:12:12Z", // invalid day - "2012-12-12T26:12:12Z", // invalid hour - "2012-12-12T12:61:12Z", // invalid minute - "2012-12-12T12:12:62Z", // invalid second - "2012-12-12 T12:12:12Z", // space after date - "2012-12-12t12:12:12Z", // wrong divider 't' - "2012-12-12T12:12:12ZZ", // trailing literal 'Z' - "+802701-12-12T12:12:12Z", // invalid year (out of bounds) - "+ 2012-12-12T12:12:12Z", // invalid space before year - "2012 -12-12T12:12:12Z", // space after year - "2012 -12-12T12:12:12Z", // multi space after year - "2012- 12-12T12:12:12Z", // space after year divider - "2012- 12-12T12:12:12Z", // multi space after year divider - "2012-12-12T 12:12:12Z", // space after date-time divider - "2012-12-12T12 :12:12Z", // space after hour - "2012-12-12T12 :12:12Z", // multi space after hour - "2012-12-12T12: 12:12Z", // space before minute - "2012-12-12T12: 12:12Z", // multi space before minute - "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider - "2012-12-12T12:12:12Z ", // trailing space - " 2012-12-12T12:12:12Z", // leading space - "2001-02-03T04:05:06-00 00", // invalid timezone spacing - "2001-02-03T04:05:06-01: 00", // invalid timezone spacing - "2001-02-03T04:05:06-01 :00", // invalid timezone spacing - "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12t12:12:12Z", // wrong divider 't' + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + "2012 -12-12T12:12:12Z", // space after year + "2012 -12-12T12:12:12Z", // multi space after year + "2012- 12-12T12:12:12Z", // space after year divider + "2012- 12-12T12:12:12Z", // multi space after year divider + "2012-12-12T 12:12:12Z", // space after date-time divider + "2012-12-12T12 :12:12Z", // space after hour + "2012-12-12T12 :12:12Z", // multi space after hour + "2012-12-12T12: 12:12Z", // space before minute + "2012-12-12T12: 12:12Z", // multi space before minute + "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider + "2012-12-12T12:12:12Z ", // trailing space + " 2012-12-12T12:12:12Z", // leading space + "2001-02-03T04:05:06-00 00", // invalid timezone spacing + "2001-02-03T04:05:06-01: 00", // invalid timezone spacing + "2001-02-03T04:05:06-01 :00", // invalid timezone spacing + "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing "2001-02-03T04:05:06-01 : :00", // invalid timezone spacing " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format From 2a6a07d9676a9c62aff096e8ac1bbcd2cab025bd Mon Sep 17 00:00:00 2001 From: Tormod Gjeitnes Hellen Date: Wed, 8 Mar 2023 18:23:56 +0100 Subject: [PATCH 249/999] Make eligible functions const. --- src/format/locales.rs | 16 ++++++++-------- src/format/scan.rs | 2 +- src/month.rs | 8 ++++---- src/naive/internals.rs | 6 +++--- src/naive/time/mod.rs | 6 +++--- src/offset/fixed.rs | 4 ++-- src/offset/local/tz_info/parser.rs | 2 +- src/offset/local/tz_info/rule.rs | 8 ++++---- src/offset/local/tz_info/timezone.rs | 6 +++--- src/round.rs | 2 +- src/weekday.rs | 4 ++-- 11 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/format/locales.rs b/src/format/locales.rs index f7b4bbde5b..d6fecc978b 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,33 +1,33 @@ use pure_rust_locales::{locale_match, Locale}; -pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] { +pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::ABMON) } -pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] { +pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::MON) } -pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] { +pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::ABDAY) } -pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] { +pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::DAY) } -pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] { +pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::AM_PM) } -pub(crate) fn d_fmt(locale: Locale) -> &'static str { +pub(crate) const fn d_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::D_FMT) } -pub(crate) fn d_t_fmt(locale: Locale) -> &'static str { +pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::D_T_FMT) } -pub(crate) fn t_fmt(locale: Locale) -> &'static str { +pub(crate) const fn t_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT) } diff --git a/src/format/scan.rs b/src/format/scan.rs index 2debb5a4e4..50315fef60 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -254,7 +254,7 @@ fn timezone_offset_internal( where F: FnMut(&str) -> ParseResult<&str>, { - fn digits(s: &str) -> ParseResult<(u8, u8)> { + const fn digits(s: &str) -> ParseResult<(u8, u8)> { let b = s.as_bytes(); if b.len() < 2 { Err(TOO_SHORT) diff --git a/src/month.rs b/src/month.rs index 6a104bd415..b95002dd70 100644 --- a/src/month.rs +++ b/src/month.rs @@ -66,7 +66,7 @@ impl Month { /// `m.succ()`: | `February` | `March` | `...` | `January` #[inline] #[must_use] - pub fn succ(&self) -> Month { + pub const fn succ(&self) -> Month { match *self { Month::January => Month::February, Month::February => Month::March, @@ -90,7 +90,7 @@ impl Month { /// `m.pred()`: | `December` | `January` | `...` | `November` #[inline] #[must_use] - pub fn pred(&self) -> Month { + pub const fn pred(&self) -> Month { match *self { Month::January => Month::December, Month::February => Month::January, @@ -114,7 +114,7 @@ impl Month { /// `m.number_from_month()`: | 1 | 2 | `...` | 12 #[inline] #[must_use] - pub fn number_from_month(&self) -> u32 { + pub const fn number_from_month(&self) -> u32 { match *self { Month::January => 1, Month::February => 2, @@ -139,7 +139,7 @@ impl Month { /// assert_eq!(Month::January.name(), "January") /// ``` #[must_use] - pub fn name(&self) -> &'static str { + pub const fn name(&self) -> &'static str { match *self { Month::January => "January", Month::February => "February", diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 2c954f7abb..e296653621 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -301,7 +301,7 @@ impl Of { } #[inline] - pub(super) fn with_ordinal(&self, ordinal: u32) -> Option { + pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { if ordinal > 366 { return None; } @@ -405,7 +405,7 @@ impl Mdf { } #[inline] - pub(super) fn with_month(&self, month: u32) -> Option { + pub(super) const fn with_month(&self, month: u32) -> Option { if month > 12 { return None; } @@ -421,7 +421,7 @@ impl Mdf { } #[inline] - pub(super) fn with_day(&self, day: u32) -> Option { + pub(super) const fn with_day(&self, day: u32) -> Option { if day > 31 { return None; } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index e9e6de2142..acc51c4a79 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -241,7 +241,7 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { + pub const fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { NaiveTime::from_hms_nano_opt(hour, min, sec, 0) } @@ -366,7 +366,7 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { + pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { return None; } @@ -409,7 +409,7 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { + pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { if secs >= 86_400 || nano >= 2_000_000_000 { return None; } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 02691da7ab..2b7a37aacd 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -55,7 +55,7 @@ impl FixedOffset { /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` #[must_use] - pub fn east_opt(secs: i32) -> Option { + pub const fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) } else { @@ -89,7 +89,7 @@ impl FixedOffset { /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` #[must_use] - pub fn west_opt(secs: i32) -> Option { + pub const fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) } else { diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 7bcf307e8c..47cc0377e4 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -237,7 +237,7 @@ impl<'a> Cursor<'a> { } /// Returns `true` if data is remaining - pub(crate) fn is_empty(&self) -> bool { + pub(crate) const fn is_empty(&self) -> bool { self.remaining.is_empty() } diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 5b8634eece..369e317a42 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -127,7 +127,7 @@ pub(super) struct AlternateTime { impl AlternateTime { /// Construct a transition rule representing alternate local time types - fn new( + const fn new( std: LocalTimeType, dst: LocalTimeType, dst_start: RuleDay, @@ -504,7 +504,7 @@ impl RuleDay { } /// Construct a transition rule day represented by a zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account - fn julian_0(julian_day_0: u16) -> Result { + const fn julian_0(julian_day_0: u16) -> Result { if julian_day_0 > 365 { return Err(Error::TransitionRule("invalid rule day julian day")); } @@ -737,7 +737,7 @@ const DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH: [i64; 12] = /// * `year`: Year /// * `month`: Month in `[1, 12]` /// * `month_day`: Day of the month in `[1, 31]` -pub(crate) fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 { +pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 { let is_leap_year = is_leap_year(year); let year = year as i64; @@ -768,7 +768,7 @@ pub(crate) fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> } /// Check if a year is a leap year -pub(crate) fn is_leap_year(year: i32) -> bool { +pub(crate) const fn is_leap_year(year: i32) -> bool { year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 352250e0bd..2fe24d2e44 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -374,7 +374,7 @@ impl<'a> TimeZoneRef<'a> { } /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone - fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { + const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { let mut unix_leap_time = unix_time; let mut i = 0; @@ -563,7 +563,7 @@ impl LocalTimeType { } /// Construct a local time type with the specified UTC offset in seconds - pub(super) fn with_offset(ut_offset: i32) -> Result { + pub(super) const fn with_offset(ut_offset: i32) -> Result { if ut_offset == i32::min_value() { return Err(Error::LocalTimeType("invalid UTC offset")); } @@ -608,7 +608,7 @@ fn find_tz_file(path: impl AsRef) -> Result { } #[inline] -fn saturating_abs(v: i32) -> i32 { +const fn saturating_abs(v: i32) -> i32 { if v.is_positive() { v } else if v == i32::min_value() { diff --git a/src/round.rs b/src/round.rs index b95f245708..6f19efe841 100644 --- a/src/round.rs +++ b/src/round.rs @@ -75,7 +75,7 @@ where } // Return the maximum span in nanoseconds for the target number of digits. -fn span_for_digits(digits: u16) -> u32 { +const fn span_for_digits(digits: u16) -> u32 { // fast lookup form of: 10^(9-min(9,digits)) match digits { 0 => 1_000_000_000, diff --git a/src/weekday.rs b/src/weekday.rs index db6d7e03c4..38fd1a6876 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -57,7 +57,7 @@ impl Weekday { /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` #[inline] #[must_use] - pub fn succ(&self) -> Weekday { + pub const fn succ(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Tue, Weekday::Tue => Weekday::Wed, @@ -76,7 +76,7 @@ impl Weekday { /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` #[inline] #[must_use] - pub fn pred(&self) -> Weekday { + pub const fn pred(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Sun, Weekday::Tue => Weekday::Mon, From 0367c76a9f212eaf3ddfc9fa64f6ed142df6505a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 22 Apr 2023 08:11:05 +0200 Subject: [PATCH 250/999] Simplify from_timestamp_millis, from_timestamp_micros --- src/naive/datetime/mod.rs | 71 ++++----------------------------------- 1 file changed, 6 insertions(+), 65 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 671e9eddf2..51ef60fbed 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -5,7 +5,6 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::convert::TryFrom; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -22,7 +21,6 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; -use core::cmp::Ordering; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -42,11 +40,6 @@ mod tests; /// touching that call when we are already sure that it WILL overflow... const MAX_SECS_BITS: usize = 44; -/// Number of nanoseconds in a millisecond -const NANOS_IN_MILLISECOND: u32 = 1_000_000; -/// Number of nanoseconds in a second -const NANOS_IN_SECOND: u32 = 1000 * NANOS_IN_MILLISECOND; - /// The minimum possible `NaiveDateTime`. #[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; @@ -87,32 +80,6 @@ pub struct NaiveDateTime { time: NaiveTime, } -/// The unit of a timestamp expressed in fractions of a second. -/// Currently either milliseconds or microseconds. -/// -/// This is a private type, used in the implementation of -/// [NaiveDateTime::from_timestamp_millis] and [NaiveDateTime::from_timestamp_micros]. -#[derive(Clone, Copy, Debug)] -enum TimestampUnit { - Millis, - Micros, -} - -impl TimestampUnit { - fn per_second(self) -> u32 { - match self { - TimestampUnit::Millis => 1_000, - TimestampUnit::Micros => 1_000_000, - } - } - fn nanos_per(self) -> u32 { - match self { - TimestampUnit::Millis => 1_000_000, - TimestampUnit::Micros => 1_000, - } - } -} - impl NaiveDateTime { /// Makes a new `NaiveDateTime` from date and time components. /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) @@ -180,7 +147,9 @@ impl NaiveDateTime { #[inline] #[must_use] pub fn from_timestamp_millis(millis: i64) -> Option { - Self::from_timestamp_unit(millis, TimestampUnit::Millis) + let secs = millis.div_euclid(1000); + let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; + NaiveDateTime::from_timestamp_opt(secs, nsecs) } /// Creates a new [NaiveDateTime] from microseconds since the UNIX epoch. @@ -207,7 +176,9 @@ impl NaiveDateTime { #[inline] #[must_use] pub fn from_timestamp_micros(micros: i64) -> Option { - Self::from_timestamp_unit(micros, TimestampUnit::Micros) + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + NaiveDateTime::from_timestamp_opt(secs, nsecs) } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, @@ -928,36 +899,6 @@ impl NaiveDateTime { pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; /// The maximum possible `NaiveDateTime`. pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; - - /// Creates a new [NaiveDateTime] from milliseconds or microseconds since the UNIX epoch. - /// - /// This is a private function used by [from_timestamp_millis] and [from_timestamp_micros]. - #[inline] - fn from_timestamp_unit(value: i64, unit: TimestampUnit) -> Option { - let (secs, subsecs) = - (value / i64::from(unit.per_second()), value % i64::from(unit.per_second())); - - match subsecs.cmp(&0) { - Ordering::Less => { - // in the case where our subsec part is negative, then we are actually in the earlier second - // hence we subtract one from the seconds part, and we then add a whole second worth of nanos - // to our nanos part. Due to the use of u32 datatype, it is more convenient to subtract - // the absolute value of the subsec nanos from a whole second worth of nanos - let nsecs = u32::try_from(subsecs.abs()).ok()? * unit.nanos_per(); - NaiveDateTime::from_timestamp_opt( - secs.checked_sub(1)?, - NANOS_IN_SECOND.checked_sub(nsecs)?, - ) - } - Ordering::Equal => NaiveDateTime::from_timestamp_opt(secs, 0), - Ordering::Greater => { - // convert the subsec millis into nanosecond scale so they can be supplied - // as the nanoseconds parameter - let nsecs = u32::try_from(subsecs).ok()? * unit.nanos_per(); - NaiveDateTime::from_timestamp_opt(secs, nsecs) - } - } - } } impl Datelike for NaiveDateTime { From 3bfb6abbbffcbb0caec606d0ef991441c2bf23a5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 15:44:26 +0200 Subject: [PATCH 251/999] Fix test_leap_second during DST transition --- src/offset/local/mod.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 041a913b8a..18b3961130 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -246,18 +246,24 @@ mod tests { // issue #123 let today = Utc::now().date_naive(); - let dt = today.and_hms_milli_opt(1, 2, 59, 1000).unwrap(); - let timestr = dt.time().to_string(); - // the OS API may or may not support the leap second, - // but there are only two sensible options. - assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr); - - let dt = today.and_hms_milli_opt(1, 2, 3, 1234).unwrap(); - let timestr = dt.time().to_string(); - assert!( - timestr == "01:02:03.234" || timestr == "01:02:04.234", - "unexpected timestr {:?}", - timestr - ); + if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) { + let timestr = dt.time().to_string(); + // the OS API may or may not support the leap second, + // but there are only two sensible options. + assert!( + timestr == "15:02:60" || timestr == "15:03:00", + "unexpected timestr {:?}", + timestr + ); + } + + if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) { + let timestr = dt.time().to_string(); + assert!( + timestr == "15:02:03.234" || timestr == "15:02:04.234", + "unexpected timestr {:?}", + timestr + ); + } } } From e3f591fba3dbcb42a9561bcf7a29960785641c86 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 23 Apr 2023 18:53:58 +0200 Subject: [PATCH 252/999] Remove num-integer dependency --- Cargo.toml | 1 - src/format/mod.rs | 10 ++++------ src/format/parsed.rs | 10 ++++------ src/naive/date.rs | 5 ++++- src/naive/datetime/mod.rs | 4 ++-- src/naive/internals.rs | 6 +++--- src/naive/time/mod.rs | 7 ++++--- src/offset/fixed.rs | 7 ++++--- src/oldtime.rs | 24 +----------------------- 9 files changed, 26 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c3a02409d..23ffda6ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ __doctest = [] [dependencies] time = { version = "0.1.43", optional = true } -num-integer = { version = "0.1.36", default-features = false } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } diff --git a/src/format/mod.rs b/src/format/mod.rs index 4ab96ed98e..bc10d4e913 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -512,8 +512,6 @@ fn format_inner( ) -> fmt::Result { let locale = Locales::new(locale); - use num_integer::{div_floor, mod_floor}; - match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), #[cfg(any(feature = "alloc", feature = "std", test))] @@ -527,11 +525,11 @@ fn format_inner( let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))), - YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))), + YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))), - IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))), + IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), Month => (2, date.map(|d| i64::from(d.month()))), Day => (2, date.map(|d| i64::from(d.day()))), WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index b5fb8e699f..2f31e000cc 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,7 +4,6 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use num_integer::div_rem; use num_traits::ToPrimitive; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; @@ -311,7 +310,8 @@ impl Parsed { if y < 0 { return Err(OUT_OF_RANGE); } - let (q_, r_) = div_rem(y, 100); + let q_ = y / 100; + let r_ = y % 100; if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { Ok(Some(y)) } else { @@ -346,8 +346,7 @@ impl Parsed { let verify_ymd = |date: NaiveDate| { let year = date.year(); let (year_div_100, year_mod_100) = if year >= 0 { - let (q, r) = div_rem(year, 100); - (Some(q), Some(r)) + (Some(year / 100), Some(year % 100)) } else { (None, None) // they should be empty to be consistent }; @@ -367,8 +366,7 @@ impl Parsed { let isoweek = week.week(); let weekday = date.weekday(); let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { - let (q, r) = div_rem(isoyear, 100); - (Some(q), Some(r)) + (Some(isoyear / 100), Some(isoyear % 100)) } else { (None, None) // they should be empty to be consistent }; diff --git a/src/naive/date.rs b/src/naive/date.rs index 718b27f787..84a24052a0 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -9,7 +9,6 @@ use core::convert::TryFrom; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; -use num_integer::div_mod_floor; use num_traits::ToPrimitive; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -2037,6 +2036,10 @@ impl Default for NaiveDate { } } +fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { + (val.div_euclid(div), val.rem_euclid(div)) +} + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 51ef60fbed..f4ef6956da 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -9,7 +9,6 @@ use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; -use num_integer::div_mod_floor; use num_traits::ToPrimitive; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -210,7 +209,8 @@ impl NaiveDateTime { #[inline] #[must_use] pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { - let (days, secs) = div_mod_floor(secs, 86_400); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); let date = days .to_i32() .and_then(|days| days.checked_add(719_163)) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index e296653621..d0a6e01471 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -17,7 +17,6 @@ use crate::Weekday; use core::{fmt, i32}; -use num_integer::{div_rem, mod_floor}; use num_traits::FromPrimitive; /// The internal date representation. This also includes the packed `Mdf` value. @@ -95,7 +94,8 @@ static YEAR_DELTAS: [u8; 401] = [ ]; pub(super) fn cycle_to_yo(cycle: u32) -> (u32, u32) { - let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365); + let mut year_mod_400 = cycle / 365; + let mut ordinal0 = cycle % 365; let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]); if ordinal0 < delta { year_mod_400 -= 1; @@ -116,7 +116,7 @@ impl YearFlags { #[inline] #[must_use] pub fn from_year(year: i32) -> YearFlags { - let year = mod_floor(year, 400); + let year = year.rem_euclid(400); YearFlags::from_year_mod_400(year) } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index acc51c4a79..a79b778c8a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -8,7 +8,6 @@ use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; -use num_integer::div_mod_floor; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -762,8 +761,10 @@ impl NaiveTime { /// Returns a triple of the hour, minute and second numbers. fn hms(&self) -> (u32, u32, u32) { - let (mins, sec) = div_mod_floor(self.secs, 60); - let (hour, min) = div_mod_floor(mins, 60); + let sec = self.secs % 60; + let mins = self.secs / 60; + let min = mins % 60; + let hour = mins / 60; (hour, min, sec) } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 2b7a37aacd..475c3e99dc 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -6,7 +6,6 @@ use core::fmt; use core::ops::{Add, Sub}; -use num_integer::div_mod_floor; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -142,8 +141,10 @@ impl fmt::Debug for FixedOffset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let offset = self.local_minus_utc; let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; - let (mins, sec) = div_mod_floor(offset, 60); - let (hour, min) = div_mod_floor(mins, 60); + let sec = offset.rem_euclid(60); + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); if sec == 0 { write!(f, "{}{:02}:{:02}", sign, hour, min) } else { diff --git a/src/oldtime.rs b/src/oldtime.rs index 8fde78670d..e27be7db63 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -462,31 +462,9 @@ impl Error for OutOfRangeError { } } -// Copied from libnum #[inline] const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { - (div_floor_64(this, other), mod_floor_64(this, other)) -} - -#[inline] -const fn div_floor_64(this: i64, other: i64) -> i64 { - match div_rem_64(this, other) { - (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, - (d, _) => d, - } -} - -#[inline] -const fn mod_floor_64(this: i64, other: i64) -> i64 { - match this % other { - r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, - r => r, - } -} - -#[inline] -const fn div_rem_64(this: i64, other: i64) -> (i64, i64) { - (this / other, this % other) + (this.div_euclid(other), this.rem_euclid(other)) } #[cfg(feature = "arbitrary")] From 5dd7961c86e63403333a9e5566b1e981cf309672 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 20:05:00 +0200 Subject: [PATCH 253/999] No need to allow `clippy::manual-non-exhaustive` --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 61bf164c8b..b32548e9ab 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: cargo fmt --check -- --color=always cargo fmt --check --manifest-path fuzz/Cargo.toml - run: | - cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + cargo clippy --color=always -- -D warnings cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings env: RUSTFLAGS: "-Dwarnings" From fc8513b88ce43d8f84b638048c6ffd8805256b1b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 11:17:19 +0200 Subject: [PATCH 254/999] [CI] Lint Windows target --- .github/workflows/lint.yml | 13 +++++++------ src/offset/local/windows.rs | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b32548e9ab..109f659b64 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,13 +12,14 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc - uses: Swatinem/rust-cache@v2 - - run: | - cargo fmt --check -- --color=always - cargo fmt --check --manifest-path fuzz/Cargo.toml - - run: | - cargo clippy --color=always -- -D warnings - cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings + - 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 -- -D warnings + - run: cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings env: RUSTFLAGS: "-Dwarnings" diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 7d9f4515e9..6475929fe4 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -96,7 +96,8 @@ impl LocalSysTime { let date = NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).unwrap(); - let time = NaiveTime::from_hms(st.wHour as u32, st.wMinute as u32, st.wSecond as u32); + let time = + NaiveTime::from_hms_opt(st.wHour as u32, st.wMinute as u32, st.wSecond as u32).unwrap(); let offset = FixedOffset::east_opt(self.offset).unwrap(); DateTime::from_utc(date.and_time(time) - offset, offset) From 40ed3bf063bb89542ccd67bc2673b44afac77485 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 19:54:28 +0200 Subject: [PATCH 255/999] Update github actions --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 109f659b64..af57ed5d8b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7569e3260e..5c8d8d53fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test --all-features --color=always -- --color=always @@ -25,7 +25,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test --lib --all-features --color=always -- --color=always @@ -40,7 +40,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.56.1 @@ -56,7 +56,7 @@ jobs: rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} @@ -73,7 +73,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 @@ -88,7 +88,7 @@ jobs: target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -103,12 +103,12 @@ jobs: target: [wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: node-version: "12" - run: | @@ -123,7 +123,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown @@ -138,7 +138,7 @@ jobs: - x86_64-sun-solaris runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} @@ -146,7 +146,7 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly - run: cargo +nightly doc --all-features --no-deps env: From 3b3432e94c65f558897cfb79367742679f1672e3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 21:06:56 +0200 Subject: [PATCH 256/999] Add link checker to CI --- .github/workflows/lint.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index af57ed5d8b..17d0966dc4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,3 +28,11 @@ jobs: steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 + + check-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-deadlinks + - run: RUSTFLAGS="--cfg docsrs" cargo deadlinks -- --features=serde From d70eb453a974b7a16a60022ac309313f20e91857 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 12 May 2023 21:07:38 +0200 Subject: [PATCH 257/999] Fix broken links --- src/naive/date.rs | 15 +++++++++------ src/naive/datetime/mod.rs | 20 +++++++++++--------- src/naive/time/mod.rs | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 84a24052a0..c5520a8771 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -139,8 +139,7 @@ impl Days { } /// ISO 8601 calendar date without timezone. -/// Allows for every [proleptic Gregorian date](#calendar-date) -/// from Jan 1, 262145 BCE to Dec 31, 262143 CE. +/// Allows for every [proleptic Gregorian date] from Jan 1, 262145 BCE to Dec 31, 262143 CE. /// Also supports the conversion from ISO 8601 ordinal and week date. /// /// # Calendar Date @@ -186,6 +185,8 @@ impl Days { /// The year number is the same as that of the [calendar date](#calendar-date). /// /// This is currently the internal format of Chrono's date types. +/// +/// [proleptic Gregorian date]: crate::NaiveDate#calendar-date #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveDate { @@ -1654,8 +1655,7 @@ impl Datelike for NaiveDate { /// An addition of `Duration` to `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_add_signed`](#method.checked_add_signed) to detect that. +/// Panics on underflow or overflow. Use [`NaiveDate::checked_add_signed`] to detect that. /// /// # Example /// @@ -1673,6 +1673,8 @@ impl Datelike for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*4 + 1), from_ymd(2018, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*400 + 97), from_ymd(2414, 1, 1)); /// ``` +/// +/// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed impl Add for NaiveDate { type Output = NaiveDate; @@ -1762,8 +1764,7 @@ impl Sub for NaiveDate { /// rounding to the closest integral number of days towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. /// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_sub_signed`](#method.checked_sub_signed) to detect that. +/// Panics on underflow or overflow. Use [`NaiveDate::checked_sub_signed`] to detect that. /// /// # Example /// @@ -1781,6 +1782,8 @@ impl Sub for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*4 + 1), from_ymd(2010, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*400 + 97), from_ymd(1614, 1, 1)); /// ``` +/// +/// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed impl Sub for NaiveDate { type Output = NaiveDate; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index f4ef6956da..9fa732a652 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -902,7 +902,7 @@ impl NaiveDateTime { } impl Datelike for NaiveDateTime { - /// Returns the year number in the [calendar date](./index.html#calendar-date). + /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). /// /// See also the [`NaiveDate::year`] method. /// @@ -1377,10 +1377,9 @@ impl Timelike for NaiveDateTime { /// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. /// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. /// /// Panics on underflow or overflow. Use [`NaiveDateTime::checked_add_signed`] /// to detect that. @@ -1424,6 +1423,8 @@ impl Timelike for NaiveDateTime { /// assert_eq!(leap + Duration::days(1), /// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1488,10 +1489,9 @@ impl Add for NaiveDateTime { /// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. /// It is the same as the addition with a negated `Duration`. /// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. /// /// Panics on underflow or overflow. Use [`NaiveDateTime::checked_sub_signed`] /// to detect that. @@ -1533,6 +1533,8 @@ impl Add for NaiveDateTime { /// assert_eq!(leap - Duration::days(1), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling impl Sub for NaiveDateTime { type Output = NaiveDateTime; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index a79b778c8a..af0a632b47 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -987,10 +987,9 @@ impl Timelike for NaiveTime { /// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. /// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. /// /// # Example /// @@ -1033,6 +1032,8 @@ impl Timelike for NaiveTime { /// assert_eq!(leap + Duration::seconds(-10), from_hmsm(3, 5, 50, 300)); /// assert_eq!(leap + Duration::days(1), from_hmsm(3, 5, 59, 300)); /// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling impl Add for NaiveTime { type Output = NaiveTime; @@ -1053,10 +1054,9 @@ impl AddAssign for NaiveTime { /// In particular the addition ignores integral number of days. /// It is the same as the addition with a negated `Duration`. /// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. /// /// # Example /// @@ -1094,6 +1094,8 @@ impl AddAssign for NaiveTime { /// assert_eq!(leap - Duration::seconds(60), from_hmsm(3, 5, 0, 300)); /// assert_eq!(leap - Duration::days(1), from_hmsm(3, 6, 0, 300)); /// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling impl Sub for NaiveTime { type Output = NaiveTime; From 7bf98de15870ab61b01ab7b942a7d37f1b432c54 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 15 May 2023 14:47:07 +0200 Subject: [PATCH 258/999] [CI] Test rustdoc --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 17d0966dc4..9f52f477e4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,3 +36,6 @@ jobs: - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - run: RUSTFLAGS="--cfg docsrs" cargo deadlinks -- --features=serde + - run: cargo doc --all-features --no-deps + env: + RUSTDOCFLAGS: -Dwarnings From eb927846b6a840ff26690c8762d5b1f90a37882b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 19 Apr 2023 18:59:30 +0200 Subject: [PATCH 259/999] Add DateTime::fixed_offset --- src/datetime/mod.rs | 8 ++++++++ src/datetime/tests.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 06d7852e95..940d7b8a35 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -325,6 +325,14 @@ impl DateTime { tz.from_utc_datetime(&self.datetime) } + /// Fix the offset from UTC to its current value, dropping the associated timezone information. + /// This it useful for converting a generic `DateTime` to `DateTime`. + #[inline] + #[must_use] + pub fn fixed_offset(&self) -> DateTime { + self.with_timezone(&self.offset().fix()) + } + /// Adds given `Duration` to the current date and time. /// /// Returns `None` when it will result in overflow. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index cf54a76775..ebf0359701 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1920,4 +1920,18 @@ fn test_datetime_local_from_preserves_offset() { let datetime_fixed: DateTime = datetime.into(); assert_eq!(&offset, datetime_fixed.offset()); + assert_eq!(datetime.fixed_offset(), datetime_fixed); +} + +#[test] +fn test_datetime_fixed_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Utc.from_utc_datetime(&naivedatetime); + let fixed_utc = FixedOffset::east_opt(0).unwrap(); + assert_eq!(datetime.fixed_offset(), fixed_utc.from_local_datetime(&naivedatetime).unwrap()); + + let fixed_offset = FixedOffset::east_opt(3600).unwrap(); + let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap(); + assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); } From b0983c25d90901b62950a74b0c6076dfc604335a Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Sat, 27 Aug 2022 23:16:46 +1000 Subject: [PATCH 260/999] Remove dependency on num-traits Co-authored-by: Dirkjan Ochtman --- src/format/parsed.rs | 42 +++++++++++++++++++-------------- src/lib.rs | 29 +++++++++++++++++++++++ src/month.rs | 49 +++++++++++++++++++++++++++++++++++---- src/naive/date.rs | 5 ++-- src/naive/datetime/mod.rs | 6 ++--- src/naive/internals.rs | 6 ++--- src/weekday.rs | 33 +++++++++++++++++++++----- 7 files changed, 133 insertions(+), 37 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 2f31e000cc..d51959a018 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,7 +4,7 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use num_traits::ToPrimitive; +use core::convert::TryFrom; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; @@ -136,7 +136,7 @@ impl Parsed { /// Tries to set the [`year`](#structfield.year) field from given value. #[inline] pub fn set_year(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.year, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value. @@ -145,7 +145,7 @@ impl Parsed { if value < 0 { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value. @@ -154,13 +154,13 @@ impl Parsed { if value < 0 { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value. #[inline] pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoyear, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. @@ -169,7 +169,10 @@ impl Parsed { if value < 0 { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent( + &mut self.isoyear_div_100, + i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, + ) } /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value. @@ -178,31 +181,34 @@ impl Parsed { if value < 0 { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent( + &mut self.isoyear_mod_100, + i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, + ) } /// Tries to set the [`month`](#structfield.month) field from given value. #[inline] pub fn set_month(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.month, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value. #[inline] pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_sun, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value. #[inline] pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_mon, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value. #[inline] pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoweek, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`weekday`](#structfield.weekday) field from given value. @@ -214,13 +220,13 @@ impl Parsed { /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.ordinal, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`day`](#structfield.day) field from given value. #[inline] pub fn set_day(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.day, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value. @@ -244,7 +250,7 @@ impl Parsed { /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { - let v = value.to_u32().ok_or(OUT_OF_RANGE)?; + let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?; set_if_consistent(&mut self.hour_div_12, v / 12)?; set_if_consistent(&mut self.hour_mod_12, v % 12)?; Ok(()) @@ -253,19 +259,19 @@ impl Parsed { /// Tries to set the [`minute`](#structfield.minute) field from given value. #[inline] pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.minute, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`second`](#structfield.second) field from given value. #[inline] pub fn set_second(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.second, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. #[inline] pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, value.to_u32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. @@ -277,7 +283,7 @@ impl Parsed { /// Tries to set the [`offset`](#structfield.offset) field from given value. #[inline] pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.offset, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Returns a parsed naive date out of given fields. diff --git a/src/lib.rs b/src/lib.rs index 8472a6fc3c..d5f1fa8405 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -422,6 +422,8 @@ mod oldtime; // this reexport is to aid the transition and should not be in the prelude! pub use oldtime::{Duration, OutOfRangeError}; +use core::fmt; + #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] use doc_comment::doctest; @@ -517,3 +519,30 @@ pub use naive::__BenchYearFlags; pub mod serde { pub use super::datetime::serde::*; } + +/// Out of range error type used in various converting APIs +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct OutOfRange { + _private: (), +} + +impl OutOfRange { + const fn new() -> OutOfRange { + OutOfRange { _private: () } + } +} + +impl fmt::Display for OutOfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "out of range") + } +} + +impl fmt::Debug for OutOfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "out of range") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OutOfRange {} diff --git a/src/month.rs b/src/month.rs index b95002dd70..8340cbc23f 100644 --- a/src/month.rs +++ b/src/month.rs @@ -1,8 +1,10 @@ -use core::fmt; +use core::{convert::TryFrom, fmt}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::OutOfRange; + /// The month of the year. /// /// This enum is just a convenience implementation. @@ -10,11 +12,11 @@ use rkyv::{Archive, Deserialize, Serialize}; /// /// It is possible to convert from a date to a month independently /// ``` -/// use num_traits::FromPrimitive; +/// # use std::convert::TryFrom; /// use chrono::prelude::*; /// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); /// // `2019-10-28T09:10:11Z` -/// let month = Month::from_u32(date.month()); +/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok(); /// assert_eq!(month, Some(Month::October)) /// ``` /// Or from a Month to an integer usable by dates @@ -157,6 +159,28 @@ impl Month { } } +impl TryFrom for Month { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(OutOfRange::new()), + } + } +} + impl num_traits::FromPrimitive for Month { /// Returns an `Option` from a i64, assuming a 1-index, January = 1. /// @@ -325,8 +349,25 @@ mod month_serde { #[cfg(test)] mod tests { + use core::convert::TryFrom; + use super::Month; - use crate::{Datelike, TimeZone, Utc}; + 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_primitive_parse() { diff --git a/src/naive/date.rs b/src/naive/date.rs index c5520a8771..4c7f383f7e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -9,7 +9,6 @@ use core::convert::TryFrom; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; -use num_traits::ToPrimitive; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -1030,7 +1029,7 @@ impl NaiveDate { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_add(rhs.num_days().to_i32()?)?; + let cycle = (cycle as i32).checked_add(i32::try_from(rhs.num_days()).ok()?)?; let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; @@ -1062,7 +1061,7 @@ impl NaiveDate { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_sub(rhs.num_days().to_i32()?)?; + let cycle = (cycle as i32).checked_sub(i32::try_from(rhs.num_days()).ok()?)?; let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 9fa732a652..ab9fda089d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -5,11 +5,11 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; +use core::convert::TryFrom; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; -use num_traits::ToPrimitive; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -211,8 +211,8 @@ impl NaiveDateTime { pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { let days = secs.div_euclid(86_400); let secs = secs.rem_euclid(86_400); - let date = days - .to_i32() + let date = i32::try_from(days) + .ok() .and_then(|days| days.checked_add(719_163)) .and_then(NaiveDate::from_num_days_from_ce_opt); let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index d0a6e01471..9249e840a0 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -16,8 +16,8 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] use crate::Weekday; +use core::convert::TryFrom; use core::{fmt, i32}; -use num_traits::FromPrimitive; /// The internal date representation. This also includes the packed `Mdf` value. pub(super) type DateImpl = i32; @@ -319,7 +319,7 @@ impl Of { #[inline] pub(super) fn weekday(&self) -> Weekday { let Of(of) = *self; - Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap() + Weekday::try_from((((of >> 4) + (of & 0b111)) % 7) as u8).unwrap() } #[inline] @@ -327,7 +327,7 @@ impl Of { // week ordinal = ordinal + delta let Of(of) = *self; let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, Weekday::from_u32(weekord % 7).unwrap()) + (weekord / 7, Weekday::try_from((weekord % 7) as u8).unwrap()) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] diff --git a/src/weekday.rs b/src/weekday.rs index 38fd1a6876..995e79c85c 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -1,8 +1,10 @@ -use core::fmt; +use core::{convert::TryFrom, fmt}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::OutOfRange; + /// The day of week. /// /// The order of the days of week depends on the context. @@ -12,12 +14,12 @@ use rkyv::{Archive, Deserialize, Serialize}; /// # Example /// ``` /// use chrono::Weekday; -/// use num_traits::cast::FromPrimitive; +/// use std::convert::TryFrom; /// /// let monday = "Monday".parse::().unwrap(); /// assert_eq!(monday, Weekday::Mon); /// -/// let sunday = Weekday::from_u8(6).unwrap(); +/// let sunday = Weekday::try_from(6).unwrap(); /// assert_eq!(sunday, Weekday::Sun); /// /// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 @@ -153,6 +155,26 @@ impl fmt::Display for Weekday { } } +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl TryFrom for Weekday { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Weekday::Mon), + 1 => Ok(Weekday::Tue), + 2 => Ok(Weekday::Wed), + 3 => Ok(Weekday::Thu), + 4 => Ok(Weekday::Fri), + 5 => Ok(Weekday::Sat), + 6 => Ok(Weekday::Sun), + _ => Err(OutOfRange::new()), + } + } +} + /// Any weekday can be represented as an integer from 0 to 6, which equals to /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. /// Do not heavily depend on this though; use explicit methods whenever possible. @@ -210,14 +232,13 @@ impl fmt::Debug for ParseWeekdayError { #[cfg(test)] mod tests { - use num_traits::FromPrimitive; - use super::Weekday; + use std::convert::TryFrom; #[test] fn test_num_days_from() { for i in 0..7 { - let base_day = Weekday::from_u64(i).unwrap(); + 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)); From 2269200cb5de476f84bc858fdcbe016cfa5978d9 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Thu, 8 Sep 2022 13:13:46 -0500 Subject: [PATCH 261/999] Update RFC 2822 and RFC 3339 docs Clarify the behavior of the parse methods, the relationship between ISO 8601 and RFC 3339, and use a brief description on the first line of each function's rustdoc to keep the resulting documentation pretty and concise. --- src/datetime/mod.rs | 57 +++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 940d7b8a35..1d0cc85094 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -552,11 +552,13 @@ where } impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses an RFC 2822 date-and-time string into a `DateTime` value. /// - /// RFC 2822 is the internet message standard that specifies the - /// representation of times in HTTP and email headers. + /// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`) + /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. + /// + /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP + /// and email headers. /// /// The RFC 2822 standard allows arbitrary intermixed whitespace. /// See [RFC 2822 Appendix A.5] @@ -577,11 +579,19 @@ impl DateTime { parsed.to_datetime() } - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses an RFC 3339 date-and-time string into a `DateTime` value. + /// + /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are + /// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a + /// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide + /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly + /// encountered variety of RFC 3339 formats. /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing + /// values in a wide range of formats, only some of which represent actual date-and-time + /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are + /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 + /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; let mut parsed = Parsed::new(); @@ -589,18 +599,15 @@ impl DateTime { parsed.to_datetime() } - /// Parses a string with the specified format string and returns a new - /// [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses a string from a user-specified format into a `DateTime` value. /// - /// See the [`crate::format::strftime`] module on the supported escape - /// sequences. + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) + /// for a version that does not require a timezone in the to-be-parsed str. The returned + /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. /// - /// See also [`TimeZone::datetime_from_str`] which gives a local - /// [`DateTime`] on specific time zone. - /// - /// Note that this method *requires a timezone* in the string. See - /// [`NaiveDateTime::parse_from_str`] - /// for a version that does not require a timezone in the to-be-parsed str. + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. /// /// # Example /// @@ -645,10 +652,10 @@ where } /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per a `SecondsFormat`. + /// formatted as per `SecondsFormat`. /// - /// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as - /// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses /// [`Fixed::TimezoneOffsetColon`] /// /// # Examples @@ -727,9 +734,9 @@ where DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) } - /// Formats the combined date and time with the specified format string. - /// See the [`crate::format::strftime`] module - /// on the supported escape sequences. + /// Formats the combined date and time per the specified format string. + /// + /// See the [`crate::format::strftime`] module for the supported escape sequences. /// /// # Example /// ```rust @@ -771,7 +778,7 @@ where ) } - /// Formats the combined date and time with the specified format string and + /// Formats the combined date and time per the specified format string and /// locale. /// /// See the [`crate::format::strftime`] module on the supported escape From ada0629bb45cf08e9ff72f0ac18fe5eb43d628cb Mon Sep 17 00:00:00 2001 From: Jonas Heylen Date: Tue, 22 Nov 2022 21:32:19 +0100 Subject: [PATCH 262/999] Fix link in serde docs --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d5f1fa8405..336e18751d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -508,12 +508,12 @@ pub use naive::__BenchYearFlags; /// Serialization/Deserialization with serde. /// /// This module provides default implementations for `DateTime` using the [RFC 3339][1] format and various -/// alternatives for use with serde's [`with` annotation][1]. +/// alternatives for use with serde's [`with` annotation][2]. /// /// *Available on crate feature 'serde' only.* /// /// [1]: https://tools.ietf.org/html/rfc3339 -/// [2]: https://serde.rs/attributes.html#field-attributes +/// [2]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { From 76285956aff0083262550e87149f40d587587d5d Mon Sep 17 00:00:00 2001 From: Kurtis Nusbaum Date: Tue, 31 Jan 2023 00:08:07 +0000 Subject: [PATCH 263/999] add and_utc_timezone function --- AUTHORS.txt | 1 + src/naive/datetime/mod.rs | 24 +++++++++++++++++++++--- src/naive/datetime/tests.rs | 10 +++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index caff57915c..e0239ea92c 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -31,6 +31,7 @@ John Nagle Jonas mg János Illés Ken Tossell +Kurtis Nusbaum Martin Risell Lilja Richard Petrie Ryan Lewis diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index ab9fda089d..477f8d51a0 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -18,6 +18,7 @@ use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::Utc; use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; @@ -887,14 +888,31 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Utc}; - /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.timezone(), Utc); + /// use chrono::{NaiveDate, FixedOffset}; + /// let hour = 3600; + /// let tz = FixedOffset::east_opt(5 * hour).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap().and_local_timezone(tz).unwrap(); + /// assert_eq!(dt.timezone(), tz); + /// ``` #[must_use] pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { tz.from_local_datetime(self) } + /// Converts the `NaiveDateTime` into the timezone-aware `DateTime`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveTime, Utc}; + /// let dt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); + /// assert_eq!(dt.timezone(), Utc); + /// ``` + #[must_use] + pub fn and_utc(&self) -> DateTime { + Utc.from_utc_datetime(self) + } + /// The minimum possible `NaiveDateTime`. pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; /// The maximum possible `NaiveDateTime`. diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 810da47015..96ff3b15b5 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -370,7 +370,7 @@ fn test_nanosecond_range() { } #[test] -fn test_and_timezone() { +fn test_and_local_timezone() { let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); let dt_utc = ndt.and_local_timezone(Utc).unwrap(); assert_eq!(dt_utc.naive_local(), ndt); @@ -381,3 +381,11 @@ fn test_and_timezone() { assert_eq!(dt_offset.naive_local(), ndt); assert_eq!(dt_offset.timezone(), offset_tz); } + +#[test] +fn test_and_utc() { + let ndt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap(); + let dt_utc = ndt.and_utc(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); +} From c944b9c39650b16c7cc4b240b1dda6e813e7486d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Apr 2023 11:33:02 +0200 Subject: [PATCH 264/999] Add parse_and_remainder methods --- src/datetime/mod.rs | 36 +++++++++++++++++++++++++++++++++++- src/format/mod.rs | 2 +- src/format/parse.rs | 30 ++++++++++++++++++++++++++++++ src/naive/date.rs | 28 ++++++++++++++++++++++++++-- src/naive/datetime/mod.rs | 27 ++++++++++++++++++++++++++- src/naive/time/mod.rs | 28 ++++++++++++++++++++++++++-- 6 files changed, 144 insertions(+), 7 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1d0cc85094..185cb6f3ee 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -23,7 +23,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -623,6 +623,40 @@ impl DateTime { parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_datetime() } + + /// Parses a string from a user-specified format into a `DateTime` value, and a + /// slice with the remaining portion of the string. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_and_remainder`] for a version that does not + /// require a timezone in `s`. The returned [`DateTime`] value will have a [`FixedOffset`] + /// reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; + /// let (datetime, remainder) = DateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 +0200 trailing text", "%Y-%m-%d %H:%M:%S %z").unwrap(); + /// assert_eq!( + /// datetime, + /// FixedOffset::east_opt(2*3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>( + s: &'a str, + fmt: &str, + ) -> ParseResult<(DateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime().map(|d| (d, remainder)) + } } impl DateTime diff --git a/src/format/mod.rs b/src/format/mod.rs index bc10d4e913..baabdd6740 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -57,7 +57,7 @@ use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; #[cfg(feature = "unstable-locales")] pub(crate) mod locales; -pub use parse::parse; +pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; /// L10n locales. #[cfg(feature = "unstable-locales")] diff --git a/src/format/parse.rs b/src/format/parse.rs index c68d725447..4c79f4a5bc 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -252,6 +252,36 @@ where parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) } +/// Tries to parse given string into `parsed` with given formatting items. +/// Returns `Ok` with a slice of the unparsed remainder. +/// +/// This particular date and time parser is: +/// +/// - Greedy. It will consume the longest possible prefix. +/// For example, `April` is always consumed entirely when the long month name is requested; +/// it equally accepts `Apr`, but prefers the longer prefix in this case. +/// +/// - Padding-agnostic (for numeric items). +/// The [`Pad`](./enum.Pad.html) field is completely ignored, +/// so one can prepend any number of zeroes before numbers. +/// +/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. +pub fn parse_and_remainder<'a, 'b, I, B>( + parsed: &mut Parsed, + s: &'b str, + items: I, +) -> ParseResult<&'b str> +where + I: Iterator, + B: Borrow>, +{ + match parse_internal(parsed, s, items) { + Ok(s) => Ok(s), + Err((s, ParseError(ParseErrorKind::TooLong))) => Ok(s), + Err((_s, e)) => Err(e), + } +} + fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, diff --git a/src/naive/date.rs b/src/naive/date.rs index 4c7f383f7e..a174cae84a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -18,8 +18,10 @@ use pure_rust_locales::Locale; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; -use crate::format::{Item, Numeric, Pad}; +use crate::format::{ + parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, + Parsed, StrftimeItems, +}; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; @@ -546,6 +548,28 @@ impl NaiveDate { parsed.to_naive_date() } + /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate}; + /// let (date, remainder) = NaiveDate::parse_and_remainder( + /// "2015-02-18 trailing text", "%Y-%m-%d").unwrap(); + /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDate, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date().map(|d| (d, remainder)) + } + /// 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. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 477f8d51a0..4a4ac8221e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -15,7 +15,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; @@ -300,6 +300,31 @@ impl NaiveDateTime { parsed.to_naive_datetime_with_offset(0) // no offset adjustment } + /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a + /// slice with the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// let (datetime, remainder) = NaiveDateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 trailing text", "%Y-%m-%d %H:%M:%S").unwrap(); + /// assert_eq!( + /// datetime, + /// NaiveDate::from_ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0).map(|d| (d, remainder)) // no offset adjustment + } + /// Retrieves a date component. /// /// # Example diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index af0a632b47..880617e511 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -13,8 +13,10 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; -use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::format::{ + parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, + Parsed, StrftimeItems, +}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; @@ -482,6 +484,28 @@ impl NaiveTime { parsed.to_naive_time() } + /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveTime}; + /// let (time, remainder) = NaiveTime::parse_and_remainder( + /// "3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); + /// assert_eq!(time, NaiveTime::from_hms_opt(3, 4, 33).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time().map(|t| (t, remainder)) + } + /// Adds given `Duration` to the current time, /// and also returns the number of *seconds* /// in the integral number of days ignored from the addition. From 07398a6270e73ba74b05dd1dafe34c6251d0b8ab Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 19 May 2023 13:58:22 +0200 Subject: [PATCH 265/999] Remove AUTHORS metadata This file hasn't been kept up to date, and it's easy to get the data from GitHub. --- AUTHORS.txt | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 AUTHORS.txt diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index e0239ea92c..0000000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,44 +0,0 @@ -Chrono is mainly written by Kang Seonghoon , -and also the following people (in ascending order): - -Alex Mikhalev -Alexander Bulaev -Ashley Mannix -Ben Boeckel -Ben Eills -Brandon W Maister -Brandon W Maister -Cecile Tonglet -Colin Ray -Corey Farwell -Dan -Danilo Bargen -David Hewson -David Ross -David Tolnay -David Willie -Eric Findlay -Eunchong Yu -Frans Skarman -Huon Wilson -Igor Gnatenko -Jake Vossen -Jim Turner -Jisoo Park -Joe Wilm -John Heitmann -John Nagle -Jonas mg -János Illés -Ken Tossell -Kurtis Nusbaum -Martin Risell Lilja -Richard Petrie -Ryan Lewis -Sergey V. Shadoy -Sergey V. Galtsev -Steve Klabnik -Tom Gallacher -klutzy -kud1ing -Yohan Boogaert From b6b35dc9324b1958f68e929307b382111bbad50e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 15:07:27 +0200 Subject: [PATCH 266/999] Make test_naiveweek clearer by including the day of week --- src/naive/date.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index a174cae84a..2513ae0360 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3019,19 +3019,19 @@ mod tests { fn test_naiveweek() { let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); let asserts = vec![ - (Weekday::Mon, "2022-05-16", "2022-05-22"), - (Weekday::Tue, "2022-05-17", "2022-05-23"), - (Weekday::Wed, "2022-05-18", "2022-05-24"), - (Weekday::Thu, "2022-05-12", "2022-05-18"), - (Weekday::Fri, "2022-05-13", "2022-05-19"), - (Weekday::Sat, "2022-05-14", "2022-05-20"), - (Weekday::Sun, "2022-05-15", "2022-05-21"), + (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), + (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), + (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), + (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), + (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), + (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), + (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), ]; for (start, first_day, last_day) in asserts { let week = date.week(start); let days = week.days(); - assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%Y-%m-%d")); - assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%Y-%m-%d")); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); assert!(days.contains(&date)); } } From cb27ebf7bf4b87beff2e5fa4f4dde7b9398d2b59 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 7 May 2023 14:32:36 +0200 Subject: [PATCH 267/999] Fix out-of-range panic in NaiveWeek::last_day --- src/naive/date.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 2513ae0360..f23e159b16 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -79,10 +79,13 @@ impl NaiveWeek { #[inline] #[must_use] pub fn first_day(&self) -> NaiveDate { - let start = self.start.num_days_from_monday(); - let end = self.date.weekday().num_days_from_monday(); - let days = if start > end { 7 - start + end } else { end - start }; - self.date - Duration::days(days.into()) + let start = self.start.num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to subtract from `self.date`. + // Do not construct an intermediate date beyond `self.date`, because that may be out of + // range if `date` is close to `NaiveDate::MAX`. + let days = start - ref_day - if start > ref_day { 7 } else { 0 }; + self.date.diff_days(days as i64).unwrap() } /// Returns a date representing the last day of the week. @@ -99,7 +102,13 @@ impl NaiveWeek { #[inline] #[must_use] pub fn last_day(&self) -> NaiveDate { - self.first_day() + Duration::days(6) + let end = self.start.pred().num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to add to `self.date`. + // Do not construct an intermediate date before `self.date` (like with `first_day()`), + // because that may be out of range if `date` is close to `NaiveDate::MIN`. + let days = end - ref_day + if end < ref_day { 7 } else { 0 }; + self.date.diff_days(days as i64).unwrap() } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -3036,6 +3045,14 @@ mod tests { } } + #[test] + fn test_naiveweek_min_max() { + let date_max = NaiveDate::MAX; + assert!(date_max.week(Weekday::Mon).first_day() <= date_max); + let date_min = NaiveDate::MIN; + assert!(date_min.week(Weekday::Mon).last_day() >= date_min); + } + #[test] fn test_weeks_from() { // tests per: https://github.com/chronotope/chrono/issues/961 From bf9f87638894dcfccb121a99653e29464be364bf Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 21 May 2023 14:23:54 -0700 Subject: [PATCH 268/999] add test_issue_866 Add single test case specific to Issue #866 --- src/offset/local/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 18b3961130..47b7570c10 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -266,4 +266,12 @@ mod tests { ); } } + + /// Test Issue #866 + #[test] + fn test_issue_866() { + #[allow(deprecated)] + let local_20221106 = Local.ymd(2022, 11, 6); + let _dt_20221106 = local_20221106.and_hms_milli_opt(1, 2, 59, 1000).unwrap(); + } } From 327e5ee4540b30ea43e6b2576f734f4cf8d6a5d2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 22 May 2023 12:13:37 +0200 Subject: [PATCH 269/999] Reformat `test_datetime_parse_from_str` --- src/datetime/tests.rs | 540 +++++++----------------------------------- 1 file changed, 80 insertions(+), 460 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index ebf0359701..87f72f9c4c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -998,6 +998,7 @@ fn test_utc_datetime_from_str_with_spaces() { #[test] fn test_datetime_parse_from_str() { let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); + let parse = DateTime::::parse_from_str; // timezone variations @@ -1005,517 +1006,136 @@ fn test_datetime_parse_from_str() { // %Z // // wrong timezone format - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data? - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 PST", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 XXXXX", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); // // %z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 --0900", - "%b %d %Y %H:%M:%S -%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 +-0900", - "%b %d %Y %H:%M:%S +%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing newline after timezone - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z ").is_err()); // trailing colon - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); // trailing colon with space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00: ", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); // trailing colon, mismatch space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900::", - "%b %d %Y %H:%M:%S %z::" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %z:00" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %z:00 " - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); // // %:z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00:", - "%b %d %Y %H:%M:%S %:z:" - ) - .is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:").is_err()); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z").is_err()); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z::" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); // - // %:::z + // %::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z").is_err()); // mismatching colon expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z").is_err()); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %::z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900\t\n", - "%b %d %Y %H:%M:%S %::z\t\n" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900:", - "%b %d %Y %H:%M:%S %::z:" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900:0", - "%b %d %Y %H:%M:%S :%::z:0" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); // mismatching colons and spaces - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900: ", - "%b %d %Y %H:%M:%S :%::z::" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); // mismatching colons expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900: 23:54:35", - "%b %d %Y %::z: %H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 :-0900:0 23:54:35", - "%b %d %Y :%::z:0 %H:%M:%S" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); // mismatching colons expectations mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 :-0900: 23:54:35", - "%b %d %Y :%::z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); // mismatching colons expectations, before end - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %::z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); // // %:::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %:::z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); // // %::::z // // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // // %#z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09: ", - "%b %d %Y %H:%M:%S %#z " - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35+-09", - "%b %d %Y %H:%M:%S+%#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35--09", - "%b %d %Y %H:%M:%S-%#z" - ), - Ok(dt), - ); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ) - .is_err()); - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -0900 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -090023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:0023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z ").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); + assert!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); + assert!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); + assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // timezone with partial minutes adjacent hours - assert_ne!( - DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), - Ok(dt), - ); + assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) with trailing space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090 ", - "%b %d %Y %H:%M:%S %#z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); // bad timezone data (partial minutes) mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -090 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:00 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // timezone data ambiguous with hours - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:23:54:35", - "%b %d %Y %#z%H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); } #[test] From d3c23e65e093e92af848b260f2dc447085ea97d3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 29 Apr 2023 07:22:31 +0200 Subject: [PATCH 270/999] Make fns in internals const --- src/naive/internals.rs | 86 +++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 9249e840a0..dd546220df 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -52,7 +52,7 @@ pub(super) const FE: YearFlags = YearFlags(0o07); pub(super) const G: YearFlags = YearFlags(0o16); pub(super) const GF: YearFlags = YearFlags(0o06); -static YEAR_TO_FLAGS: [YearFlags; 400] = [ +const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, @@ -71,7 +71,7 @@ static YEAR_TO_FLAGS: [YearFlags; 400] = [ D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 ]; -static YEAR_DELTAS: [u8; 401] = [ +const YEAR_DELTAS: &[u8; 401] = &[ 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, @@ -93,21 +93,21 @@ static YEAR_DELTAS: [u8; 401] = [ 96, 97, 97, 97, 97, // 400+1 ]; -pub(super) fn cycle_to_yo(cycle: u32) -> (u32, u32) { +pub(super) const fn cycle_to_yo(cycle: u32) -> (u32, u32) { let mut year_mod_400 = cycle / 365; let mut ordinal0 = cycle % 365; - let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]); + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; if ordinal0 < delta { year_mod_400 -= 1; - ordinal0 += 365 - u32::from(YEAR_DELTAS[year_mod_400 as usize]); + ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; } else { ordinal0 -= delta; } (year_mod_400, ordinal0 + 1) } -pub(super) fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { - year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1 +pub(super) const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { + year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 } impl YearFlags { @@ -115,26 +115,26 @@ impl YearFlags { #[doc(hidden)] // for benchmarks only #[inline] #[must_use] - pub fn from_year(year: i32) -> YearFlags { + pub const fn from_year(year: i32) -> YearFlags { let year = year.rem_euclid(400); YearFlags::from_year_mod_400(year) } #[inline] - pub(super) fn from_year_mod_400(year: i32) -> YearFlags { + pub(super) const fn from_year_mod_400(year: i32) -> YearFlags { YEAR_TO_FLAGS[year as usize] } #[inline] - pub(super) fn ndays(&self) -> u32 { + pub(super) const fn ndays(&self) -> u32 { let YearFlags(flags) = *self; - 366 - u32::from(flags >> 3) + 366 - (flags >> 3) as u32 } #[inline] - pub(super) fn isoweek_delta(&self) -> u32 { + pub(super) const fn isoweek_delta(&self) -> u32 { let YearFlags(flags) = *self; - let mut delta = u32::from(flags) & 0b0111; + let mut delta = (flags & 0b0111) as u32; if delta < 3 { delta += 7; } @@ -178,7 +178,7 @@ pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `( pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; const XX: i8 = -128; -static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ +const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0 @@ -221,7 +221,7 @@ static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ 100, // 12 ]; -static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ +const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 0, 0, // 0 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, @@ -271,27 +271,31 @@ pub(super) struct Of(pub(crate) u32); impl Of { #[inline] - pub(super) fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { + pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { match ordinal <= 366 { - true => Some(Of((ordinal << 4) | u32::from(flags))), + true => Some(Of((ordinal << 4) | flags as u32)), false => None, } } #[inline] - pub(super) fn from_mdf(Mdf(mdf): Mdf) -> Of { + pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Of { let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)), - None => Of(0), + if mdl <= MAX_MDL { + // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. + let v = MDL_TO_OL[mdl as usize]; + Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3)) + } else { + // Panicking here would be reasonable, but we are just going on with a safe value. + Of(0) } } #[inline] - pub(super) fn valid(&self) -> bool { + pub(super) const fn valid(&self) -> bool { let Of(of) = *self; let ol = of >> 3; - (MIN_OL..=MAX_OL).contains(&ol) + ol >= MIN_OL && ol <= MAX_OL } #[inline] @@ -332,7 +336,7 @@ impl Of { #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] - pub(super) fn to_mdf(&self) -> Mdf { + pub(super) const fn to_mdf(&self) -> Mdf { Mdf::from_of(*self) } @@ -368,33 +372,39 @@ impl fmt::Debug for Of { /// (month, day of month and leap flag), /// which is an index to the `MDL_TO_OL` lookup table. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub(super) struct Mdf(pub(super) u32); +pub(super) struct Mdf(u32); impl Mdf { #[inline] - pub(super) fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { + pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { match month <= 12 && day <= 31 { - true => Some(Mdf((month << 9) | (day << 4) | u32::from(flags))), + true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), false => None, } } #[inline] - pub(super) fn from_of(Of(of): Of) -> Mdf { + pub(super) const fn from_of(Of(of): Of) -> Mdf { let ol = of >> 3; - match OL_TO_MDL.get(ol as usize) { - Some(&v) => Mdf(of + (u32::from(v) << 3)), - None => Mdf(0), + if ol <= MAX_OL { + // Array is indexed from `[1..=MAX_OL]`, with a `0` index having a meaningless value. + Mdf(of + ((OL_TO_MDL[ol as usize] as u32) << 3)) + } else { + // Panicking here would be reasonable, but we are just going on with a safe value. + Mdf(0) } } #[cfg(test)] - pub(super) fn valid(&self) -> bool { + pub(super) const fn valid(&self) -> bool { let Mdf(mdf) = *self; let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => v >= 0, - None => false, + if mdl <= MAX_MDL { + // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. + MDL_TO_OL[mdl as usize] >= 0 + } else { + // Panicking here would be reasonable, but we are just going on with a safe value. + false } } @@ -431,14 +441,14 @@ impl Mdf { } #[inline] - pub(super) fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { + pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; - Mdf((mdf & !0b1111) | u32::from(flags)) + Mdf((mdf & !0b1111) | flags as u32) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] - pub(super) fn to_of(&self) -> Of { + pub(super) const fn to_of(&self) -> Of { Of::from_mdf(*self) } } From 210f2689ee14f0ddfa15a6b1088e35bcdc18e649 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 29 Apr 2023 17:14:53 +0200 Subject: [PATCH 271/999] Make internal functions returning Weekday const --- src/naive/internals.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index dd546220df..30772c3dee 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -16,7 +16,6 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] use crate::Weekday; -use core::convert::TryFrom; use core::{fmt, i32}; /// The internal date representation. This also includes the packed `Mdf` value. @@ -321,9 +320,9 @@ impl Of { } #[inline] - pub(super) fn weekday(&self) -> Weekday { + pub(super) const fn weekday(&self) -> Weekday { let Of(of) = *self; - Weekday::try_from((((of >> 4) + (of & 0b111)) % 7) as u8).unwrap() + weekday_from_u32_mod7((of >> 4) + (of & 0b111)) } #[inline] @@ -331,7 +330,7 @@ impl Of { // week ordinal = ordinal + delta let Of(of) = *self; let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, Weekday::try_from((weekord % 7) as u8).unwrap()) + (weekord / 7, weekday_from_u32_mod7(weekord)) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] @@ -467,11 +466,28 @@ impl fmt::Debug for Mdf { } } +/// Create a `Weekday` from an `u32`, with Monday = 0. +/// Infallible, takes any `n` and applies `% 7`. +#[inline] +const fn weekday_from_u32_mod7(n: u32) -> Weekday { + match n % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } +} + #[cfg(test)] mod tests { use num_iter::range_inclusive; + use num_traits::FromPrimitive; use std::u32; + use super::weekday_from_u32_mod7; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; use crate::Weekday; @@ -824,4 +840,12 @@ mod tests { } } } + + #[test] + fn test_weekday_from_u32_mod7() { + for i in 0..=1000 { + assert_eq!(weekday_from_u32_mod7(i), Weekday::from_u32(i % 7).unwrap()); + } + assert_eq!(weekday_from_u32_mod7(u32::MAX), Weekday::Thu); + } } From 7d98475f77f5f50e52fca2c589d1b43423fe51dc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Apr 2023 06:59:39 +0200 Subject: [PATCH 272/999] Ensure `Of` is always valid --- src/naive/date.rs | 34 ++++----- src/naive/internals.rs | 154 ++++++++++++++++++++++++++--------------- 2 files changed, 117 insertions(+), 71 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index f23e159b16..9404c895d5 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -254,8 +254,8 @@ impl NaiveDate { } /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. fn from_of(year: i32, of: Of) -> Option { - if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() { - let Of(of) = of; + if (MIN_YEAR..=MAX_YEAR).contains(&year) { + let of = of.inner(); Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) }) } else { None @@ -264,7 +264,7 @@ impl NaiveDate { /// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification. fn from_mdf(year: i32, mdf: Mdf) -> Option { - NaiveDate::from_of(year, mdf.to_of()) + NaiveDate::from_of(year, mdf.to_of()?) } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) @@ -957,7 +957,7 @@ impl NaiveDate { /// Returns the packed ordinal-flags. #[inline] const fn of(&self) -> Of { - Of((self.ymdf & 0b1_1111_1111_1111) as u32) + Of::from_date_impl(self.ymdf) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -965,20 +965,16 @@ impl NaiveDate { /// Returns `None` when the resulting `NaiveDate` would be invalid. #[inline] fn with_mdf(&self, mdf: Mdf) -> Option { - self.with_of(mdf.to_of()) + Some(self.with_of(mdf.to_of()?)) } /// Makes a new `NaiveDate` with the packed ordinal-flags changed. /// /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// Does not check if the year flags match the year. #[inline] - fn with_of(&self, of: Of) -> Option { - if of.valid() { - let Of(of) = of; - Some(NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of as DateImpl }) - } else { - None - } + const fn with_of(&self, of: Of) -> NaiveDate { + NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl } } /// Makes a new `NaiveDate` for the next calendar date. @@ -1007,7 +1003,10 @@ impl NaiveDate { #[inline] #[must_use] pub fn succ_opt(&self) -> Option { - self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1)) + match self.of().succ() { + Some(of) => Some(self.with_of(of)), + None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1), + } } /// Makes a new `NaiveDate` for the previous calendar date. @@ -1036,7 +1035,10 @@ impl NaiveDate { #[inline] #[must_use] pub fn pred_opt(&self) -> Option { - self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31)) + match self.of().pred() { + Some(of) => Some(self.with_of(of)), + None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), + } } /// Adds the `days` part of given `Duration` to the current date. @@ -1655,7 +1657,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal)?) + self.of().with_ordinal(ordinal).map(|of| self.with_of(of)) } /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. @@ -1680,7 +1682,7 @@ impl Datelike for NaiveDate { #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { let ordinal = ordinal0.checked_add(1)?; - self.with_of(self.of().with_ordinal(ordinal)?) + self.with_ordinal(ordinal) } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 30772c3dee..78f787b935 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -18,7 +18,7 @@ use crate::Weekday; use core::{fmt, i32}; -/// The internal date representation. This also includes the packed `Mdf` value. +/// The internal date representation: `year << 13 | Of` pub(super) type DateImpl = i32; pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13; @@ -172,8 +172,9 @@ impl fmt::Debug for YearFlags { } } +// OL: (ordinal << 1) | leap year flag pub(super) const MIN_OL: u32 = 1 << 1; -pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` +pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; const XX: i8 = -128; @@ -265,58 +266,70 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ /// /// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), /// which is an index to the `OL_TO_MDL` lookup table. +/// +/// The methods implemented on `Of` always return a valid value. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub(super) struct Of(pub(crate) u32); +pub(super) struct Of(u32); impl Of { #[inline] pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { - match ordinal <= 366 { - true => Some(Of((ordinal << 4) | flags as u32)), - false => None, - } + let of = Of((ordinal << 4) | flags as u32); + of.validate() + } + + pub(super) const fn from_date_impl(date_impl: DateImpl) -> Of { + // We assume the value in the `DateImpl` is valid. + Of((date_impl & 0b1_1111_1111_1111) as u32) } #[inline] - pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Of { + pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option { let mdl = mdf >> 3; - if mdl <= MAX_MDL { - // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. - let v = MDL_TO_OL[mdl as usize]; - Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3)) - } else { - // Panicking here would be reasonable, but we are just going on with a safe value. - Of(0) + if mdl > MAX_MDL { + // Panicking on out-of-bounds indexing would be reasonable, but just return `None`. + return None; } + // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. + let v = MDL_TO_OL[mdl as usize]; + let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3)); + of.validate() } #[inline] - pub(super) const fn valid(&self) -> bool { - let Of(of) = *self; - let ol = of >> 3; - ol >= MIN_OL && ol <= MAX_OL + pub(super) const fn inner(&self) -> u32 { + self.0 } + /// Returns `(ordinal << 1) | leap-year-flag`. #[inline] - pub(super) const fn ordinal(&self) -> u32 { - let Of(of) = *self; - of >> 4 + const fn ol(&self) -> u32 { + self.0 >> 3 } #[inline] - pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { - if ordinal > 366 { - return None; + const fn validate(self) -> Option { + let ol = self.ol(); + match ol >= MIN_OL && ol <= MAX_OL { + true => Some(self), + false => None, } + } - let Of(of) = *self; - Some(Of((of & 0b1111) | (ordinal << 4))) + #[inline] + pub(super) const fn ordinal(&self) -> u32 { + self.0 >> 4 + } + + #[inline] + pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { + let of = Of((ordinal << 4) | (self.0 & 0b1111)); + of.validate() } #[inline] pub(super) const fn flags(&self) -> YearFlags { - let Of(of) = *self; - YearFlags((of & 0b1111) as u8) + YearFlags((self.0 & 0b1111) as u8) } #[inline] @@ -339,16 +352,20 @@ impl Of { Mdf::from_of(*self) } + /// Returns an `Of` with the next day, or `None` if this is the last day of the year. #[inline] - pub(super) const fn succ(&self) -> Of { - let Of(of) = *self; - Of(of + (1 << 4)) + pub(super) const fn succ(&self) -> Option { + let of = Of(self.0 + (1 << 4)); + of.validate() } + /// Returns an `Of` with the previous day, or `None` if this is the first day of the year. #[inline] - pub(super) const fn pred(&self) -> Of { - let Of(of) = *self; - Of(of - (1 << 4)) + pub(super) const fn pred(&self) -> Option { + match self.ordinal() { + 1 => None, + _ => Some(Of(self.0 - (1 << 4))), + } } } @@ -370,13 +387,17 @@ impl fmt::Debug for Of { /// The whole bits except for the least 3 bits are referred as `Mdl` /// (month, day of month and leap flag), /// which is an index to the `MDL_TO_OL` lookup table. +/// +/// The methods implemented on `Mdf` do not always return a valid value. +/// Dates that can't exist, like February 30, can still be represented. +/// Use `Mdl::valid` to check whether the date is valid. #[derive(PartialEq, PartialOrd, Copy, Clone)] pub(super) struct Mdf(u32); impl Mdf { #[inline] pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { - match month <= 12 && day <= 31 { + match month >= 1 && month <= 12 && day >= 1 && day <= 31 { true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), false => None, } @@ -447,7 +468,7 @@ impl Mdf { #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] - pub(super) const fn to_of(&self) -> Of { + pub(super) const fn to_of(&self) -> Option { Of::from_mdf(*self) } } @@ -542,7 +563,7 @@ mod tests { }; assert!( - of.valid() == expected, + of.validate().is_some() == expected, "ordinal {} = {:?} should be {} for dominical year {:?}", ordinal, of, @@ -662,8 +683,7 @@ mod tests { fn test_of_fields() { for &flags in FLAGS.iter() { for ordinal in range_inclusive(1u32, 366) { - let of = Of::new(ordinal, flags).unwrap(); - if of.valid() { + if let Some(of) = Of::new(ordinal, flags) { assert_eq!(of.ordinal(), ordinal); } } @@ -676,14 +696,9 @@ mod tests { let of = Of::new(ordinal, flags).unwrap(); for ordinal in range_inclusive(0u32, 1024) { - let of = match of.with_ordinal(ordinal) { - Some(of) => of, - None if ordinal > 366 => continue, - None => panic!("failed to create Of with ordinal {}", ordinal), - }; - - assert_eq!(of.valid(), Of::new(ordinal, flags).unwrap().valid()); - if of.valid() { + let of = of.with_ordinal(ordinal); + assert_eq!(of, Of::new(ordinal, flags)); + if let Some(of) = of { assert_eq!(of.ordinal(), ordinal); } } @@ -808,8 +823,9 @@ mod tests { #[test] fn test_of_to_mdf() { for i in range_inclusive(0u32, 8192) { - let of = Of(i); - assert_eq!(of.valid(), of.to_mdf().valid()); + if let Some(of) = Of(i).validate() { + assert!(of.to_mdf().valid()); + } } } @@ -817,16 +833,15 @@ mod tests { fn test_mdf_to_of() { for i in range_inclusive(0u32, 8192) { let mdf = Mdf(i); - assert_eq!(mdf.valid(), mdf.to_of().valid()); + assert_eq!(mdf.valid(), mdf.to_of().is_some()); } } #[test] fn test_of_to_mdf_to_of() { for i in range_inclusive(0u32, 8192) { - let of = Of(i); - if of.valid() { - assert_eq!(of, of.to_mdf().to_of()); + if let Some(of) = Of(i).validate() { + assert_eq!(of, of.to_mdf().to_of().unwrap()); } } } @@ -836,11 +851,40 @@ mod tests { for i in range_inclusive(0u32, 8192) { let mdf = Mdf(i); if mdf.valid() { - assert_eq!(mdf, mdf.to_of().to_mdf()); + assert_eq!(mdf, mdf.to_of().unwrap().to_mdf()); } } } + #[test] + fn test_invalid_returns_none() { + let regular_year = YearFlags::from_year(2023); + let leap_year = YearFlags::from_year(2024); + assert!(Of::new(0, regular_year).is_none()); + assert!(Of::new(366, regular_year).is_none()); + assert!(Of::new(366, leap_year).is_some()); + assert!(Of::new(367, regular_year).is_none()); + + assert!(Mdf::new(0, 1, regular_year).is_none()); + assert!(Mdf::new(13, 1, regular_year).is_none()); + assert!(Mdf::new(1, 0, regular_year).is_none()); + assert!(Mdf::new(1, 32, regular_year).is_none()); + assert!(Mdf::new(2, 31, regular_year).is_some()); + + assert!(Of::from_mdf(Mdf::new(2, 30, regular_year).unwrap()).is_none()); + assert!(Of::from_mdf(Mdf::new(2, 30, leap_year).unwrap()).is_none()); + assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none()); + assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); + assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); + + assert!(Of::new(365, regular_year).unwrap().succ().is_none()); + assert!(Of::new(365, leap_year).unwrap().succ().is_some()); + assert!(Of::new(366, leap_year).unwrap().succ().is_none()); + + assert!(Of::new(1, regular_year).unwrap().pred().is_none()); + assert!(Of::new(1, leap_year).unwrap().pred().is_none()); + } + #[test] fn test_weekday_from_u32_mod7() { for i in 0..=1000 { From 403b247affd97e9ae6e16da3462bc22569c9df26 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 15 May 2023 12:17:54 +0200 Subject: [PATCH 273/999] Change `NaiveDate::from_of` to take ordinal and year flags --- src/naive/date.rs | 53 +++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9404c895d5..54d5e2258b 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -252,19 +252,35 @@ impl NaiveDate { pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { (self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7 } - /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. - fn from_of(year: i32, of: Of) -> Option { - if (MIN_YEAR..=MAX_YEAR).contains(&year) { - let of = of.inner(); - Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) }) - } else { - None + + /// Makes a new `NaiveDate` from year, ordinal and flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_ordinal_and_flags( + year: i32, + ordinal: u32, + flags: YearFlags, + ) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + // Enable debug check once the MSRV >= 1.57 (panicking in const feature) + // debug_assert!(YearFlags::from_year(year).0 == flags.0); + match Of::new(ordinal, flags) { + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. } } - /// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification. - fn from_mdf(year: i32, mdf: Mdf) -> Option { - NaiveDate::from_of(year, mdf.to_of()?) + /// Makes a new `NaiveDate` from year and packed month-day-flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_mdf(year: i32, mdf: Mdf) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + match mdf.to_of() { + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + None => None, // Non-existing date + } } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) @@ -335,7 +351,7 @@ impl NaiveDate { #[must_use] pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { let flags = YearFlags::from_year(year); - NaiveDate::from_of(year, Of::new(ordinal, flags)?) + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) @@ -404,20 +420,21 @@ impl NaiveDate { if weekord <= delta { // ordinal < 1, previous year let prevflags = YearFlags::from_year(year - 1); - NaiveDate::from_of( + NaiveDate::from_ordinal_and_flags( year - 1, - Of::new(weekord + prevflags.ndays() - delta, prevflags)?, + weekord + prevflags.ndays() - delta, + prevflags, ) } else { let ordinal = weekord - delta; let ndays = flags.ndays(); if ordinal <= ndays { // this year - NaiveDate::from_of(year, Of::new(ordinal, flags)?) + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } else { // ordinal > ndays, next year let nextflags = YearFlags::from_year(year + 1); - NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)?) + NaiveDate::from_ordinal_and_flags(year + 1, ordinal - ndays, nextflags) } } } else { @@ -462,7 +479,7 @@ impl NaiveDate { let (year_div_400, cycle) = div_mod_floor(days, 146_097); let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week @@ -1070,7 +1087,7 @@ impl NaiveDate { let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } /// Subtracts the `days` part of given `Duration` from the current date. @@ -1102,7 +1119,7 @@ impl NaiveDate { let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } /// Subtracts another `NaiveDate` from the current date. From b4c7fb4688c68a262a505cfc8d58d3b2273a6f3e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 26 May 2023 11:40:19 +0200 Subject: [PATCH 274/999] Extract timezone info from tzdata file on Android (#978) --- .github/workflows/test.yml | 9 ++++++++- Cargo.toml | 3 +++ src/offset/local/tz_info/timezone.rs | 8 ++++++++ src/offset/local/unix.rs | 7 +++---- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c8d8d53fa..a18e07aaf0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,7 +100,14 @@ jobs: strategy: matrix: os: [ubuntu-latest] - target: [wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android] + target: + [ + wasm32-unknown-unknown, + wasm32-wasi, + wasm32-unknown-emscripten, + aarch64-apple-ios, + aarch64-linux-android, + ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/Cargo.toml b/Cargo.toml index 23ffda6ea2..b925dc1431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,9 @@ winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "tim [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } +[target.'cfg(target_os = "android")'.dependencies] +android-tzdata = "0.1.1" + [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 2fe24d2e44..33c89060ce 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -43,6 +43,14 @@ impl TimeZone { return Self::from_tz_data(&fs::read("/etc/localtime")?); } + // attributes are not allowed on if blocks in Rust 1.38 + #[cfg(target_os = "android")] + { + if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) { + return Self::from_tz_data(&bytes); + } + } + let mut chars = tz_string.chars(); if chars.next() == Some(':') { return Self::from_file(&mut find_tz_file(chars.as_str())?); diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 22114f21ef..68b206cd90 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -68,19 +68,18 @@ struct Cache { last_checked: SystemTime, } -#[cfg(target_os = "android")] -const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo"; - #[cfg(target_os = "aix")] const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo"; -#[allow(dead_code)] // keeps the cfg simpler #[cfg(not(any(target_os = "android", target_os = "aix")))] const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; fn fallback_timezone() -> Option { let tz_name = iana_time_zone::get_timezone().ok()?; + #[cfg(not(target_os = "android"))] let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?; + #[cfg(target_os = "android")] + let bytes = android_tzdata::find_tz_data(&tz_name).ok()?; TimeZone::from_tz_data(&bytes).ok() } From 8beda4cbeb2ca7cce948df9c86d571f4ae32aa8a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 10 May 2023 16:07:26 +0200 Subject: [PATCH 275/999] Remove mention of deprecated `Date` in main documentation --- src/lib.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 336e18751d..d56bd1fdc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -330,34 +330,6 @@ //! assert_eq!(dt.timestamp(), 1_500_000_000); //! ``` //! -//! ### Individual date -//! -//! Chrono also provides an individual date type ([**`Date`**](./struct.Date.html)). -//! It also has time zones attached, and have to be constructed via time zones. -//! Most operations available to `DateTime` are also available to `Date` whenever appropriate. -//! -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -#![cfg_attr(feature = "std", doc = "```rust")] -//! use chrono::prelude::*; -//! use chrono::offset::LocalResult; -//! -//! # // these *may* fail, but only very rarely. just rerun the test if you were that unfortunate ;) -//! assert_eq!(Utc::today(), Utc::now().date()); -//! assert_eq!(Local::today(), Local::now().date()); -//! -//! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().weekday(), Weekday::Fri); -//! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().and_local_timezone(Utc).unwrap().format("%H%M%S").to_string(), -//! "070809"); -//! ``` -//! -//! There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. -//! -//! `DateTime` has [`date`](./struct.DateTime.html#method.date) method -//! which returns a `Date` which represents its date component. -//! There is also a [`time`](./struct.DateTime.html#method.time) method, -//! which simply returns a naive local time described below. -//! //! ### Naive date and time //! //! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` From e86df391a3c9dffd48fe05d8f38c0c3f2b13da4b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 17 May 2023 14:40:05 +0200 Subject: [PATCH 276/999] Remove unused imports in doctests --- src/datetime/mod.rs | 12 ++++++------ src/datetime/serde.rs | 24 ++++++++++++------------ src/format/mod.rs | 3 +-- src/lib.rs | 1 + src/naive/date.rs | 4 ++-- src/naive/datetime/mod.rs | 18 +++++++----------- src/naive/datetime/serde.rs | 8 ++++---- src/naive/time/mod.rs | 4 ++-- src/round.rs | 14 +++++++------- 9 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 185cb6f3ee..1bdda19009 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -208,7 +208,7 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone, NaiveDate}; + /// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); @@ -232,7 +232,7 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone, NaiveDate}; + /// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); @@ -256,7 +256,7 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone, NaiveDate}; + /// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); @@ -564,7 +564,7 @@ impl DateTime { /// See [RFC 2822 Appendix A.5] /// /// ``` - /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; + /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() @@ -640,7 +640,7 @@ impl DateTime { /// # Example /// /// ```rust - /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; + /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// let (datetime, remainder) = DateTime::parse_and_remainder( /// "2015-02-18 23:16:09 +0200 trailing text", "%Y-%m-%d %H:%M:%S %z").unwrap(); /// assert_eq!( @@ -695,7 +695,7 @@ where /// # Examples /// /// ```rust - /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), /// "2018-01-26T18:30:09.453+00:00"); diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index d30f805198..0c49abf92a 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -122,7 +122,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds; /// #[derive(Deserialize, Serialize)] @@ -158,7 +158,7 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; /// #[derive(Serialize)] @@ -247,7 +247,7 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_nanoseconds_option; /// #[derive(Deserialize, Serialize)] @@ -282,7 +282,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; /// #[derive(Serialize)] @@ -378,7 +378,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds; /// #[derive(Deserialize, Serialize)] @@ -413,7 +413,7 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; /// #[derive(Serialize)] @@ -502,7 +502,7 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_microseconds_option; /// #[derive(Deserialize, Serialize)] @@ -536,7 +536,7 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; /// #[derive(Serialize)] @@ -632,7 +632,7 @@ pub mod ts_microseconds_option { /// # Example /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds; /// #[derive(Deserialize, Serialize)] @@ -667,7 +667,7 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; /// #[derive(Serialize)] @@ -753,7 +753,7 @@ pub mod ts_milliseconds { /// # Example /// /// ```rust -/// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; +/// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::{Deserialize, Serialize}; /// use chrono::serde::ts_milliseconds_option; /// #[derive(Deserialize, Serialize)] @@ -787,7 +787,7 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{TimeZone, DateTime, Utc, NaiveDate}; + /// # use chrono::{DateTime, Utc, NaiveDate}; /// # use serde_derive::Serialize; /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; /// #[derive(Serialize)] diff --git a/src/format/mod.rs b/src/format/mod.rs index baabdd6740..75c0194fd5 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -18,8 +18,7 @@ //! # Example #![cfg_attr(not(feature = "std"), doc = "```ignore")] #![cfg_attr(feature = "std", doc = "```rust")] -//! # use std::error::Error; -//! use chrono::prelude::*; +//! use chrono::{TimeZone, Utc}; //! //! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); //! diff --git a/src/lib.rs b/src/lib.rs index d56bd1fdc7..a1547b13ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,6 +219,7 @@ //! The `unstable-locales` feature requires and implies at least the `alloc` feature. //! //! ```rust +//! # #[allow(unused_imports)] //! use chrono::prelude::*; //! //! # #[cfg(feature = "unstable-locales")] diff --git a/src/naive/date.rs b/src/naive/date.rs index 54d5e2258b..314354731a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1754,7 +1754,7 @@ impl Add for NaiveDate { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate, Months}; + /// use chrono::{NaiveDate, Months}; /// /// let from_ymd = NaiveDate::from_ymd; /// @@ -1782,7 +1782,7 @@ impl Sub for NaiveDate { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate, Months}; + /// use chrono::{NaiveDate, Months}; /// /// let from_ymd = NaiveDate::from_ymd; /// diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 4a4ac8221e..880d96f011 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -196,7 +196,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; + /// use chrono::NaiveDateTime; /// use std::i64; /// /// let from_timestamp_opt = NaiveDateTime::from_timestamp_opt; @@ -288,7 +288,7 @@ impl NaiveDateTime { /// Years before 1 BCE or after 9999 CE, require an initial sign /// ///``` - /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use chrono::NaiveDateTime; /// # let parse_from_str = NaiveDateTime::parse_from_str; /// let fmt = "%Y-%m-%d %H:%M:%S"; /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); @@ -638,8 +638,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use std::str::FromStr; - /// use chrono::{Months, NaiveDate, NaiveDateTime}; + /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() @@ -741,8 +740,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use std::str::FromStr; - /// use chrono::{Months, NaiveDate, NaiveDateTime}; + /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() @@ -929,7 +927,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveTime, Utc}; + /// use chrono::{NaiveDate, Utc}; /// let dt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); /// assert_eq!(dt.timezone(), Utc); /// ``` @@ -1496,8 +1494,7 @@ impl Add for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDateTime, Months, NaiveDate}; - /// use std::str::FromStr; + /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), @@ -1603,8 +1600,7 @@ impl SubAssign for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDateTime, Months, NaiveDate}; -/// use std::str::FromStr; +/// use chrono::{Months, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() - Months::new(11), diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 79509f0277..5ab92d9559 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -250,7 +250,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; /// #[derive(Deserialize)] @@ -503,7 +503,7 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; /// #[derive(Deserialize)] @@ -753,7 +753,7 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; /// #[derive(Deserialize)] @@ -1000,7 +1000,7 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; /// #[derive(Deserialize)] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 880617e511..440ffead26 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -75,7 +75,7 @@ mod tests; /// All methods accepting fractional seconds will accept such values. /// /// ``` -/// use chrono::{NaiveDate, NaiveTime, Utc, TimeZone}; +/// use chrono::{NaiveDate, NaiveTime, Utc}; /// /// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); /// @@ -162,7 +162,7 @@ mod tests; /// will be represented as the second part being 60, as required by ISO 8601. /// /// ``` -/// use chrono::{Utc, TimeZone, NaiveDate}; +/// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); diff --git a/src/round.rs b/src/round.rs index 6f19efe841..101055a0e5 100644 --- a/src/round.rs +++ b/src/round.rs @@ -24,7 +24,7 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc, NaiveDate}; + /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); @@ -36,7 +36,7 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc, NaiveDate}; + /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); @@ -111,7 +111,7 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), @@ -128,7 +128,7 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), @@ -243,7 +243,7 @@ pub enum RoundingError { /// Error when the Duration exceeds the Duration from or until the Unix epoch. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!( @@ -256,7 +256,7 @@ pub enum RoundingError { /// Error when `Duration.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc, NaiveDate}; + /// # use chrono::{DurationRound, Duration, RoundingError, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap(); /// /// assert_eq!( @@ -269,7 +269,7 @@ pub enum RoundingError { /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); From 03a64cefd2e92d280c0367c198c5d247407f26c1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 15:00:33 +0200 Subject: [PATCH 277/999] Avoid deprecated methods in doctests --- src/datetime/serde.rs | 2 +- src/lib.rs | 2 +- src/naive/date.rs | 22 +++++++++++----------- src/naive/datetime/mod.rs | 30 +++++++++++++++--------------- src/naive/isoweek.rs | 8 ++++---- src/naive/time/mod.rs | 24 ++++++++++++------------ src/offset/fixed.rs | 12 ++++++++---- src/offset/local/mod.rs | 2 +- src/offset/utc.rs | 4 ++-- 9 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 0c49abf92a..12fc3dc89c 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -838,7 +838,7 @@ pub mod ts_milliseconds_option { /// } /// /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); + /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp_opt(1526522699, 918000000).unwrap()) })); /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; /// assert_eq!(s, E::V(S { time: None })); /// let t: E = serde_json::from_str(r#"{}"#)?; diff --git a/src/lib.rs b/src/lib.rs index a1547b13ae..76835f2112 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -323,7 +323,7 @@ //! use chrono::{DateTime, TimeZone, Utc}; //! //! // Construct a datetime from epoch: -//! let dt = Utc.timestamp(1_500_000_000, 0); +//! let dt = Utc.timestamp_opt(1_500_000_000, 0).unwrap(); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: diff --git a/src/naive/date.rs b/src/naive/date.rs index 314354731a..1fab3c5c71 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -376,7 +376,7 @@ impl NaiveDate { /// ``` /// use chrono::{NaiveDate, Weekday}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; /// /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); @@ -392,7 +392,7 @@ impl NaiveDate { /// /// ``` /// # use chrono::{NaiveDate, Weekday}; - /// # let from_ymd = NaiveDate::from_ymd; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; /// // Mo Tu We Th Fr Sa Su /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, @@ -875,7 +875,7 @@ impl NaiveDate { /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// - /// let dt: NaiveDateTime = d.and_hms_micro(12, 34, 56, 789_012); + /// let dt: NaiveDateTime = d.and_hms_micro_opt(12, 34, 56, 789_012).unwrap(); /// assert_eq!(dt.year(), 2015); /// assert_eq!(dt.weekday(), Weekday::Wed); /// assert_eq!(dt.second(), 56); @@ -1133,7 +1133,7 @@ impl NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let since = NaiveDate::signed_duration_since; /// /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), Duration::zero()); @@ -1434,7 +1434,7 @@ impl Datelike for NaiveDate { /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap(); /// /// // ...is preceded by the last day of the original month - /// d.pred().day() + /// d.pred_opt().unwrap().day() /// } /// /// assert_eq!(ndays_in_month(2015, 8), 31); @@ -1490,7 +1490,7 @@ impl Datelike for NaiveDate { /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(); /// /// // ...is preceded by the last day of the original year - /// d.pred().ordinal() + /// d.pred_opt().unwrap().ordinal() /// } /// /// assert_eq!(ndays_in_year(2015), 365); @@ -1713,7 +1713,7 @@ impl Datelike for NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) + Duration::zero(), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(86399), from_ymd(2014, 1, 1)); @@ -1756,7 +1756,7 @@ impl Add for NaiveDate { /// ``` /// use chrono::{NaiveDate, Months}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); @@ -1784,7 +1784,7 @@ impl Sub for NaiveDate { /// ``` /// use chrono::{NaiveDate, Months}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); @@ -1822,7 +1822,7 @@ impl Sub for NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) - Duration::zero(), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(86399), from_ymd(2014, 1, 1)); @@ -1865,7 +1865,7 @@ impl SubAssign for NaiveDate { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), Duration::zero()); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), Duration::days(1)); diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 880d96f011..7a38d4ffd6 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -474,8 +474,8 @@ impl NaiveDateTime { /// let nanos = dt.timestamp_nanos(); /// assert_eq!(nanos, 1_000_000_000_000_000_555); /// assert_eq!( - /// dt, - /// NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + /// Some(dt), + /// NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32) /// ); /// ``` #[inline] @@ -565,7 +565,7 @@ impl NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); @@ -598,7 +598,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_add_signed(Duration::zero()), @@ -671,7 +671,7 @@ impl NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); @@ -704,7 +704,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_sub_signed(Duration::zero()), @@ -789,7 +789,7 @@ impl NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// - /// let from_ymd = NaiveDate::from_ymd; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), @@ -806,7 +806,7 @@ impl NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), /// Duration::seconds(3600) + Duration::milliseconds(500)); @@ -1430,7 +1430,7 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); @@ -1452,7 +1452,7 @@ impl Timelike for NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); @@ -1541,7 +1541,7 @@ impl Add for NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); @@ -1563,7 +1563,7 @@ impl Add for NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); @@ -1639,7 +1639,7 @@ impl Sub for NaiveDateTime { /// ``` /// use chrono::{Duration, NaiveDate}; /// -/// let from_ymd = NaiveDate::from_ymd; +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), Duration::seconds(3600 + 60 + 1)); @@ -1655,7 +1655,7 @@ impl Sub for NaiveDateTime { /// /// ``` /// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), /// Duration::seconds(3600) + Duration::milliseconds(500)); @@ -1811,7 +1811,7 @@ impl str::FromStr for NaiveDateTime { /// use chrono::NaiveDateTime; /// /// let default_date = NaiveDateTime::default(); -/// assert_eq!(default_date, NaiveDateTime::from_timestamp(0, 0)); +/// assert_eq!(Some(default_date), NaiveDateTime::from_timestamp_opt(0, 0)); /// ``` impl Default for NaiveDateTime { fn default() -> Self { diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 45c2a81ede..df5556a120 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -58,7 +58,7 @@ impl IsoWeek { /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().year(), 2015); /// ``` /// @@ -67,7 +67,7 @@ impl IsoWeek { /// /// ``` /// # use chrono::{NaiveDate, Datelike, Weekday}; - /// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.year(), 2014); /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` @@ -85,7 +85,7 @@ impl IsoWeek { /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week(), 15); /// ``` #[inline] @@ -102,7 +102,7 @@ impl IsoWeek { /// ``` /// use chrono::{NaiveDate, Datelike, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week0(), 14); /// ``` #[inline] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 440ffead26..24453bf6a2 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -516,7 +516,7 @@ impl NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// - /// let from_hms = NaiveTime::from_hms; + /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; /// /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(11)), /// (from_hms(14, 4, 5), 0)); @@ -599,7 +599,7 @@ impl NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// - /// let from_hms = NaiveTime::from_hms; + /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; /// /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(2)), /// (from_hms(1, 4, 5), 0)); @@ -630,7 +630,7 @@ impl NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// - /// let from_hmsm = NaiveTime::from_hms_milli; + /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let since = NaiveTime::signed_duration_since; /// /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), @@ -656,7 +656,7 @@ impl NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; - /// # let from_hmsm = NaiveTime::from_hms_milli; + /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// # let since = NaiveTime::signed_duration_since; /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), /// Duration::seconds(1)); @@ -1020,7 +1020,7 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// -/// let from_hmsm = NaiveTime::from_hms_milli; +/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::zero(), from_hmsm(3, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(1), from_hmsm(3, 5, 8, 0)); @@ -1036,7 +1036,7 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::days(800), from_hmsm(3, 5, 7, 0)); @@ -1046,7 +1046,7 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); /// assert_eq!(leap + Duration::zero(), from_hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap + Duration::milliseconds(-500), from_hmsm(3, 5, 59, 800)); @@ -1087,7 +1087,7 @@ impl AddAssign for NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// -/// let from_hmsm = NaiveTime::from_hms_milli; +/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::zero(), from_hmsm(3, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(1), from_hmsm(3, 5, 6, 0)); @@ -1101,7 +1101,7 @@ impl AddAssign for NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::days(800), from_hmsm(3, 5, 7, 0)); /// ``` @@ -1110,7 +1110,7 @@ impl AddAssign for NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); /// assert_eq!(leap - Duration::zero(), from_hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap - Duration::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); @@ -1154,7 +1154,7 @@ impl SubAssign for NaiveTime { /// ``` /// use chrono::{Duration, NaiveTime}; /// -/// let from_hmsm = NaiveTime::from_hms_milli; +/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), Duration::zero()); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), Duration::milliseconds(25)); @@ -1172,7 +1172,7 @@ impl SubAssign for NaiveTime { /// /// ``` /// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), /// Duration::milliseconds(1500)); diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 475c3e99dc..246d6666ea 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -49,8 +49,10 @@ impl FixedOffset { #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::east_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() - /// .and_hms_opt(0, 0, 0).unwrap(); + /// let datetime = FixedOffset::east_opt(5 * hour) + /// .unwrap() + /// .with_ymd_and_hms(2016, 11, 08, 0, 0, 0) + /// .unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` #[must_use] @@ -83,8 +85,10 @@ impl FixedOffset { #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::west_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap() - /// .and_hms_opt(0, 0, 0).unwrap(); + /// let datetime = FixedOffset::west_opt(5 * hour) + /// .unwrap() + /// .with_ymd_and_hms(2016, 11, 08, 0, 0, 0) + /// .unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` #[must_use] diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 47b7570c10..030b6f952b 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -49,7 +49,7 @@ mod tz_info; /// use chrono::{Local, DateTime, TimeZone}; /// /// let dt: DateTime = Local::now(); -/// let dt: DateTime = Local.timestamp(0, 0); +/// let dt: DateTime = Local.timestamp_opt(0, 0).unwrap(); /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] diff --git a/src/offset/utc.rs b/src/offset/utc.rs index aeaeb672dc..dbcb8eecbb 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -35,9 +35,9 @@ use crate::{Date, DateTime}; /// ``` /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; /// -/// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); +/// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc); /// -/// assert_eq!(Utc.timestamp(61, 0), dt); +/// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] From 799cf8d18bef6568620657bb024d6dca57492a48 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 15:06:38 +0200 Subject: [PATCH 278/999] Update main examples and use `?` --- src/lib.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 76835f2112..25df724952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,21 +128,25 @@ //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! +//! # fn doctest() -> Option<()> { +//! //! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` +//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(9, 10, 11)?.and_local_timezone(Utc).unwrap()); +//! //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") -//! assert_eq!(dt, Utc.yo(2014, 189).and_hms_opt(9, 10, 11).unwrap()); +//! assert_eq!(dt, NaiveDate::from_yo_opt(2014, 189)?.and_hms_opt(9, 10, 11)?.and_utc()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. -//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms_opt(9, 10, 11).unwrap()); +//! assert_eq!(dt, NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc()); //! -//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap().and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap().and_local_timezone(Utc).unwrap()); -//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap()); +//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_milli_opt(9, 10, 11, 12)?.and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_micro_opt(9, 10, 11, 12_000)?.and_local_timezone(Utc).unwrap()); +//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_nano_opt(9, 10, 11, 12_000_000)?.and_local_timezone(Utc).unwrap()); //! //! // dynamic verification -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), -//! LocalResult::Single(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33).unwrap())); -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -//! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), +//! LocalResult::Single(NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc())); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); //! //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. @@ -150,6 +154,9 @@ //! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; +//! # Some(()) +//! # } +//! # doctest().unwrap(); //! ``` //! //! Various properties are available to the date and time, and can be altered individually. From 7c90f35a655e9aed9c444b6a938616f99f7426bc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 15:08:33 +0200 Subject: [PATCH 279/999] Ensure results are used --- src/format/parse.rs | 5 +++-- src/offset/local/mod.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 4c79f4a5bc..a48c026cff 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -530,8 +530,9 @@ where /// /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2000-01-02T03:04:05Z".parse::>(); -/// "2000-01-02 03:04:05Z".parse::>(); +/// "2000-01-02T03:04:05Z".parse::>()?; +/// "2000-01-02 03:04:05Z".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 030b6f952b..1adddb7d60 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -48,8 +48,9 @@ mod tz_info; /// ``` /// use chrono::{Local, DateTime, TimeZone}; /// -/// let dt: DateTime = Local::now(); -/// let dt: DateTime = Local.timestamp_opt(0, 0).unwrap(); +/// let dt1: DateTime = Local::now(); +/// let dt2: DateTime = Local.timestamp_opt(0, 0).unwrap(); +/// assert!(dt1 >= dt2); /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] From c14b3bb5f95806db9509b24f01a5cc0ad3621944 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 15:28:11 +0200 Subject: [PATCH 280/999] Fix documentation that is no longer true --- src/datetime/mod.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1bdda19009..2e9203a307 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1087,14 +1087,16 @@ where /// Accepts a relaxed form of RFC3339. /// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. +/// parts. /// /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, Utc}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; @@ -1106,14 +1108,16 @@ impl str::FromStr for DateTime { /// Accepts a relaxed form of RFC3339. /// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. +/// parts. /// /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, Local}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) /// ``` #[cfg(feature = "clock")] #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] From 1d1608239068427c910efd58a9de412248b6b73e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 16:46:43 +0200 Subject: [PATCH 281/999] Use deserialization result in doctests --- src/datetime/serde.rs | 35 +++++++++++++++++++++-------------- src/naive/datetime/serde.rs | 28 ++++++++++++++++++---------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 12fc3dc89c..72ada0ccaa 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -189,16 +189,17 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -316,16 +317,17 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -444,16 +446,17 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -570,16 +573,17 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -698,16 +702,17 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -962,16 +967,17 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_ts")] /// time: DateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -1082,16 +1088,17 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # use chrono::{DateTime, Utc}; + /// # use chrono::{DateTime, TimeZone, Utc}; /// # use serde_derive::Deserialize; /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_tsopt")] /// time: Option> /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 5ab92d9559..8107f384d9 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -128,13 +128,14 @@ pub mod ts_nanoseconds { /// # use chrono::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_ts")] /// time: NaiveDateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -253,13 +254,14 @@ pub mod ts_nanoseconds_option { /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_tsopt")] /// time: Option /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -378,13 +380,14 @@ pub mod ts_microseconds { /// # use chrono::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_ts")] /// time: NaiveDateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -506,13 +509,14 @@ pub mod ts_microseconds_option { /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_tsopt")] /// time: Option /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -631,13 +635,14 @@ pub mod ts_milliseconds { /// # use chrono::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_ts")] /// time: NaiveDateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -746,7 +751,7 @@ pub mod ts_milliseconds_option { } } - /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// Deserialize a `NaiveDateTime` from a millisecond timestamp or none /// /// Intended for use with `serde`s `deserialize_with` attribute. /// @@ -756,13 +761,14 @@ pub mod ts_milliseconds_option { /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_tsopt")] /// time: Option /// } /// - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -881,13 +887,14 @@ pub mod ts_seconds { /// # use chrono::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_ts")] /// time: NaiveDateTime /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] @@ -1003,13 +1010,14 @@ pub mod ts_seconds_option { /// # use chrono::naive::NaiveDateTime; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; - /// #[derive(Deserialize)] + /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_tsopt")] /// time: Option /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0) }); /// # Ok::<(), serde_json::Error>(()) /// ``` #[must_use] From bdf8b1951549357ff2ce6b85fea2bc689452cc01 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 23 May 2023 14:57:55 +0200 Subject: [PATCH 282/999] Deny warnings in doctests --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 25df724952..3737d1a9b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -382,7 +382,7 @@ //! Advanced time zone handling is not yet supported. //! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. -#![doc(html_root_url = "https://docs.rs/chrono/latest/")] +#![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] #![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 #![deny(missing_docs)] #![deny(missing_debug_implementations)] From 7fb3e5921c882041bf7df04c66b1809b7ed5c933 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 23 Apr 2023 22:45:59 +0200 Subject: [PATCH 283/999] Just rely on the standard library for Local::now --- src/offset/local/mod.rs | 5 +++-- src/offset/local/stub.rs | 11 ----------- src/offset/local/unix.rs | 7 +------ src/offset/local/windows.rs | 15 --------------- 4 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 1adddb7d60..74aa902b57 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -10,7 +10,8 @@ use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[allow(deprecated)] -use crate::{Date, DateTime}; +use crate::Date; +use crate::{DateTime, Utc}; // we don't want `stub.rs` when the target_os is not wasi or emscripten // as we use js-sys to get the date instead @@ -74,7 +75,7 @@ impl Local { )))] #[must_use] pub fn now() -> DateTime { - inner::now() + Utc::now().with_timezone(&Local) } /// Returns a `DateTime` which corresponds to the current date and time. diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 9ececd3c22..3b42717d82 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -13,10 +13,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; use super::{FixedOffset, Local}; use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; -pub(super) fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) -} - /// Converts a local `NaiveDateTime` to the `time::Timespec`. #[cfg(not(all( target_arch = "wasm32", @@ -92,13 +88,6 @@ struct Timespec { } impl Timespec { - /// Constructs a timespec representing the current time in UTC. - fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - /// Converts this timespec into the system's local time. fn local(self) -> Tm { let mut tm = Tm { diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 68b206cd90..d933cfb1d6 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,12 +12,7 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::Sys use super::tz_info::TimeZone; use super::{DateTime, FixedOffset, Local, NaiveDateTime}; -use crate::{Datelike, LocalResult, Utc}; - -pub(super) fn now() -> DateTime { - let now = Utc::now().naive_utc(); - naive_to_local(&now, false).unwrap() -} +use crate::{Datelike, LocalResult}; pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { TZ_INFO.with(|maybe_cache| { diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 6475929fe4..455a707839 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -15,7 +15,6 @@ use std::result::Result; use winapi::shared::minwindef::FILETIME; use winapi::um::minwinbase::SYSTEMTIME; -use winapi::um::sysinfoapi::GetLocalTime; use winapi::um::timezoneapi::{ SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, }; @@ -40,10 +39,6 @@ macro_rules! windows_sys_call { const HECTONANOSECS_IN_SEC: i64 = 10_000_000; const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; -pub(super) fn now() -> DateTime { - LocalSysTime::local().datetime() -} - /// Converts a local `NaiveDateTime` to the `time::Timespec`. pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { let naive_sys_time = system_time_from_naive_date_time(d); @@ -65,16 +60,6 @@ struct LocalSysTime { } impl LocalSysTime { - fn local() -> Self { - let mut now = MaybeUninit::::uninit(); - unsafe { GetLocalTime(now.as_mut_ptr()) } - // SAFETY: GetLocalTime cannot fail according to spec, so we can assume the value - // is initialized. - let st = unsafe { now.assume_init() }; - - Self::from_local_time(st).expect("Current local time must exist") - } - fn from_utc_time(utc_time: SYSTEMTIME) -> Result { let local_time = utc_to_local_time(&utc_time)?; let utc_secs = system_time_as_unix_seconds(&utc_time)?; From 2b7a068be87a6e21b8c9f68cad769d7c9c75b760 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 5 May 2023 11:08:07 +0200 Subject: [PATCH 284/999] Only implement the `offset_from_` methods on Local --- src/offset/local/mod.rs | 65 +++-------- src/offset/local/stub.rs | 216 +----------------------------------- src/offset/local/unix.rs | 38 +++---- src/offset/local/windows.rs | 65 +++++------ 4 files changed, 63 insertions(+), 321 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 74aa902b57..7f3f0f9488 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -8,7 +8,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; -use crate::naive::{NaiveDate, NaiveDateTime}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::Date; use crate::{DateTime, Utc}; @@ -104,33 +104,10 @@ impl TimeZone for Local { Local } - // they are easier to define in terms of the finished date and time unlike other offsets #[allow(deprecated)] fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { - self.from_local_date(local).map(|date| *date.offset()) - } - - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { - self.from_local_datetime(local).map(|datetime| *datetime.offset()) - } - - #[allow(deprecated)] - fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { - *self.from_utc_date(utc).offset() - } - - fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { - *self.from_utc_datetime(utc).offset() - } - - // override them for avoiding redundant works - #[allow(deprecated)] - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - // this sounds very strange, but required for keeping `TimeZone::ymd` sane. - // in the other words, we use the offset at the local midnight - // but keep the actual date unaltered (much like `FixedOffset`). - let midnight = self.from_local_datetime(&local.and_hms_opt(0, 0, 0).unwrap()); - midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) + // Get the offset at local midnight. + self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) } #[cfg(all( @@ -138,14 +115,9 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let mut local = local.clone(); - // Get the offset from the js runtime - let offset = - FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) - .unwrap(); - local -= crate::Duration::seconds(offset.local_minus_utc() as i64); - LocalResult::Single(DateTime::from_utc(local, offset)) + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + let offset = js_sys::Date::new_0().get_timezone_offset(); + LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } #[cfg(not(all( @@ -153,14 +125,14 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - inner::naive_to_local(local, true) + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + inner::offset_from_local_datetime(local) } #[allow(deprecated)] - fn from_utc_date(&self, utc: &NaiveDate) -> Date { - let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap()); - Date::from_utc(*utc, *midnight.offset()) + fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { + // Get the offset at midnight. + self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN)) } #[cfg(all( @@ -168,12 +140,9 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // Get the offset from the js runtime - let offset = - FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) - .unwrap(); - DateTime::from_utc(*utc, offset) + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + let offset = js_sys::Date::new_0().get_timezone_offset(); + LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } #[cfg(not(all( @@ -181,10 +150,8 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // this is OK to unwrap as getting local time from a UTC - // timestamp is never ambiguous - inner::naive_to_local(utc, false).unwrap() + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + inner::offset_from_utc_datetime(utc).unwrap() } } diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 3b42717d82..38ef680773 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -8,218 +8,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::time::{SystemTime, UNIX_EPOCH}; +use crate::{FixedOffset, LocalResult, NaiveDateTime}; -use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) -)))] -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - let spec = Timespec { - sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), - }, - nsec: tm.tm_nsec, - }; - - // Adjust for leap seconds - let mut tm = spec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - LocalResult::Single(tm_to_datetime(tm)) -} - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) -)))] -fn tm_to_datetime(mut tm: Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - - let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Converts this timespec into the system's local time. - fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[repr(C)] -pub(super) struct Tm { - /// Seconds after the minute - [0, 60] - tm_sec: i32, - - /// Minutes after the hour - [0, 59] - tm_min: i32, - - /// Hours after midnight - [0, 23] - tm_hour: i32, - - /// Day of the month - [1, 31] - tm_mday: i32, - - /// Months since January - [0, 11] - tm_mon: i32, - - /// Years since 1900 - tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - tm_wday: i32, - - /// Days since January 1 - [0, 365] - tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - tm_nsec: i32, -} - -fn time_to_tm(ts: i64, tm: &mut Tm) { - let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }; - - static YTAB: [[i64; 12]; 2] = [ - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - ]; - - let mut year = 1970; - - let dayclock = ts % 86400; - let mut dayno = ts / 86400; - - tm.tm_sec = (dayclock % 60) as i32; - tm.tm_min = ((dayclock % 3600) / 60) as i32; - tm.tm_hour = (dayclock / 3600) as i32; - tm.tm_wday = ((dayno + 4) % 7) as i32; - loop { - let yearsize = if leapyear(year) { 366 } else { 365 }; - if dayno >= yearsize { - dayno -= yearsize; - year += 1; - } else { - break; - } - } - tm.tm_year = (year - 1900) as i32; - tm.tm_yday = dayno as i32; - let mut mon = 0; - while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] { - dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon]; - mon += 1; - } - tm.tm_mon = mon as i32; - tm.tm_mday = dayno as i32 + 1; - tm.tm_isdst = 0; -} - -fn tm_to_time(tm: &Tm) -> i64 { - let mut y = tm.tm_year as i64 + 1900; - let mut m = tm.tm_mon as i64 + 1; - if m <= 2 { - y -= 1; - m += 12; - } - let d = tm.tm_mday as i64; - let h = tm.tm_hour as i64; - let mi = tm.tm_min as i64; - let s = tm.tm_sec as i64; - (365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400 - + 3600 * h - + 60 * mi - + s -} - -pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { - // FIXME: Add timezone logic - time_to_tm(sec, tm); -} - -pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { - tm_to_time(tm) +pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) } -pub(super) fn local_tm_to_time(tm: &Tm) -> i64 { - // FIXME: Add timezone logic - tm_to_time(tm) +pub(super) fn offset_from_local_datetime(_local_time: &NaiveDateTime) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index d933cfb1d6..ce96a6e3bb 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -11,10 +11,18 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; use super::tz_info::TimeZone; -use super::{DateTime, FixedOffset, Local, NaiveDateTime}; +use super::{FixedOffset, NaiveDateTime}; use crate::{Datelike, LocalResult}; -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + offset(local, true) +} + +fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { TZ_INFO.with(|maybe_cache| { maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) }) @@ -96,7 +104,7 @@ fn current_zone(var: Option<&str>) -> TimeZone { } impl Cache { - fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult> { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult { let now = SystemTime::now(); match now.duration_since(self.last_checked) { @@ -148,32 +156,16 @@ impl Cache { .offset(); return match FixedOffset::east_opt(offset) { - Some(offset) => LocalResult::Single(DateTime::from_utc(d, offset)), + Some(offset) => LocalResult::Single(offset), None => LocalResult::None, }; } // we pass through the year as the year of a local point in time must either be valid in that locale, or - // the entire time was skipped in which case we will return LocalResult::None anywa. - match self - .zone + // the entire time was skipped in which case we will return LocalResult::None anyway. + self.zone .find_local_time_type_from_local(d.timestamp(), d.year()) .expect("unable to select local time type") - { - LocalResult::None => LocalResult::None, - LocalResult::Ambiguous(early, late) => { - let early_offset = FixedOffset::east_opt(early.offset()).unwrap(); - let late_offset = FixedOffset::east_opt(late.offset()).unwrap(); - - LocalResult::Ambiguous( - DateTime::from_utc(d - early_offset, early_offset), - DateTime::from_utc(d - late_offset, late_offset), - ) - } - LocalResult::Single(tt) => { - let offset = FixedOffset::east_opt(tt.offset()).unwrap(); - LocalResult::Single(DateTime::from_utc(d - offset, offset)) - } - } + .map(|o| FixedOffset::east_opt(o.offset()).unwrap()) } } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 455a707839..84585170c2 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -19,8 +19,8 @@ use winapi::um::timezoneapi::{ SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, }; -use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use super::FixedOffset; +use crate::{Datelike, LocalResult, NaiveDateTime, Timelike}; /// This macro calls a Windows API FFI and checks whether the function errored with the provided error_id. If an error returns, /// the macro will return an `Error::last_os_error()`. @@ -39,54 +39,43 @@ macro_rules! windows_sys_call { const HECTONANOSECS_IN_SEC: i64 = 10_000_000; const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + offset(local, true) +} + /// Converts a local `NaiveDateTime` to the `time::Timespec`. -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { +pub(super) fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { let naive_sys_time = system_time_from_naive_date_time(d); let local_sys_time = match local { - false => LocalSysTime::from_utc_time(naive_sys_time), - true => LocalSysTime::from_local_time(naive_sys_time), + false => from_utc_time(naive_sys_time), + true => from_local_time(naive_sys_time), }; - if let Ok(local) = local_sys_time { - return LocalResult::Single(local.datetime()); + if let Ok(offset) = local_sys_time { + return LocalResult::Single(offset); } LocalResult::None } -struct LocalSysTime { - inner: SYSTEMTIME, - offset: i32, +fn from_utc_time(utc_time: SYSTEMTIME) -> Result { + let local_time = utc_to_local_time(&utc_time)?; + let utc_secs = system_time_as_unix_seconds(&utc_time)?; + let local_secs = system_time_as_unix_seconds(&local_time)?; + let offset = (local_secs - utc_secs) as i32; + Ok(FixedOffset::east_opt(offset).unwrap()) } -impl LocalSysTime { - fn from_utc_time(utc_time: SYSTEMTIME) -> Result { - let local_time = utc_to_local_time(&utc_time)?; - let utc_secs = system_time_as_unix_seconds(&utc_time)?; - let local_secs = system_time_as_unix_seconds(&local_time)?; - let offset = (local_secs - utc_secs) as i32; - Ok(Self { inner: local_time, offset }) - } - - fn from_local_time(local_time: SYSTEMTIME) -> Result { - let utc_time = local_to_utc_time(&local_time)?; - let utc_secs = system_time_as_unix_seconds(&utc_time)?; - let local_secs = system_time_as_unix_seconds(&local_time)?; - let offset = (local_secs - utc_secs) as i32; - Ok(Self { inner: local_time, offset }) - } - - fn datetime(self) -> DateTime { - let st = self.inner; - - let date = - NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).unwrap(); - let time = - NaiveTime::from_hms_opt(st.wHour as u32, st.wMinute as u32, st.wSecond as u32).unwrap(); - - let offset = FixedOffset::east_opt(self.offset).unwrap(); - DateTime::from_utc(date.and_time(time) - offset, offset) - } +fn from_local_time(local_time: SYSTEMTIME) -> Result { + let utc_time = local_to_utc_time(&local_time)?; + let utc_secs = system_time_as_unix_seconds(&utc_time)?; + let local_secs = system_time_as_unix_seconds(&local_time)?; + let offset = (local_secs - utc_secs) as i32; + Ok(FixedOffset::east_opt(offset).unwrap()) } fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { From 51cce84b867ccd023cc5ea7a3abdff560c5a5464 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 May 2023 13:41:54 +0200 Subject: [PATCH 285/999] Move stub into `local/mod.rs` --- src/offset/local/mod.rs | 29 +++++++++++++++++++---------- src/offset/local/stub.rs | 19 ------------------- 2 files changed, 19 insertions(+), 29 deletions(-) delete mode 100644 src/offset/local/stub.rs diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 7f3f0f9488..41be2a9d30 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -13,8 +13,14 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Date; use crate::{DateTime, Utc}; -// we don't want `stub.rs` when the target_os is not wasi or emscripten -// as we use js-sys to get the date instead +#[cfg(unix)] +#[path = "unix.rs"] +mod inner; + +#[cfg(windows)] +#[path = "windows.rs"] +mod inner; + #[cfg(all( not(unix), not(windows), @@ -24,16 +30,19 @@ use crate::{DateTime, Utc}; not(any(target_os = "emscripten", target_os = "wasi")) )) ))] -#[path = "stub.rs"] -mod inner; +mod inner { + use crate::{FixedOffset, LocalResult, NaiveDateTime}; -#[cfg(unix)] -#[path = "unix.rs"] -mod inner; + pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + } -#[cfg(windows)] -#[path = "windows.rs"] -mod inner; + pub(super) fn offset_from_local_datetime( + _local_time: &NaiveDateTime, + ) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + } +} #[cfg(unix)] mod tz_info; diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs deleted file mode 100644 index 38ef680773..0000000000 --- a/src/offset/local/stub.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use crate::{FixedOffset, LocalResult, NaiveDateTime}; - -pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) -} - -pub(super) fn offset_from_local_datetime(_local_time: &NaiveDateTime) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) -} From a8f177e62f0a1fd2b8ad4af0bf98a82ab4f8744e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 May 2023 13:45:05 +0200 Subject: [PATCH 286/999] Move wasmbind methods into inner module --- src/offset/local/mod.rs | 48 ++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 41be2a9d30..372fd49762 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -44,6 +44,24 @@ mod inner { } } +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] +mod inner { + use crate::{FixedOffset, LocalResult, NaiveDateTime}; + + pub(super) fn offset_from_utc_datetime(_utc: &NaiveDateTime) -> LocalResult { + let offset = js_sys::Date::new_0().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) + } +} + #[cfg(unix)] mod tz_info; @@ -119,21 +137,6 @@ impl TimeZone for Local { self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) } - #[cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))] - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { - let offset = js_sys::Date::new_0().get_timezone_offset(); - LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) - } - - #[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - )))] fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { inner::offset_from_local_datetime(local) } @@ -144,21 +147,6 @@ impl TimeZone for Local { self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN)) } - #[cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))] - fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { - let offset = js_sys::Date::new_0().get_timezone_offset(); - LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) - } - - #[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - )))] fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { inner::offset_from_utc_datetime(utc).unwrap() } From 656c941fc4293d1832ae273b26e78c2ae1f3a505 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 May 2023 21:00:51 +0200 Subject: [PATCH 287/999] Switch test to `TryFrom` --- src/naive/internals.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 78f787b935..842b3ac407 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -505,7 +505,7 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday { #[cfg(test)] mod tests { use num_iter::range_inclusive; - use num_traits::FromPrimitive; + use std::convert::TryFrom; use std::u32; use super::weekday_from_u32_mod7; @@ -888,7 +888,7 @@ mod tests { #[test] fn test_weekday_from_u32_mod7() { for i in 0..=1000 { - assert_eq!(weekday_from_u32_mod7(i), Weekday::from_u32(i % 7).unwrap()); + assert_eq!(weekday_from_u32_mod7(i), Weekday::try_from((i % 7) as u8).unwrap()); } assert_eq!(weekday_from_u32_mod7(u32::MAX), Weekday::Thu); } From c01e3a723883fc148c7e6052ddf4f6b42ff41e14 Mon Sep 17 00:00:00 2001 From: scarf Date: Sun, 28 May 2023 13:35:45 +0900 Subject: [PATCH 288/999] test: explicitly set `LANG` in gnu `date` --- tests/dateutils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index fb1e3e47a7..870e39298f 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -98,6 +98,7 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim // Z%Z - too many ways to represent it, will most likely fail let output = process::Command::new(path) + .env("LANG", "c") .arg("-d") .arg(format!( "{}-{:02}-{:02} {:02}:{:02}:{:02}", From 42fca00f2b265c56bf0a5fde4298e74a4dd63dab Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 19:20:14 -0700 Subject: [PATCH 289/999] test.yml run statement should fail early Force the `run` script to fail early using bash shell `set` options. Previously, errors before the last statement of the script would be ignored. --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a18e07aaf0..029dfe2b21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -119,9 +119,11 @@ jobs: with: node-version: "12" - run: | + set -euxo pipefail export RUST_BACKTRACE=1 curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh wasm-pack --version + shell: bash - run: cargo build --target ${{ matrix.target }} --color=always features_check_wasm: From c0a99b747b970c62ac24ed3b407354e3df5ad471 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 19:40:16 -0700 Subject: [PATCH 290/999] Use bash instead of sh The script at the URL uses bash-isms. Previously `sh` was silently emitting an error but by luck was succeeding. --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 029dfe2b21..ee6231bdc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,7 +121,8 @@ jobs: - run: | set -euxo pipefail export RUST_BACKTRACE=1 - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + 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 From 82221c023005cd0171f9c1af2607b7850d968c67 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 29 May 2023 10:59:34 +0200 Subject: [PATCH 291/999] Bump version to 0.4.25 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b925dc1431..e0b9ce76f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.24" +version = "0.4.25" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 3c3836d8ba204ecad398f54c3851a59493d7c949 Mon Sep 17 00:00:00 2001 From: tottoto Date: Tue, 30 May 2023 04:26:31 +0900 Subject: [PATCH 292/999] Remove num-iter dependency --- Cargo.toml | 1 - src/naive/date.rs | 4 +--- src/naive/internals.rs | 29 ++++++++++++++--------------- src/traits.rs | 4 +--- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0b9ce76f6..832806c12c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ android-tzdata = "0.1.1" serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } bincode = { version = "1.3.0" } -num-iter = { version = "0.1.35", default-features = false } doc-comment = { version = "0.3" } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] diff --git a/src/naive/date.rs b/src/naive/date.rs index 1fab3c5c71..8c1682f1f0 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2376,9 +2376,7 @@ mod tests { #[test] fn test_readme_doomsday() { - use num_iter::range_inclusive; - - for y in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { + for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { // even months let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 842b3ac407..49daf5d383 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -504,7 +504,6 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday { #[cfg(test)] mod tests { - use num_iter::range_inclusive; use std::convert::TryFrom; use std::u32; @@ -555,7 +554,7 @@ mod tests { #[test] fn test_of() { fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { - for ordinal in range_inclusive(ordinal1, ordinal2) { + for ordinal in ordinal1..=ordinal2 { let of = match Of::new(ordinal, flags) { Some(of) => of, None if !expected => continue, @@ -591,8 +590,8 @@ mod tests { #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { - for month in range_inclusive(month1, month2) { - for day in range_inclusive(day1, day2) { + for month in month1..=month2 { + for day in day1..=day2 { let mdf = match Mdf::new(month, day, flags) { Some(mdf) => mdf, None if !expected => continue, @@ -682,7 +681,7 @@ mod tests { #[test] fn test_of_fields() { for &flags in FLAGS.iter() { - for ordinal in range_inclusive(1u32, 366) { + for ordinal in 1u32..=366 { if let Some(of) = Of::new(ordinal, flags) { assert_eq!(of.ordinal(), ordinal); } @@ -695,7 +694,7 @@ mod tests { fn check(flags: YearFlags, ordinal: u32) { let of = Of::new(ordinal, flags).unwrap(); - for ordinal in range_inclusive(0u32, 1024) { + for ordinal in 0u32..=1024 { let of = of.with_ordinal(ordinal); assert_eq!(of, Of::new(ordinal, flags)); if let Some(of) = of { @@ -733,7 +732,7 @@ mod tests { for &flags in FLAGS.iter() { let mut prev = Of::new(1, flags).unwrap().weekday(); - for ordinal in range_inclusive(2u32, flags.ndays()) { + for ordinal in 2u32..=flags.ndays() { let of = Of::new(ordinal, flags).unwrap(); let expected = prev.succ(); assert_eq!(of.weekday(), expected); @@ -745,8 +744,8 @@ mod tests { #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { - for month in range_inclusive(1u32, 12) { - for day in range_inclusive(1u32, 31) { + for month in 1u32..=12 { + for day in 1u32..31 { let mdf = match Mdf::new(month, day, flags) { Some(mdf) => mdf, None => continue, @@ -766,7 +765,7 @@ mod tests { fn check(flags: YearFlags, month: u32, day: u32) { let mdf = Mdf::new(month, day, flags).unwrap(); - for month in range_inclusive(0u32, 16) { + for month in 0u32..=16 { let mdf = match mdf.with_month(month) { Some(mdf) => mdf, None if month > 12 => continue, @@ -779,7 +778,7 @@ mod tests { } } - for day in range_inclusive(0u32, 1024) { + for day in 0u32..=1024 { let mdf = match mdf.with_day(day) { Some(mdf) => mdf, None if day > 31 => continue, @@ -822,7 +821,7 @@ mod tests { #[test] fn test_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { + for i in 0u32..=8192 { if let Some(of) = Of(i).validate() { assert!(of.to_mdf().valid()); } @@ -831,7 +830,7 @@ mod tests { #[test] fn test_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { + for i in 0u32..=8192 { let mdf = Mdf(i); assert_eq!(mdf.valid(), mdf.to_of().is_some()); } @@ -839,7 +838,7 @@ mod tests { #[test] fn test_of_to_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { + for i in 0u32..=8192 { if let Some(of) = Of(i).validate() { assert_eq!(of, of.to_mdf().to_of().unwrap()); } @@ -848,7 +847,7 @@ mod tests { #[test] fn test_mdf_to_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { + for i in 0u32..=8192 { let mdf = Mdf(i); if mdf.valid() { assert_eq!(mdf, mdf.to_of().unwrap().to_mdf()); diff --git a/src/traits.rs b/src/traits.rs index 1b6af6926b..a6199dc6dc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -219,9 +219,7 @@ mod tests { date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) } - use num_iter::range_inclusive; - - for year in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) { + for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); assert_eq!( jan1_year.num_days_from_ce(), From f616167f8bba47f6b9a4aee906556e4fefcd2fab Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 19:09:43 -0700 Subject: [PATCH 293/999] yamllint cleanup lint.yml test.yml NFC fix most `yamllint` errors and warnings in `lint.yml` and `test.yml`. --- .github/workflows/lint.yml | 12 ++++++++---- .github/workflows/test.yml | 31 +++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9f52f477e4..8e5aac42c3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,16 +18,20 @@ jobs: - 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 -- -D warnings - - run: cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings + - run: | + cargo clippy --color=always --target x86_64-pc-windows-msvc \ + -- -D warnings + - run: | + cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ + -- -D warnings env: RUSTFLAGS: "-Dwarnings" cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 check-doc: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee6231bdc5..ee616c7731 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,9 +45,18 @@ jobs: with: toolchain: 1.56.1 - uses: Swatinem/rust-cache@v2 - # run --lib and --doc to avoid the long running integration tests which are run elsewhere - - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde --color=always -- --color=always - - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde --color=always -- --color=always + # run --lib and --doc to avoid the long running integration tests + # which are run elsewhere + - run: | + cargo test --lib \ + --features \ + unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \ + --color=always -- --color=always + - run: | + cargo test --doc \ + --features \ + unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \ + --color=always -- --color=always rust_versions: strategy: @@ -63,7 +72,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo check --benches - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets - # run --lib and --doc to avoid the long running integration tests which are run elsewhere + # run --lib and --doc to avoid the long running integration tests + # which are run elsewhere - run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always @@ -77,7 +87,13 @@ jobs: - uses: dtolnay/rust-toolchain@stable - 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 + - 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 + # run using `bash` on all platforms for consistent + # line-continuation marks + shell: bash - run: cargo test --lib --no-default-features - run: cargo test --doc --no-default-features @@ -139,7 +155,10 @@ 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 + - run: | + cargo hack check --feature-powerset --optional-deps serde,rkyv \ + --skip default --skip __internal_bench --skip __doctest \ + --skip iana-time-zone --skip pure-rust-locales cross-targets: strategy: From c0d848d4c815d8288d1387fade64ca9af50655b5 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 16:58:14 -0700 Subject: [PATCH 294/999] parse tests around nanosecond parsing bare '.' --- src/format/parse.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index a48c026cff..4a0ceb0f54 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -864,6 +864,7 @@ fn test_parse() { // fixed: dot plus nanoseconds check!("", [fix!(Nanosecond)]; ); // no field set, but not an error + check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); check!(".0", [fix!(Nanosecond)]; nanosecond: 0); @@ -886,13 +887,13 @@ fn test_parse() { check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(". 4", [fix!(Nanosecond)]; INVALID); check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); @@ -908,6 +909,7 @@ fn test_parse() { check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); @@ -921,6 +923,7 @@ fn test_parse() { check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); From b5b936af515f4cce124841bcd708d4591007bfa5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 May 2023 20:02:35 +0200 Subject: [PATCH 295/999] Error on negative durations in `DurationRound` --- src/round.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/round.rs b/src/round.rs index 101055a0e5..f43ab595c3 100644 --- a/src/round.rs +++ b/src/round.rs @@ -178,6 +178,9 @@ where T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { + if span < 0 { + return Err(RoundingError::DurationExceedsLimit); + } if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { return Err(RoundingError::TimestampExceedsLimit); } @@ -217,6 +220,9 @@ where T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { + if span < 0 { + return Err(RoundingError::DurationExceedsLimit); + } if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { return Err(RoundingError::TimestampExceedsLimit); } @@ -304,10 +310,10 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { - use super::{Duration, DurationRound, SubsecRound}; + use super::{Duration, DurationRound, RoundingError, SubsecRound}; use crate::offset::{FixedOffset, TimeZone, Utc}; - use crate::NaiveDate; use crate::Timelike; + use crate::{NaiveDate, NaiveDateTime}; #[test] fn test_round_subsecs() { @@ -760,4 +766,19 @@ mod tests { "1969-12-12 12:10:00 UTC" ); } + + #[test] + fn issue1010() { + let dt = NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288).unwrap(); + let span = Duration::microseconds(-7019067213869040); + assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = NaiveDateTime::from_timestamp_opt(320041586, 1920103021).unwrap(); + let span = Duration::nanoseconds(-8923838508697114584); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = NaiveDateTime::from_timestamp_opt(-2621440, 0).unwrap(); + let span = Duration::nanoseconds(-9223372036854771421); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + } } From 05c9408f434906851730c0698a19cad847107aa0 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:10:53 -0700 Subject: [PATCH 296/999] consolidate path to `date` --- tests/dateutils.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 870e39298f..3c473e7045 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -52,19 +52,23 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { } } +/// path to Unix `date` command. Should work on most Linux and Unixes. Not the +/// path for MacOS (/bin/date) which uses a different version of `date` with +/// different arguments (so it won't run which is okay). +/// for testing only +#[allow(dead_code)] +#[cfg(not(target_os = "aix"))] +const DATE_PATH: &'static str = "/usr/bin/date"; +#[allow(dead_code)] +#[cfg(target_os = "aix")] +const DATE_PATH: &'static str = "/opt/freeware/bin/date"; + #[test] #[cfg(unix)] fn try_verify_against_date_command() { - #[cfg(not(target_os = "aix"))] - let date_path = "/usr/bin/date"; - #[cfg(target_os = "aix")] - let date_path = "/opt/freeware/bin/date"; - - if !path::Path::new(date_path).exists() { - // date command not found, skipping - // avoid running this on macOS, which has path /bin/date - // as the required CLI arguments are not present in the - // macOS build. + + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {:?} not found, skipping", DATE_PATH); return; } @@ -75,7 +79,7 @@ fn try_verify_against_date_command() { || (2020..=2022).contains(&date.year()) || (2073..=2077).contains(&date.year()) { - verify_against_date_command_local(date_path, date); + verify_against_date_command_local(DATE_PATH, date); } date += chrono::Duration::hours(1); @@ -125,15 +129,13 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim #[test] #[cfg(target_os = "linux")] fn try_verify_against_date_command_format() { - let date_path = "/usr/bin/date"; - - if !path::Path::new(date_path).exists() { - // date command not found, skipping + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {:?} not found, skipping", DATE_PATH); return; } let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); while date.year() < 2008 { - verify_against_date_command_format_local(date_path, date); + verify_against_date_command_format_local(DATE_PATH, date); date += chrono::Duration::days(55); } } From b1729f3a9b17e242509329d202d9923926bc814d Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sat, 27 May 2023 21:48:25 -0700 Subject: [PATCH 297/999] try_verify_against_date sanity check date command Sanity check `date` command used in tests `try_verify_against_date_command` and `try_verify_against_date_command_format`. Make sure it's the kind of `data` command expected (accepts `--version`). Also, it's just good to print this for debugging purposes. --- tests/dateutils.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 3c473e7045..f8f89e3528 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -63,6 +63,23 @@ const DATE_PATH: &'static str = "/usr/bin/date"; #[cfg(target_os = "aix")] const DATE_PATH: &'static str = "/opt/freeware/bin/date"; +#[cfg(test)] +/// test helper to sanity check the date command behaves as expected +/// asserts the command succeeded +fn assert_run_date_version() { + // note environment variable `LANG` + match std::env::var_os("LANG") { + Some(lang) => eprintln!("LANG: {:?}", lang), + None => eprintln!("LANG not set"), + } + let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap(); + let stdout = String::from_utf8(out.stdout).unwrap(); + let stderr = String::from_utf8(out.stderr).unwrap(); + // note the `date` binary version + eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr); + assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH); +} + #[test] #[cfg(unix)] fn try_verify_against_date_command() { @@ -71,6 +88,7 @@ fn try_verify_against_date_command() { eprintln!("date command {:?} not found, skipping", DATE_PATH); return; } + assert_run_date_version(); let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); @@ -133,6 +151,8 @@ fn try_verify_against_date_command_format() { eprintln!("date command {:?} not found, skipping", DATE_PATH); return; } + assert_run_date_version(); + let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); while date.year() < 2008 { verify_against_date_command_format_local(DATE_PATH, date); From e41451dedc2d5d5b07f9582689bbbd7d91b3233f Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sat, 27 May 2023 22:13:28 -0700 Subject: [PATCH 298/999] try_verify_against_date_command NFC print progress messages Print progress messages for human users watching this long-running test `try_verify_against_date_command`. --- tests/dateutils.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index f8f89e3528..d671ecca87 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -83,7 +83,6 @@ fn assert_run_date_version() { #[test] #[cfg(unix)] fn try_verify_against_date_command() { - if !path::Path::new(DATE_PATH).exists() { eprintln!("date command {:?} not found, skipping", DATE_PATH); return; @@ -92,16 +91,29 @@ fn try_verify_against_date_command() { let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + eprintln!( + "Run command {:?} for every hour from {} to 2077, skipping some years...", + DATE_PATH, + date.year() + ); + let mut count: u64 = 0; + let mut year_at = date.year(); while date.year() < 2078 { if (1975..=1977).contains(&date.year()) || (2020..=2022).contains(&date.year()) || (2073..=2077).contains(&date.year()) { + if date.year() != year_at { + eprintln!("at year {}...", date.year()); + year_at = date.year(); + } verify_against_date_command_local(DATE_PATH, date); + count += 1; } date += chrono::Duration::hours(1); } + eprintln!("Command {:?} was run {} times", DATE_PATH, count); } #[cfg(target_os = "linux")] From 373c6e46de5f412e3c19c541d53b719f8a50b185 Mon Sep 17 00:00:00 2001 From: tottoto Date: Tue, 30 May 2023 07:47:54 +0900 Subject: [PATCH 299/999] Update to 2021 edition --- Cargo.toml | 2 +- src/format/parsed.rs | 2 -- src/month.rs | 5 +---- src/naive/date.rs | 6 +----- src/naive/datetime/mod.rs | 1 - src/naive/internals.rs | 1 - src/weekday.rs | 4 +--- 7 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 832806c12c..26f516ecc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["date-and-time"] readme = "README.md" license = "MIT/Apache-2.0" exclude = ["/ci/*"] -edition = "2018" +edition = "2021" rust-version = "1.56.0" [lib] diff --git a/src/format/parsed.rs b/src/format/parsed.rs index d51959a018..9526785ee2 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,8 +4,6 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use core::convert::TryFrom; - use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; diff --git a/src/month.rs b/src/month.rs index 8340cbc23f..11df7613dd 100644 --- a/src/month.rs +++ b/src/month.rs @@ -1,4 +1,4 @@ -use core::{convert::TryFrom, fmt}; +use core::fmt; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -12,7 +12,6 @@ use crate::OutOfRange; /// /// It is possible to convert from a date to a month independently /// ``` -/// # use std::convert::TryFrom; /// use chrono::prelude::*; /// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); /// // `2019-10-28T09:10:11Z` @@ -349,8 +348,6 @@ mod month_serde { #[cfg(test)] mod tests { - use core::convert::TryFrom; - use super::Month; use crate::{Datelike, OutOfRange, TimeZone, Utc}; diff --git a/src/naive/date.rs b/src/naive/date.rs index 8c1682f1f0..4bcb306836 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -5,7 +5,6 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::convert::TryFrom; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; @@ -2284,10 +2283,7 @@ mod tests { }; use crate::oldtime::Duration; use crate::{Datelike, Weekday}; - use std::{ - convert::{TryFrom, TryInto}, - i32, u32, - }; + use std::{i32, u32}; #[test] fn diff_months() { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 7a38d4ffd6..0a3f70380e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -5,7 +5,6 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::convert::TryFrom; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 49daf5d383..65b47ae73b 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -504,7 +504,6 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday { #[cfg(test)] mod tests { - use std::convert::TryFrom; use std::u32; use super::weekday_from_u32_mod7; diff --git a/src/weekday.rs b/src/weekday.rs index 995e79c85c..e2973cf798 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -1,4 +1,4 @@ -use core::{convert::TryFrom, fmt}; +use core::fmt; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -14,7 +14,6 @@ use crate::OutOfRange; /// # Example /// ``` /// use chrono::Weekday; -/// use std::convert::TryFrom; /// /// let monday = "Monday".parse::().unwrap(); /// assert_eq!(monday, Weekday::Mon); @@ -233,7 +232,6 @@ impl fmt::Debug for ParseWeekdayError { #[cfg(test)] mod tests { use super::Weekday; - use std::convert::TryFrom; #[test] fn test_num_days_from() { From becc22219d12f7fd27b2811618e9d432baf4acc5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 30 May 2023 18:09:48 +0200 Subject: [PATCH 300/999] Revert backport of #807 --- src/datetime/mod.rs | 5 - src/datetime/tests.rs | 616 ++---------------------------- src/format/mod.rs | 12 +- src/format/parse.rs | 732 +++++------------------------------- src/format/scan.rs | 100 +---- src/format/strftime.rs | 82 ---- src/naive/date.rs | 51 +-- src/naive/datetime/tests.rs | 66 +--- src/naive/time/mod.rs | 3 + src/naive/time/tests.rs | 85 +---- 10 files changed, 182 insertions(+), 1570 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2e9203a307..65a39aa8da 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -560,9 +560,6 @@ impl DateTime { /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP /// and email headers. /// - /// The RFC 2822 standard allows arbitrary intermixed whitespace. - /// See [RFC 2822 Appendix A.5] - /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( @@ -570,8 +567,6 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` - /// - /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 87f72f9c4c..c6be142d08 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -240,94 +240,6 @@ fn test_datetime_sub_months() { ); } -// local helper function to easily create a DateTime -fn ymdhms( - fixedoffset: &FixedOffset, - year: i32, - month: u32, - day: u32, - hour: u32, - min: u32, - sec: u32, -) -> DateTime { - fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() -} - -// local helper function to easily create a DateTime -fn ymdhms_milli( - fixedoffset: &FixedOffset, - year: i32, - month: u32, - day: u32, - hour: u32, - min: u32, - sec: u32, - milli: i64, -) -> DateTime { - fixedoffset - .with_ymd_and_hms(year, month, day, hour, min, sec) - .unwrap() - .checked_add_signed(Duration::milliseconds(milli)) - .unwrap() -} - -// local helper function to easily create a DateTime -fn ymdhms_micro( - fixedoffset: &FixedOffset, - year: i32, - month: u32, - day: u32, - hour: u32, - min: u32, - sec: u32, - micro: i64, -) -> DateTime { - fixedoffset - .with_ymd_and_hms(year, month, day, hour, min, sec) - .unwrap() - .checked_add_signed(Duration::microseconds(micro)) - .unwrap() -} - -// local helper function to easily create a DateTime -fn ymdhms_nano( - fixedoffset: &FixedOffset, - year: i32, - month: u32, - day: u32, - hour: u32, - min: u32, - sec: u32, - nano: i64, -) -> DateTime { - fixedoffset - .with_ymd_and_hms(year, month, day, hour, min, sec) - .unwrap() - .checked_add_signed(Duration::nanoseconds(nano)) - .unwrap() -} - -// local helper function to easily create a DateTime -fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { - Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() -} - -// local helper function to easily create a DateTime -fn ymdhms_milli_utc( - year: i32, - month: u32, - day: u32, - hour: u32, - min: u32, - sec: u32, - milli: i64, -) -> DateTime { - Utc.with_ymd_and_hms(year, month, day, hour, min, sec) - .unwrap() - .checked_add_signed(Duration::milliseconds(milli)) - .unwrap() -} - #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -432,15 +344,16 @@ fn test_datetime_with_timezone() { } #[test] -fn test_datetime_rfc2822() { +fn test_datetime_rfc2822_and_rfc3339() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); - - // timezone 0 assert_eq!( Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - // timezone +05 + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -452,157 +365,55 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); - // seconds 60 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) + .and_hms_milli_opt(23, 16, 9, 150) .unwrap() ) .unwrap() - .to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" - ); - - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) - ); - assert_eq!( - ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), - "Thu, 19 Feb 2015 00:20:32 +0500" - ); - assert_eq!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), - Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) - ); - assert_ne!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), - Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) - ); - - // many varying whitespace intermixed - assert_eq!( - DateTime::::parse_from_rfc2822( - "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" - ), - Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) - ); - // example from RFC 2822 Appendix A.5. - assert_eq!( - DateTime::::parse_from_rfc2822( - "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" - ), - Ok( - ymdhms( - &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), - 1969, 2, 13, 23, 32, 0, - ) - ) + .to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" ); - // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" assert_eq!( - DateTime::::parse_from_rfc2822( - "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" - ), - Ok( - ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() ) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" ); - - // bad year - assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); - // wrong format - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() - ); - // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") - .is_err()); - // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") - .is_err()); - // wrong day of week separator '.' - assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); - // *trailing* space causes failure - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() - ); -} - -#[test] -fn test_datetime_rfc3339() { - let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); - let edt0 = FixedOffset::east_opt(0).unwrap(); - - // timezone 0 - assert_eq!( - Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - // timezone +05 assert_eq!( - edt5.from_local_datetime( + edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) + .and_hms_micro_opt(23, 59, 59, 1_234_567) .unwrap() ) .unwrap() .to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); - - assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); - assert_eq!( - ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); - assert_eq!( - ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-19T00:00:00.234567+05:00" - ); - assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), - Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) - ); - assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), - Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) - ); - assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), - Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) - ); - assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + "2015-02-18T23:59:60.234567+05:00" ); assert_eq!( - ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-19T00:00:00.234567+05:00" - ); - assert_eq!( - ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), - Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt5 + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -613,8 +424,8 @@ fn test_datetime_rfc3339() { ); assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt5 + DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -623,41 +434,6 @@ fn test_datetime_rfc3339() { ) .unwrap()) ); - assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); - - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() - ); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() - ); } #[test] @@ -799,106 +575,13 @@ fn test_datetime_from_str() { } #[test] -fn test_parse_datetime_utc() { - // valid cases - let valid = [ - "2001-02-03T04:05:06Z", - "2001-02-03T04:05:06+0000", - "2001-02-03T04:05:06-00:00", - "2001-02-03T04:05:06-01:00", - "2012-12-12T12:12:12Z", - "2015-02-18T23:16:09.153Z", - "2015-2-18T23:16:09.153Z", - "+2015-2-18T23:16:09.153Z", - "-77-02-18T23:16:09Z", - "+82701-05-6T15:9:60.898989898989Z", - ]; - for &s in &valid { - eprintln!("test_parse_datetime_utc valid {:?}", s); - let d = match s.parse::>() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::>() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - let invalid = [ - "", // empty - "Z", // missing data - "15Z", // missing data - "15:8:9Z", // missing date - "15-8-9Z", // missing time or date - "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format - "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format - "1441497364.649", // valid datetime, wrong format - "+1441497364.649", // valid datetime, wrong format - "+1441497364", // valid datetime, wrong format - "+1441497364Z", // valid datetime, wrong format - "2014/02/03 04:05:06Z", // valid datetime, wrong format - "2001-02-03T04:05:0600:00", // valid datetime, timezone too close - "2015-15-15T15:15:15Z", // invalid datetime - "2012-12-12T12:12:12x", // invalid timezone - "2012-123-12T12:12:12Z", // invalid month - "2012-12-77T12:12:12Z", // invalid day - "2012-12-12T26:12:12Z", // invalid hour - "2012-12-12T12:61:12Z", // invalid minute - "2012-12-12T12:12:62Z", // invalid second - "2012-12-12 T12:12:12Z", // space after date - "2012-12-12t12:12:12Z", // wrong divider 't' - "2012-12-12T12:12:12ZZ", // trailing literal 'Z' - "+802701-12-12T12:12:12Z", // invalid year (out of bounds) - "+ 2012-12-12T12:12:12Z", // invalid space before year - "2012 -12-12T12:12:12Z", // space after year - "2012 -12-12T12:12:12Z", // multi space after year - "2012- 12-12T12:12:12Z", // space after year divider - "2012- 12-12T12:12:12Z", // multi space after year divider - "2012-12-12T 12:12:12Z", // space after date-time divider - "2012-12-12T12 :12:12Z", // space after hour - "2012-12-12T12 :12:12Z", // multi space after hour - "2012-12-12T12: 12:12Z", // space before minute - "2012-12-12T12: 12:12Z", // multi space before minute - "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider - "2012-12-12T12:12:12Z ", // trailing space - " 2012-12-12T12:12:12Z", // leading space - "2001-02-03T04:05:06-00 00", // invalid timezone spacing - "2001-02-03T04:05:06-01: 00", // invalid timezone spacing - "2001-02-03T04:05:06-01 :00", // invalid timezone spacing - "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing - "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing - "2001-02-03T04:05:06-01 : :00", // invalid timezone spacing - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format - ]; - for &s in invalid.iter() { - eprintln!("test_parse_datetime_utc invalid {:?}", s); - assert!(s.parse::>().is_err()); - } -} - -#[test] -fn test_utc_datetime_from_str() { - let edt = FixedOffset::east_opt(570 * 60).unwrap(); - let edt0 = FixedOffset::east_opt(0).unwrap(); - let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); +fn test_datetime_parse_from_str() { + let ymdhms = |y, m, d, h, n, s, off| { + FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() + }; assert_eq!( - DateTime::::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) + DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") @@ -907,235 +590,6 @@ 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!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), - ); - assert_eq!( - "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) - ); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - // no test for `DateTime`, we cannot verify that much. -} - -#[test] -fn test_utc_datetime_from_str_with_spaces() { - let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); - // with varying spaces - should succeed - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!( - Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), - Ok(dt), - ); - assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); - // with varying spaces - should fail - // leading whitespace in format - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err()); - // trailing whitespace in format - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); - // extra mid-string whitespace in format - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); - // mismatched leading whitespace - assert!(Utc.datetime_from_str("\tAug 09 2013 23:54:35", "\n%b %d %Y %H:%M:%S").is_err()); - // mismatched trailing whitespace - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err()); - // mismatched mid-string whitespace - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err()); - // trailing whitespace in format - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); - // trailing whitespace (newline) in format - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); - // leading space in data - assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); - // trailing space in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); - // trailing tab in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); - // mismatched newlines - assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); - // trailing literal in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); -} - -#[test] -fn test_datetime_parse_from_str() { - let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); - let parse = DateTime::::parse_from_str; - - // timezone variations - - // - // %Z - // - // wrong timezone format - assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); - // bad timezone data? - assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); - // bad timezone data - assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); - - // - // %z - // - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); - assert!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); - assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); - // trailing newline after timezone - assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z ").is_err()); - // trailing colon - assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); - // trailing colon with space - assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); - // trailing colon, mismatch space - assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); - // wrong timezone data - assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); - - // - // %:z - // - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); - assert!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:").is_err()); - // wrong timezone data - assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z").is_err()); - // timezone data hs too many colons - assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); - // timezone data hs too many colons - assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); - - // - // %::z - // - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); - assert!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z").is_err()); - // mismatching colon expectations - assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z").is_err()); - assert!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z").is_err()); - // wrong timezone data - assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); - // mismatching colons and spaces - assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); - // mismatching colons expectations - assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); - assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); - // mismatching colons expectations mid-string - assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); - // mismatching colons expectations, before end - assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); - - // - // %:::z - // - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); - // wrong timezone data - assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); - - // - // %::::z - // - // too many colons - assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); - // too many colons - assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); - // too many colons - assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); - // too many colons - assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); - - // - // %#z - // - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); - assert!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z ").is_err()); - assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); - assert!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); - assert!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); - assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); - assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); - // timezone with partial minutes adjacent hours - assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); - // bad timezone data - assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); - // bad timezone data (partial minutes) - assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); - // bad timezone data (partial minutes) with trailing space - assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); - // bad timezone data (partial minutes) mid-string - assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); - // bad timezone data - assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); - // timezone data ambiguous with hours - assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); } #[test] diff --git a/src/format/mod.rs b/src/format/mod.rs index 75c0194fd5..93c9f86d09 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -207,27 +207,27 @@ pub enum Fixed { /// /// It does not support parsing, its use in the parser is an immediate failure. TimezoneName, - /// Offset from the local time to UTC (`+09:00` or `-0400` or `+00:00`). + /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). /// - /// In the parser, the colon may be omitted, + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). /// - /// In the parser, the colon may be omitted, + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. /// The offset is limited from `-24:00:00` to `+24:00:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetDoubleColon, /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). /// - /// In the parser, the colon may be omitted, + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. /// The offset is limited from `-24` to `+24`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetTripleColon, - /// Offset from the local time to UTC (`+09:00` or `-0400` or `Z`). + /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). /// - /// In the parser, the colon may be omitted, + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. diff --git a/src/format/parse.rs b/src/format/parse.rs index 4a0ceb0f54..ca8193e15d 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -42,10 +42,6 @@ fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<() }) } -/// Parse an RFC 2822 format datetime -/// e.g. `Fri, 21 Nov 1997 09:55:06 -0600` -/// -/// This function allows arbitrary intermixed whitespace per RFC 2822 appendix A.5 fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ @@ -241,7 +237,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st /// /// - Padding-agnostic (for numeric items). /// The [`Pad`](./enum.Pad.html) field is completely ignored, -/// so one can prepend any number of zeroes before numbers. +/// so one can prepend any number of whitespace then any number of zeroes before numbers. /// /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> @@ -326,37 +322,13 @@ where s = &s[prefix.len()..]; } - Item::Space(item_space) => { - for expect in item_space.chars() { - let actual = match s.chars().next() { - Some(c) => c, - None => { - return Err((s, TOO_SHORT)); - } - }; - if expect != actual { - return Err((s, INVALID)); - } - // advance `s` forward 1 char - s = scan::s_next(s); - } + Item::Space(_) => { + s = s.trim_left(); } #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedSpace(ref item_space) => { - for expect in item_space.chars() { - let actual = match s.chars().next() { - Some(c) => c, - None => { - return Err((s, TOO_SHORT)); - } - }; - if expect != actual { - return Err((s, INVALID)); - } - // advance `s` forward 1 char - s = scan::s_next(s); - } + Item::OwnedSpace(_) => { + s = s.trim_left(); } Item::Numeric(ref spec, ref _pad) => { @@ -389,6 +361,7 @@ where Internal(ref int) => match int._dummy {}, }; + s = s.trim_left(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -481,26 +454,26 @@ where | &TimezoneOffsetDoubleColon | &TimezoneOffsetTripleColon | &TimezoneOffset => { - s = scan::trim1(s); - let offset = - try_consume!(scan::timezone_offset(s, scan::consume_colon_maybe)); + let offset = try_consume!(scan::timezone_offset( + s.trim_left(), + scan::colon_or_space + )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - s = scan::trim1(s); - let offset = - try_consume!(scan::timezone_offset_zulu(s, scan::consume_colon_maybe)); + let offset = try_consume!(scan::timezone_offset_zulu( + s.trim_left(), + scan::colon_or_space + )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } - &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - s = scan::trim1(s); let offset = try_consume!(scan::timezone_offset_permissive( - s, - scan::consume_colon_maybe + s.trim_left(), + scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -525,14 +498,15 @@ where } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are accepted as the separator between the date and time -/// parts. +/// A space or a 'T' are acepted as the separator between the date and time +/// parts. Additional spaces are allowed between each component. /// +/// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2000-01-02T03:04:05Z".parse::>()?; -/// "2000-01-02 03:04:05Z".parse::>()?; -/// # Ok::<(), chrono::ParseError>(()) +/// "2012-12-12T12:12:12Z".parse::>(); +/// "2012-12-12 12:12:12Z".parse::>(); +/// "2012- 12-12T12: 12:12Z".parse::>(); /// ``` impl str::FromStr for DateTime { type Err = ParseError; @@ -540,19 +514,25 @@ impl str::FromStr for DateTime { fn from_str(s: &str) -> ParseResult> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), + Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), + Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -574,6 +554,7 @@ impl str::FromStr for DateTime { #[cfg(test)] #[test] fn test_parse() { + use super::IMPOSSIBLE; use super::*; // workaround for Rust issue #22255 @@ -585,11 +566,9 @@ fn test_parse() { macro_rules! check { ($fmt:expr, $items:expr; $err:tt) => ( - eprintln!("test_parse: format {:?}", $fmt); assert_eq!(parse_all($fmt, &$items), Err($err)) ); ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { - eprintln!("test_parse: format {:?}", $fmt); let mut expected = Parsed::new(); $(expected.$k = Some($v);)* assert_eq!(parse_all($fmt, &$items), Ok(expected)) @@ -600,126 +579,35 @@ fn test_parse() { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); - check!("abc", []; TOO_LONG); - check!("🤠", []; TOO_LONG); // whitespaces check!("", [sp!("")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!("")]; TOO_LONG); - check!(" ", [sp!(" ")]; TOO_LONG); - check!(" ", [sp!(" ")]; TOO_LONG); - check!(" ", [sp!(" ")]; TOO_LONG); - check!("", [sp!(" ")]; TOO_SHORT); - check!(" ", [sp!(" ")]; TOO_SHORT); - check!(" ", [sp!(" ")]; TOO_SHORT); - check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); - check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); - check!(" ", [sp!(" "), sp!(" ")]; ); - check!(" ", [sp!(" "), sp!(" ")]; ); - check!(" ", [sp!(" "), sp!(" ")]; ); - check!(" ", [sp!(" "), sp!(" "), sp!(" ")]; ); - check!("\t", [sp!("")]; TOO_LONG); - check!(" \n\r \n", [sp!("")]; TOO_LONG); - check!("\t", [sp!("\t")]; ); - check!("\t", [sp!(" ")]; INVALID); - check!(" ", [sp!("\t")]; INVALID); - check!("\t\r", [sp!("\t\r")]; ); - check!("\t\r ", [sp!("\t\r ")]; ); - check!("\t \r", [sp!("\t \r")]; ); - check!(" \t\r", [sp!(" \t\r")]; ); - check!(" \n\r \n", [sp!(" \n\r \n")]; ); - check!(" \t\n", [sp!(" \t")]; TOO_LONG); - check!(" \n\t", [sp!(" \t\n")]; INVALID); - check!("\u{2002}", [sp!("\u{2002}")]; ); - // most unicode whitespace characters - check!( - "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [sp!("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; - ); - // most unicode whitespace characters - check!( - "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [ - sp!("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), - sp!("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") - ]; - ); + check!(" ", [sp!("")]; ); + check!("\t", [sp!("")]; ); + check!(" \n\r \n", [sp!("")]; ); check!("a", [sp!("")]; TOO_LONG); - check!("a", [sp!(" ")]; INVALID); - // a Space containing a literal can match a literal, but this should not be done - check!("a", [sp!("a")]; ); - check!("abc", [sp!("")]; TOO_LONG); - check!("abc", [sp!(" ")]; INVALID); - check!(" abc", [sp!("")]; TOO_LONG); - check!(" abc", [sp!(" ")]; TOO_LONG); - - // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal - check!("", [lit!("")]; ); check!("", [lit!("a")]; TOO_SHORT); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); - check!("🤠", [lit!("a")]; INVALID); check!("A", [lit!("a")]; INVALID); - check!("a", [lit!("z")]; INVALID); - check!("a", [lit!("🤠")]; TOO_SHORT); - check!("a", [lit!("\u{0363}a")]; TOO_SHORT); - check!("\u{0363}a", [lit!("a")]; INVALID); - check!("\u{0363}a", [lit!("\u{0363}a")]; ); - check!("a", [lit!("ab")]; TOO_SHORT); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("1", [lit!("1")]; ); - check!("1234", [lit!("1234")]; ); - check!("+1234", [lit!("+1234")]; ); - check!("PST", [lit!("PST")]; ); - check!("🤠", [lit!("🤠")]; ); - check!("🤠a", [lit!("🤠"), lit!("a")]; ); - check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); - check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); - // literals can be together check!("xy", [lit!("xy")]; ); - check!("xyz", [lit!("xyz")]; ); - // or literals can be apart check!("xy", [lit!("x"), lit!("y")]; ); - check!("xyz", [lit!("x"), lit!("yz")]; ); - check!("xyz", [lit!("xy"), lit!("z")]; ); - check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); - // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; INVALID); - check!("x y", [lit!("x"), sp!(" "), lit!("y")]; ); - - // whitespaces + literals - check!("a\n", [lit!("a"), sp!("\n")]; ); - check!("\tab\n", [sp!("\t"), lit!("ab"), sp!("\n")]; ); - check!("ab\tcd\ne", [lit!("ab"), sp!("\t"), lit!("cd"), sp!("\n"), lit!("e")]; ); - check!("+1ab\tcd\r\n+,.", [lit!("+1ab"), sp!("\t"), lit!("cd"), sp!("\r\n"), lit!("+,.")]; ); - // whitespace and literals can be intermixed - check!("a\tb", [lit!("a\tb")]; ); - check!("a\tb", [lit!("a"), sp!("\t"), lit!("b")]; ); + check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); // numeric check!("1987", [num!(Year)]; year: 1987); check!("1987 ", [num!(Year)]; TOO_LONG); check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed check!("x123", [num!(Year)]; INVALID); - check!("o123", [num!(Year)]; INVALID); check!("2015", [num!(Year)]; year: 2015); check!("0000", [num!(Year)]; year: 0); check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; INVALID); - check!(" \t987", [sp!(" \t"), num!(Year)]; year: 987); - check!(" \t987🤠", [sp!(" \t"), num!(Year), lit!("🤠")]; year: 987); - check!("987🤠", [num!(Year), lit!("🤠")]; year: 987); + check!(" \t987", [num!(Year)]; year: 987); check!("5", [num!(Year)]; year: 5); check!("5\0", [num!(Year)]; TOO_LONG); check!("\x005", [num!(Year)]; INVALID); @@ -728,16 +616,12 @@ fn test_parse() { check!("12345", [nums!(Year), lit!("5")]; year: 1234); check!("12345", [num0!(Year), lit!("5")]; year: 1234); check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; INVALID); - check!("1234 1234", [num!(Year), sp!(" "), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; INVALID); + check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234xx1234", [num!(Year), lit!("xx"), num!(Year)]; year: 1234); - check!("1234 x 1234", [num!(Year), sp!(" "), lit!("x"), sp!(" "), num!(Year)]; year: 1234); - check!("1234 x 1235", [num!(Year), sp!(" "), lit!("x"), sp!(" "), lit!("1235")]; year: 1234); + check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); // signed numeric check!("-42", [num!(Year)]; year: -42); @@ -746,18 +630,10 @@ fn test_parse() { check!("+0042", [num!(Year)]; year: 42); check!("-42195", [num!(Year)]; year: -42195); check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; INVALID); - check!(" +42195", [num!(Year)]; INVALID); - check!(" -42195", [num!(Year)]; INVALID); - check!(" +42195", [num!(Year)]; INVALID); - check!("-42195 ", [num!(Year)]; TOO_LONG); - check!("+42195 ", [num!(Year)]; TOO_LONG); + check!(" -42195", [num!(Year)]; year: -42195); + check!(" +42195", [num!(Year)]; year: 42195); check!(" - 42", [num!(Year)]; INVALID); check!(" + 42", [num!(Year)]; INVALID); - check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); - check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); - check!(" - 42", [sp!(" "), num!(Year)]; INVALID); - check!(" + 42", [sp!(" "), num!(Year)]; INVALID); check!("-", [num!(Year)]; TOO_SHORT); check!("+", [num!(Year)]; TOO_SHORT); @@ -765,41 +641,26 @@ fn test_parse() { check!("345", [num!(Ordinal)]; ordinal: 345); check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; INVALID); - check!("345 ", [num!(Ordinal)]; TOO_LONG); - check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); - check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); - check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); - check!("345🤠", [num!(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num!(Ordinal)]; INVALID); + check!(" 345", [num!(Ordinal)]; ordinal: 345); check!(" +345", [num!(Ordinal)]; INVALID); check!(" -345", [num!(Ordinal)]; INVALID); - check!("\t345", [sp!("\t"), num!(Ordinal)]; ordinal: 345); - check!(" +345", [sp!(" "), num!(Ordinal)]; INVALID); - check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields - check!("1234 5678", [num!(Year), num!(IsoYear)]; INVALID); check!("1234 5678", - [num!(Year), sp!(" "), num!(IsoYear)]; + [num!(Year), num!(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; - INVALID); - check!("12 34🤠56 78", - [num!(YearDiv100), sp!(" "), num!(YearMod100), - lit!("🤠"), num!(IsoYearDiv100), sp!(" "), num!(IsoYearMod100)]; year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); check!("1 2 3 4 5 6", - [num!(Month), sp!(" "), num!(Day), sp!(" "), num!(WeekFromSun), sp!(" "), - num!(WeekFromMon), sp!(" "), num!(IsoWeek), sp!(" "), num!(NumDaysFromSun)]; + [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), + num!(NumDaysFromSun)]; month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); check!("7 89 01", - [num!(WeekdayFromMon), sp!(" "), num!(Ordinal), sp!(" "), num!(Hour12)]; + [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); check!("23 45 6 78901234 567890123", - [num!(Hour), sp!(" "), num!(Minute), sp!(" "), num!(Second), sp!(" "), - num!(Nanosecond), sp!(" "), num!(Timestamp)]; + [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); @@ -808,7 +669,6 @@ fn test_parse() { check!("Apr", [fix!(ShortMonthName)]; month: 4); check!("APR", [fix!(ShortMonthName)]; month: 4); check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("\u{0363}APR", [fix!(ShortMonthName)]; INVALID); check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed check!("A", [fix!(ShortMonthName)]; TOO_SHORT); check!("Sol", [fix!(ShortMonthName)]; INVALID); @@ -846,15 +706,7 @@ fn test_parse() { check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [sp!(" "), fix!(LowerAmPm)]; hour_div_12: 0); - check!("Am🤠", [fix!(LowerAmPm), lit!("🤠")]; hour_div_12: 0); - check!("🤠Am", [lit!("🤠"), fix!(LowerAmPm)]; hour_div_12: 0); - check!("\u{0363}am", [fix!(LowerAmPm)]; INVALID); - check!("\u{0360}am", [fix!(LowerAmPm)]; INVALID); check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("Am ", [fix!(LowerAmPm)]; TOO_LONG); - check!("a.m.", [fix!(LowerAmPm)]; INVALID); - check!("A.M.", [fix!(LowerAmPm)]; INVALID); check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fix!(LowerAmPm)]; TOO_SHORT); check!("p", [fix!(LowerAmPm)]; TOO_SHORT); @@ -872,21 +724,11 @@ fn test_parse() { check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421951", [fix!(Nanosecond)]; nanosecond: 421_951_000); - check!(".4219512", [fix!(Nanosecond)]; nanosecond: 421_951_200); - check!(".42195123", [fix!(Nanosecond)]; nanosecond: 421_951_230); check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".4219508035", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".42195080354", [fix!(Nanosecond)]; nanosecond: 421_950_803); check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003", [fix!(Nanosecond)]; nanosecond: 3); - check!(".0000000031", [fix!(Nanosecond)]; nanosecond: 3); - check!(".0000000035", [fix!(Nanosecond)]; nanosecond: 3); check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".0000000009", [fix!(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000); + check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(". 4", [fix!(Nanosecond)]; INVALID); check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming @@ -898,12 +740,8 @@ fn test_parse() { check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("4210", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("421🤠", [internal_fix!(Nanosecond3NoDot), lit!("🤠")]; nanosecond: 421_000_000); - check!("🤠421", [lit!("🤠"), internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("123456789", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); @@ -911,13 +749,10 @@ fn test_parse() { check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("1234567", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); - check!("123456789", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); @@ -925,375 +760,58 @@ fn test_parse() { check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("1234567890", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets - - // TimezoneOffset - check!("1", [fix!(TimezoneOffset)]; INVALID); - check!("12", [fix!(TimezoneOffset)]; INVALID); - check!("123", [fix!(TimezoneOffset)]; INVALID); - check!("1234", [fix!(TimezoneOffset)]; INVALID); - check!("12345", [fix!(TimezoneOffset)]; INVALID); - check!("123456", [fix!(TimezoneOffset)]; INVALID); - check!("1234567", [fix!(TimezoneOffset)]; INVALID); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; INVALID); - check!("+12 34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; INVALID); - check!("+12: :34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:::34", [fix!(TimezoneOffset)]; INVALID); - check!("+12::::34", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); - check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" +12:34", [fix!(TimezoneOffset)]; INVALID); - check!(" -12:34", [fix!(TimezoneOffset)]; INVALID); - check!("\t -12:34", [fix!(TimezoneOffset)]; INVALID); - check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 :34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); - check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 :34", [fix!(TimezoneOffset)]; INVALID); - check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); - check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("A", [fix!(TimezoneOffset)]; INVALID); - check!("PST", [fix!(TimezoneOffset)]; INVALID); - check!("#Z", [fix!(TimezoneOffset)]; INVALID); - check!(":Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z:", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!(" :Z", [fix!(TimezoneOffset)]; INVALID); - check!(" Z", [fix!(TimezoneOffset)]; INVALID); - check!(" z", [fix!(TimezoneOffset)]; INVALID); - - // TimezoneOffsetColon - check!("1", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); - check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("A", [fix!(TimezoneOffsetColon)]; INVALID); - check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); - // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` - // and `TimezoneOffsetTripleColon` for function `parse_internal`. - // No need for separate tests for `TimezoneOffsetDoubleColon` and - // `TimezoneOffsetTripleColon`. - - // TimezoneOffsetZ - check!("1", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fix!(TimezoneOffsetZ)]; INVALID); - check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` - // in function `parse_internal`. - // No need for separate tests for `TimezoneOffsetColonZ`. - - // TimezoneOffsetPermissive - check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); - check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); - check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - - // TimezoneName - check!("CEST", [fix!(TimezoneName)]; ); - check!("cest", [fix!(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); - check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); - check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); + check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); + check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); + check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); + check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -1306,31 +824,10 @@ fn test_parse() { num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); - check!("20150204143705.567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("20150204143705.567891", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567891000); - check!("20150204143705.567891023", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567891023); - check!("Mon, 10 Jun 2013 09:32:37 GMT", + check!("Mon, 10 Jun 2013 09:32:37 GMT", [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", - [lit!("🤠"), fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), lit!("🤠"), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT"), lit!("🤠")]; + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", @@ -1351,22 +848,6 @@ fn test_parse() { check!("12345678901234.56789", [num!(Timestamp), fix!(Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); - - // docstring examples from `impl str::FromStr` - check!("2000-01-02T03:04:05Z", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), - internal_fix!(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); - check!("2000-01-02 03:04:05Z", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), sp!(" "), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), - internal_fix!(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); } #[cfg(test)] @@ -1383,8 +864,6 @@ fn test_rfc2822() { ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace - ("Tue, 20 Jan 2015\t17:35:20\t-0800\t\t(UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok("Tue, 20 Jan 2015 17:35:20 -0800"), @@ -1431,7 +910,6 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), - ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -1519,26 +997,11 @@ fn test_rfc3339() { ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' - ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD - ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS - ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value - ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format - ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T", Err(TOO_SHORT)), // missing HMS - ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S - ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { @@ -1553,7 +1016,6 @@ fn test_rfc3339() { // Test against test data above for &(date, checkdate) in testdates.iter() { - eprintln!("test_rfc3339: date {:?}, expect {:?}", date, checkdate); let d = rfc3339_to_datetime(date); // parse a date let dt = match d { // did we get a value? diff --git a/src/format/scan.rs b/src/format/scan.rs index 50315fef60..2962ef162b 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -198,41 +198,9 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } } -/// Returns slice remaining after first char. -/// If <=1 chars in `s` then return an empty slice -pub(super) fn s_next(s: &str) -> &str { - match s.char_indices().nth(1) { - Some((offset, _)) => &s[offset..], - None => { - // one or zero chars in `s`, return empty string - &s[s.len()..] - } - } -} - -/// If the first `char` is whitespace then consume it and return `s`. -/// Else return `s`. -pub(super) fn trim1(s: &str) -> &str { - match s.chars().next() { - Some(c) if c.is_whitespace() => s_next(s), - Some(_) | None => s, - } -} - -/// Consumes one colon char `:` if it is at the front of `s`. -/// Always returns `Ok(s)`. -pub(super) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> { - if s.is_empty() { - // nothing consumed - return Ok(s); - } - - if s.starts_with(':') { - s = s_next(s); - // consumed `':'` - } - - Ok(s) +/// Consumes any number (including zero) of colon or spaces. +pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { + Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -447,65 +415,3 @@ fn test_rfc2822_comments() { ); } } - -#[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 fc97da3c0f..24bae20c0a 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -513,32 +513,12 @@ impl<'a> Iterator for StrftimeItems<'a> { 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!( @@ -552,63 +532,12 @@ fn test_strftime_items() { 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]); @@ -628,10 +557,6 @@ fn test_strftime_items() { 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]); } @@ -741,13 +666,6 @@ fn test_strftime_docs() { assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); - - // 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")] diff --git a/src/naive/date.rs b/src/naive/date.rs index 4bcb306836..b9404a15f5 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2061,10 +2061,13 @@ impl str::FromStr for NaiveDate { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -2868,28 +2871,24 @@ mod tests { // valid cases let valid = [ "-0000000123456-1-2", - "-123456-1-2", + " -123456 - 1 - 2 ", "-12345-1-2", "-1234-12-31", "-7-6-5", "350-2-28", "360-02-29", "0360-02-29", - "2015-2-18", - "2015-02-18", + "2015-2 -18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { - eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; - eprintln!("d {:?} (NaiveDate)", d); let s_ = format!("{:?}", d); - eprintln!("s_ {:?}", s_); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, @@ -2897,7 +2896,6 @@ mod tests { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; - eprintln!("d_ {:?} (NaiveDate)", d_); assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ @@ -2910,36 +2908,13 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - let invalid = [ - "", // empty - "x", // invalid - "Fri, 09 Aug 2013 GMT", // valid date, wrong format - "Sat Jun 30 2012", // valid date, wrong format - "1441497364.649", // valid datetime, wrong format - "+1441497364.649", // valid datetime, wrong format - "+1441497364", // valid datetime, wrong format - "2014/02/03", // valid date, wrong format - "2014", // datetime missing data - "2014-01", // datetime missing data - "2014-01-00", // invalid day - "2014-11-32", // invalid day - "2014-13-01", // invalid month - "2014-13-57", // invalid month, day - "2001 -02-03", // space after year - "2001- 02-03", // space before month - "2001 - 02-03", // space around year-month divider - "2001-02 -03", // space after month - "2001-02- 03", // space before day - "2001-02 - 03", // space around month-day divider - "2001-02-03 ", // trailing space - " 2001-02-03", // leading space - " -123456 - 1 - 2 ", // many spaces - "9999999-9-9", // invalid year (out of bounds) - ]; - for &s in &invalid { - eprintln!("test_date_from_str invalid {:?}", s); - assert!(s.parse::().is_err()); - } + assert!("".parse::().is_err()); + assert!("x".parse::().is_err()); + assert!("2014".parse::().is_err()); + assert!("2014-01".parse::().is_err()); + assert!("2014-01-00".parse::().is_err()); + assert!("2014-13-57".parse::().is_err()); + assert!("9999999-9-9".parse::().is_err()); // out-of-bounds } #[test] @@ -2950,7 +2925,7 @@ mod tests { Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u=%Y-%j"), + NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), Ok(ymd(2015, 2, 2)) ); assert_eq!( diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 96ff3b15b5..07309a48ac 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -197,15 +197,11 @@ fn test_datetime_timestamp() { fn test_datetime_from_str() { // valid cases let valid = [ - "2001-02-03T04:05:06", - "2012-12-12T12:12:12", - "2015-02-18T23:16:09.153", - "2015-2-18T23:16:09.153", + "2015-2-18T23:16:9.15", "-77-02-18T23:16:09", - "+82701-05-6T15:9:60.898989898989", + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { - eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -230,48 +226,16 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - let invalid = [ - "", // empty - "x", // invalid / missing data - "15", // missing data - "15:8:9", // looks like a time (invalid date) - "15-8-9", // looks like a date (invalid) - "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format - "Sat Jun 30 23:59:60 2012", // valid date, wrong format - "1441497364.649", // valid date, wrong format - "+1441497364.649", // valid date, wrong format - "+1441497364", // valid date, wrong format - "2014/02/03 04:05:06", // valid date, wrong format - "2015-15-15T15:15:15", // invalid date - "2012-12-12T12:12:12x", // bad timezone / trailing literal - "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal - "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal - "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal - "2012-123-12T12:12:12", // invalid month - "2012 -12-12T12:12:12", // space after year - "2012 -12-12T12:12:12", // multi space after year - "2012- 12-12T12:12:12", // space before month - "2012- 12-12T12:12:12", // multi space before month - "2012-12-12 T12:12:12", // space after day - "2012-12-12T 12:12:12", // space after date-time divider - "2012-12-12T12 :12:12", // space after hour - "2012-12-12T12 :12:12", // multi space after hour - "2012-12-12T12: 12:12", // space before minute - "2012-12-12T12: 12:12", // multi space before minute - "2012-12-12T12 : 12:12", // space around hour-minute divider - "2012-12-12T12:12:12 ", // trailing space - " 2012-12-12T12:12:12", // leading space - "2012-12-12t12:12:12", // bad divider 't' - "2012-12-12 12:12:12", // missing divider 'T' - "2012-12-12T12:12:12Z", // trailing char 'Z' - "+ 82701-123-12T12:12:12", // strange year, invalid month - "+802701-123-12T12:12:12", // out-of-bound year, invalid month - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", // many spaces - ]; - for &s in invalid.iter() { - eprintln!("test_datetime_from_str invalid {:?}", s); - assert!(s.parse::().is_err()); - } + assert!("".parse::().is_err()); + assert!("x".parse::().is_err()); + assert!("15".parse::().is_err()); + assert!("15:8:9".parse::().is_err()); + assert!("15-8-9".parse::().is_err()); + assert!("2015-15-15T15:15:15".parse::().is_err()); + assert!("2012-12-12T12:12:12x".parse::().is_err()); + assert!("2012-123-12T12:12:12".parse::().is_err()); + assert!("+ 82701-123-12T12:12:12".parse::().is_err()); + assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound } #[test] @@ -285,12 +249,8 @@ fn test_datetime_parse_from_str() { NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) ); // ignore offset - assert!( - // intermixed whitespace - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S").is_err() - ); assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u %H%M%S"), + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), Ok(ymdhms(2015, 2, 2, 0, 0, 0)) ); assert_eq!( diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 24453bf6a2..3700c5cba2 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1305,11 +1305,14 @@ impl str::FromStr for NaiveTime { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), + Item::Space(""), ]; let mut parsed = Parsed::new(); diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index c14c02fb72..62c46a247e 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -232,37 +232,12 @@ fn test_date_from_str() { "0:0:0", "0:0:0.0000000", "0:0:0.0000003", - "01:02:03", - "4:3:2.1", - "9:8:7", - "09:8:7", - "9:08:7", - "9:8:07", - "09:08:7", - "09:8:07", - "09:08:7", - "9:08:07", - "09:08:07", - "9:8:07.123", - "9:08:7.123", - "09:8:7.123", - "09:08:7.123", - "9:08:07.123", - "09:8:07.123", - "09:08:07.123", - "09:08:07.123", - "09:08:07.1234", - "09:08:07.12345", - "09:08:07.123456", - "09:08:07.1234567", - "09:08:07.12345678", - "09:08:07.123456789", - "09:08:07.1234567891", - "09:08:07.12345678912", + " 4 : 3 : 2.1 ", + " 09:08:07 ", + " 9:8:07 ", "23:59:60.373929310237", ]; for &s in &valid { - eprintln!("test_time_parse_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -287,42 +262,15 @@ fn test_date_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - let invalid = [ - "", // empty - "x", // invalid - "15", // missing data - "15:8", // missing data - "15:8:x", // missing data, invalid data - "15:8:9x", // missing data, invalid data - "23:59:61", // invalid second (out of bounds) - "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) - "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) - "1441497364.649", // valid datetime, not a NaiveTime - "+1441497364.649", // valid datetime, not a NaiveTime - "+1441497364", // valid datetime, not a NaiveTime - "01 :02:03", // space after hour - "01: 02:03", // space before minute - "01 : 02:03", // space around hour-minute divider - "01:02 :03", // space after minute - "01:02: 03", // space before second - "01:02 : 03", // space around minute-second divider - "01:02:03 .456", // space after second - "01:02:03. 456", // space before fraction - "01:02:03 ", // trailing space - "01:02:03.456 ", // trailing space - " 01:02:03", // leading space - " 4 : 3 : 2.1 ", // spaces intermixed throughout - "001:02:03", // invalid hour - "01:002:03", // invalid minute - "01:02:003", // invalid second - "12:34:56.x", // invalid fraction - "12:34:56. 0", // invalid fraction format - "09:08:00000000007", // invalid second / invalid fraction format - ]; - for &s in &invalid { - eprintln!("test_time_parse_from_str invalid {:?}", s); - assert!(s.parse::().is_err()); - } + assert!("".parse::().is_err()); + assert!("x".parse::().is_err()); + assert!("15".parse::().is_err()); + assert!("15:8".parse::().is_err()); + assert!("15:8:x".parse::().is_err()); + assert!("15:8:9x".parse::().is_err()); + assert!("23:59:61".parse::().is_err()); + assert!("12:34:56.x".parse::().is_err()); + assert!("12:34:56. 0".parse::().is_err()); } #[test] @@ -333,15 +281,6 @@ fn test_time_parse_from_str() { Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); - assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); - assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); - assert_eq!( - NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), - Ok(hms(12, 59, 0)) - ); - assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_err()); - assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_err()); - assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_err()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } From 8bd13db2425301a9b60a309a79b61c72f1cf79f0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 30 May 2023 18:24:51 +0200 Subject: [PATCH 301/999] Use result in doctest --- src/format/parse.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index ca8193e15d..ed3f91f6df 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -504,9 +504,10 @@ where /// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012- 12-12T12: 12:12Z".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) /// ``` impl str::FromStr for DateTime { type Err = ParseError; From 7e6646d85b1e1f511d2ab9436e48bd113aa78b36 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 30 May 2023 11:29:29 +0200 Subject: [PATCH 302/999] Update README --- README.md | 85 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ad95918899..639a09144d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[Chrono][docsrs]: Date and Time for Rust +[Chrono][docsrs]: Timezone-aware date and time handling ======================================== [![Chrono GitHub Actions][gh-image]][gh-checks] @@ -15,45 +15,66 @@ [gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg [gitter]: https://gitter.im/chrono-rs/chrono -It aims to be a feature-complete superset of -the [time](https://github.com/rust-lang-deprecated/time) library. -In particular, +Chrono aims to provide all functionality needed to do correct operations on dates and times in the +[proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): -* Chrono strictly adheres to ISO 8601. -* Chrono is timezone-aware by default, with separate timezone-naive types. -* Chrono is space-optimal and (while not being the primary goal) reasonably efficient. +* The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware + by default, with separate timezone-naive types. +* Operations that may produce an invalid or ambiguous date and time return `Option` or + [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). +* Configurable parsing and formatting with an `strftime` inspired date and time formatting syntax. +* The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with + the current timezone of the OS. +* Types and operations are implemented to be reasonably efficient. -There were several previous attempts to bring a good date and time library to Rust, -which Chrono builds upon and should acknowledge: +Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate +[Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for +full timezone support. -* [Initial research on - the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) +## Documentation + +See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference. ## Limitations -Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. +* Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +* Date types are limited to about +/- 262,000 years from the common epoch. +* Time types are limited to nanosecond accuracy. +* Leap seconds can be represented, but Chrono does not fully support them. + See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). + +## Crate features + +Default features: + +* `alloc`: Enable features that depend on allocation (primarily string formatting) +* `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` + and adds interoperation with standard library types and traits. +* `clock`: Enables reading the system time (`now`) and local timezone (`Local`). +* `wasmbind`: Interface with the JS Date API for the `wasm32` target. + +Optional features: + +* `serde`: Enable serialization/deserialization via serde. +* `rkyv`: Enable serialization/deserialization via rkyv. +* `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). +* `old_time`: compatability with the `Duration` type of the `time` 0.1 crate (deprecated). +* `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate. +* `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. + The implementation and API may change or even be removed in a patch release. Feedback welcome. + +## Rust version requirements + +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.56.0**. -Date types are limited in about +/- 262,000 years from the common epoch. -Time types are limited in the nanosecond accuracy. +The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done +lightly. -[Leap seconds are supported in the representation but -Chrono doesn't try to make use of them](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html#leap-second-handling). -(The main reason is that leap seconds are not really predictable.) -Almost *every* operation over the possible leap seconds will ignore them. -Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -if you want. +## License -Chrono inherently does not support an inaccurate or partial date and time representation. -Any operation that can be ambiguous will return `None` in such cases. -For example, "a month later" of 2014-01-30 is not well-defined -and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. +This project is licensed under either of -Non ISO week handling is not yet supported. -For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -crate ([sources](https://github.com/bcourtine/chrono-ext/)). +* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +* [MIT License](https://opensource.org/licenses/MIT) -Advanced time zone handling is not yet supported. -For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. +at your option. From 5b8449e1f2fb863c2a2c69368a5c5e0366821fc8 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 30 May 2023 21:17:11 +0200 Subject: [PATCH 303/999] Bump version to 0.4.26 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 26f516ecc6..b89975421c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.25" +version = "0.4.26" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 865e5991b5300ae07a6a4ae31e4198bd53a7401a Mon Sep 17 00:00:00 2001 From: Kevin Matthes <92332892+kevinmatthes@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:52:28 +0100 Subject: [PATCH 304/999] Create CITATION.cff --- CITATION.cff | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..44723dfceb --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,23 @@ +# Parser settings. +cff-version: 1.2.0 +message: Please cite this crate using these information. + +# Version information. +date-released: 2022-11-12 +version: 0.4.23 + +# Project information. +abstract: Date and time library for Rust +authors: + - alias: . + # email: + family-names: . + given-names: . + # website: +license: + - Apache-2.0 + - MIT +repository-artifact: https://crates.io/crates/chrono +repository-code: https://github.com/chronotope/chrono +title: chrono +url: https://docs.rs/chrono From a03b2957e2791605993d096e7d952176b5b956ef Mon Sep 17 00:00:00 2001 From: Kevin Matthes <92332892+kevinmatthes@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:27:55 +0100 Subject: [PATCH 305/999] Add CFF validation job to lint.yml --- .github/workflows/lint.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8e5aac42c3..7e26d4e6b4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,3 +43,13 @@ jobs: - run: cargo doc --all-features --no-deps env: RUSTDOCFLAGS: -Dwarnings + + cffconvert: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: citation-file-format/cffconvert-github-action@2.0.0 + with: + args: --validate From 7ec93b100f25189a63c74c96222714564bf61204 Mon Sep 17 00:00:00 2001 From: Kevin Matthes <92332892+kevinmatthes@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:35:33 +0100 Subject: [PATCH 306/999] Add author names and update revision information --- CITATION.cff | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 44723dfceb..260fb0940e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,17 +3,24 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2022-11-12 -version: 0.4.23 +date-released: 2023-03-12 +version: 0.4.24 # Project information. abstract: Date and time library for Rust authors: - - alias: . - # email: - family-names: . - given-names: . - # website: + - alias: quodlibetor + family-names: Maister + given-names: Brandon W. + - alias: djc + family-names: Ochtman + given-names: Dirkjan + - alias: lifthrasiir + family-names: Seonghoon + given-names: Kang + - alias: esheppa + family-names: Sheppard + given-names: Eric license: - Apache-2.0 - MIT From 5eeedae729fd4a53a73ca670a0d673e20e995300 Mon Sep 17 00:00:00 2001 From: Kevin Matthes <92332892+kevinmatthes@users.noreply.github.com> Date: Mon, 29 May 2023 15:57:08 +0200 Subject: [PATCH 307/999] Update CITATION.cff --- CITATION.cff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 260fb0940e..2a88aea8e9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-03-12 -version: 0.4.24 +date-released: 2023-05-29 +version: 0.4.25 # Project information. abstract: Date and time library for Rust From 26e4f24df518b6fdeb344d692de9cf9c4de5dba2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 May 2023 09:42:48 +0200 Subject: [PATCH 308/999] Update CITATION.cff --- CITATION.cff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 2a88aea8e9..6ed9a60b1d 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-05-29 -version: 0.4.25 +date-released: 2023-05-31 +version: 0.4.26 # Project information. abstract: Date and time library for Rust From b6cc4dbe63d672acba5eeca9080fcea5acf75d2f Mon Sep 17 00:00:00 2001 From: tottoto Date: Thu, 1 Jun 2023 16:23:08 +0900 Subject: [PATCH 309/999] Format toml with taplo (#1117) --- .github/workflows/lint.yml | 8 ++++++++ Cargo.toml | 4 ++-- ci/core-test/Cargo.toml | 6 +++--- deny.toml | 6 +++--- taplo.toml | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 taplo.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7e26d4e6b4..a0cf75d9de 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,6 +27,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/Cargo.toml b/Cargo.toml index b89975421c..2745c67ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,12 +35,12 @@ rustc-serialize = { version = "0.3.20", optional = true } 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] diff --git a/ci/core-test/Cargo.toml b/ci/core-test/Cargo.toml index b35ff9a7dc..4258f9d90c 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 13b9fac5f7..0e486c8474 100644 --- a/deny.toml +++ b/deny.toml @@ -4,9 +4,9 @@ copyleft = "deny" [advisories] ignore = [ - "RUSTSEC-2020-0071", # time 0.1, doesn't affect the API we use - "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) - "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility + "RUSTSEC-2020-0071", # time 0.1, doesn't affect the API we use + "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/taplo.toml b/taplo.toml new file mode 100644 index 0000000000..5fe42eb83f --- /dev/null +++ b/taplo.toml @@ -0,0 +1,4 @@ +include = ["deny.toml", "**/Cargo.toml"] + +[formatting] +inline_table_expand = false From 01833a18114fb3f732594d7a1c3e5f32eb738f80 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 1 Jun 2023 18:05:42 +0200 Subject: [PATCH 310/999] Use `Borrow` in `DateTime::signed_duration_since` --- src/datetime/mod.rs | 8 +++++--- src/datetime/tests.rs | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 65a39aa8da..f3e79ee661 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; @@ -409,8 +408,11 @@ impl DateTime { /// This does not overflow or underflow at all. #[inline] #[must_use] - pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { - self.datetime.signed_duration_since(rhs.datetime) + pub fn signed_duration_since( + self, + rhs: impl Borrow>, + ) -> OldDuration { + self.datetime.signed_duration_since(rhs.borrow().datetime) } /// Returns a view to the naive UTC datetime. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index c6be142d08..e86ac4f04e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -313,6 +313,15 @@ fn test_datetime_offset() { assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); } +#[test] +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); +} + #[test] fn test_datetime_date_and_time() { let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); From 2dbf66f391f2c9a590fa5a27d3f32bcc8fb21277 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 10:22:59 +0200 Subject: [PATCH 311/999] Add `Sub` implementation that takes rhs by reference --- src/datetime/mod.rs | 9 +++++++++ src/datetime/tests.rs | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index f3e79ee661..14ce537ab4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1048,6 +1048,15 @@ impl Sub> for DateTime { } } +impl Sub<&DateTime> for DateTime { + type Output = OldDuration; + + #[inline] + fn sub(self, rhs: &DateTime) -> OldDuration { + self.signed_duration_since(rhs) + } +} + impl Add for DateTime { type Output = DateTime; diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index e86ac4f04e..7b3c164406 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -320,6 +320,10 @@ fn signed_duration_since_autoref() { 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] From 1187e3ee043d579dbfc2f8ae61383e6dd17f8c78 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Jun 2023 10:23:35 +0200 Subject: [PATCH 312/999] Stop vendoring `saturating_abs` --- src/offset/local/tz_info/timezone.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 33c89060ce..d2de060237 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] = From c1c272a575061990014b53abad4ff363040b585f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Jun 2023 08:44:14 +0200 Subject: [PATCH 313/999] Ensure `timestamp_nanos` panics on overflow in release builds --- src/naive/datetime/mod.rs | 6 ++++-- src/naive/datetime/tests.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 0a3f70380e..d32a388e5d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -480,8 +480,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. diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 07309a48ac..53422c8c18 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -329,6 +329,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 + Duration::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 + Duration::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(); From 5c3f40cc670887398ecf84dfe8b5004b0ec01f13 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 5 Jun 2023 06:45:32 +0200 Subject: [PATCH 314/999] Fix dead code error when running dateutils test on Windows --- tests/dateutils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index d671ecca87..f49659d2e6 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -64,6 +64,7 @@ const DATE_PATH: &'static str = "/usr/bin/date"; const DATE_PATH: &'static 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() { From 3760f2a23df43baa9e21573e1f16ee9ff3911dc7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 5 Jun 2023 06:46:37 +0200 Subject: [PATCH 315/999] [CI] Run all test on Windows and MacOS --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee616c7731..c97c0b1608 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 From dfd7a807177f87ef6dbe90a271e89b11fff061e2 Mon Sep 17 00:00:00 2001 From: Eugene Lomov Date: Mon, 5 Jun 2023 15:54:47 +0300 Subject: [PATCH 316/999] Added Ord and Eq traits for types, which already derives PartialOrd and PartialEq (#1128) --- src/month.rs | 4 ++-- src/naive/date.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/month.rs b/src/month.rs index 11df7613dd..4c6321e152 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 = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -218,7 +218,7 @@ impl num_traits::FromPrimitive 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); diff --git a/src/naive/date.rs b/src/naive/date.rs index b9404a15f5..6ad560b9e0 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -137,7 +137,7 @@ impl NaiveWeek { /// that adding `Duration::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 `Duration::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 { From 70a25b1f9f799152db1fc83430a04baf519afcde Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 09:45:43 +0200 Subject: [PATCH 317/999] Document panicking of `NaiveWeek` --- src/naive/date.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 6ad560b9e0..7e6d66d646 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -66,6 +66,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 /// /// ``` @@ -89,6 +94,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 /// /// ``` @@ -114,6 +124,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 /// /// ``` From cafd845f065c2509cc42918597e2614f6f4c7063 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 09:46:10 +0200 Subject: [PATCH 318/999] Clearly document errors and panics for `NaiveDate` --- src/naive/date.rs | 162 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 30 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 7e6d66d646..9b43ba8b13 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -300,7 +300,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 { @@ -310,7 +313,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 /// @@ -335,7 +343,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 { @@ -345,7 +356,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 /// @@ -372,7 +388,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 { @@ -383,7 +402,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 /// @@ -459,6 +483,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] @@ -470,6 +496,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 @@ -497,15 +525,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)`. /// - /// # Panics + /// `n` is 1-indexed. /// - /// 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. + /// # Panics /// - /// `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 { @@ -513,17 +541,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, @@ -612,10 +648,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!( @@ -641,10 +681,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!( @@ -715,8 +759,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!( @@ -743,8 +791,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!( @@ -798,6 +850,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] @@ -811,6 +865,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 @@ -835,6 +891,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] @@ -848,6 +906,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 @@ -880,6 +940,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 @@ -907,6 +969,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 @@ -939,6 +1003,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] @@ -952,6 +1018,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 @@ -1010,6 +1078,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] @@ -1020,6 +1090,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the next calendar date. /// + /// # Errors + /// /// Returns `None` when `self` is the last representable date. /// /// # Example @@ -1042,6 +1114,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] @@ -1052,6 +1126,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the previous calendar date. /// + /// # Errors + /// /// Returns `None` when `self` is the first representable date. /// /// # Example @@ -1072,9 +1148,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. + /// + /// # Errors /// - /// Returns `None` when it will result in overflow. + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -1104,9 +1182,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 `Duration` from the current date. + /// Subtracts the number of whole days in the given `Duration` 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 /// @@ -1172,6 +1252,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(); @@ -1555,9 +1639,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 /// @@ -1591,7 +1678,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 /// @@ -1610,7 +1699,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 /// @@ -1630,7 +1722,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 /// @@ -1649,7 +1743,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 /// @@ -1669,7 +1765,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 /// @@ -1693,7 +1792,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 /// From cbfd21b3cf88958015cd20c3e843edb2325c55ef Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 12:04:58 +0200 Subject: [PATCH 319/999] Clearly document errors and panics for `NaiveTime` --- src/naive/time/mod.rs | 46 +++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 3700c5cba2..328f2cbf76 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -212,6 +212,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] @@ -225,6 +227,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 @@ -251,6 +255,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] @@ -264,6 +270,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 @@ -294,6 +302,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] @@ -307,6 +317,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 @@ -335,6 +347,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] @@ -348,6 +362,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 @@ -380,6 +396,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] @@ -393,6 +411,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 @@ -506,10 +526,8 @@ impl NaiveTime { parsed.to_naive_time().map(|t| (t, remainder)) } - /// Adds given `Duration` to the current time, - /// and also returns the number of *seconds* + /// Adds given `Duration` to the current time, and also returns the number of *seconds* /// in the integral number of days ignored from the addition. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) /// /// # Example /// @@ -589,10 +607,8 @@ impl NaiveTime { (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) } - /// Subtracts given `Duration` from the current time, - /// and also returns the number of *seconds* + /// Subtracts given `Duration` from the current time, and also returns the number of *seconds* /// in the integral number of days ignored from the subtraction. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) /// /// # Example /// @@ -886,7 +902,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 /// @@ -908,7 +926,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 /// @@ -930,10 +950,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 /// /// ``` @@ -954,10 +977,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 /// /// ``` From c0b634cac05c0e82893a4e2186520510130904b9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 09:20:30 +0200 Subject: [PATCH 320/999] Clearly document errors and panics for `NaiveDateTime` --- src/naive/datetime/mod.rs | 142 ++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 51 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index d32a388e5d..1bd17d57be 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -113,7 +113,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] @@ -126,7 +130,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 /// @@ -155,7 +162,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 /// @@ -189,8 +199,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 /// @@ -390,11 +403,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 /// /// ``` @@ -421,11 +429,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 /// /// ``` @@ -451,13 +454,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 /// @@ -559,7 +560,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 /// @@ -632,9 +635,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 /// @@ -665,7 +670,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 /// @@ -734,9 +741,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 /// @@ -1097,12 +1106,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 /// /// ``` @@ -1119,10 +1132,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 /// /// ``` @@ -1140,10 +1155,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 /// /// ``` @@ -1161,10 +1179,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 /// /// ``` @@ -1181,10 +1201,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 /// /// ``` @@ -1201,10 +1223,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 /// /// ``` @@ -1228,10 +1253,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 /// /// ``` @@ -1327,10 +1355,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 /// /// ``` @@ -1348,10 +1378,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 /// @@ -1370,12 +1401,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 /// /// ``` @@ -1399,6 +1433,10 @@ impl Timelike for NaiveDateTime { /// /// See also the [`NaiveTime::with_nanosecond`] method. /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// /// # Example /// /// ``` @@ -1423,7 +1461,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 From 401c298b8f0e92232aa65f395f06c1ce7a699264 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Jun 2023 12:56:45 +0200 Subject: [PATCH 321/999] Clearly document errors and panics for `DateTime` --- src/datetime/mod.rs | 266 ++++++++++++++++++++++++++++++++++++-------- src/traits.rs | 2 +- 2 files changed, 218 insertions(+), 50 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 14ce537ab4..23a5cde8c7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -120,6 +120,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 /// /// ``` @@ -149,11 +156,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)] @@ -162,10 +177,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::*; @@ -181,8 +201,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 { @@ -197,12 +216,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 /// @@ -221,12 +235,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 /// @@ -245,12 +254,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. + /// + /// # Panics /// - /// 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.) + /// 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 /// @@ -269,22 +281,18 @@ impl DateTime { self.datetime.timestamp_nanos() } - /// Returns the number of milliseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999 + /// Returns the number of milliseconds since the last second boundary. /// - /// 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 { @@ -293,9 +301,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 { @@ -317,7 +323,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 { @@ -334,7 +341,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: OldDuration) -> Option> { @@ -345,10 +354,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() @@ -359,7 +374,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: OldDuration) -> Option> { @@ -370,10 +387,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() @@ -382,9 +405,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() @@ -393,9 +421,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() @@ -423,6 +456,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 { @@ -430,6 +469,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(); @@ -661,6 +704,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] @@ -866,35 +914,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)) @@ -919,21 +1044,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)) diff --git a/src/traits.rs b/src/traits.rs index a6199dc6dc..22174da209 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; From 7ebaed563db016484160dc33e4a95a72061678c6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 07:25:35 +0200 Subject: [PATCH 322/999] Remove Makefile --- Makefile | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 63aef15ace..0000000000 --- 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 rustc-serialize bincode' From 751e442e599cfdee5bb3529efadcd897d0ea2f37 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 18 May 2023 19:11:41 +0200 Subject: [PATCH 323/999] Make `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` public --- src/naive/date.rs | 1 + src/naive/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9b43ba8b13..7e0185383f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2045,6 +2045,7 @@ impl DoubleEndedIterator 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, diff --git a/src/naive/mod.rs b/src/naive/mod.rs index c41acba8d3..d3d69196f4 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}; #[cfg(feature = "rustc-serialize")] #[allow(deprecated)] pub use self::datetime::rustc_serialize::TsSeconds; From 249e649a1a868434b49bb89f8d93471b98ddb2ba Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:11:24 +0200 Subject: [PATCH 324/999] Implement `FusedIterator` --- src/naive/date.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 7e0185383f..1cc3515bef 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}; @@ -2045,6 +2046,8 @@ impl DoubleEndedIterator for NaiveDateDaysIterator { } } +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 { @@ -2082,6 +2085,8 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { } } +impl FusedIterator for NaiveDateWeeksIterator {} + // TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator, // TrustedLen, and Step once they becomes stable. // See: https://github.com/chronotope/chrono/issues/208 From ece804bc06596d68022b56fb4f27a2000835b98a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:12:17 +0200 Subject: [PATCH 325/999] Simplify iterators implementation --- src/naive/date.rs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 1cc3515bef..1162684911 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2017,13 +2017,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) } @@ -2037,11 +2034,9 @@ 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) } } @@ -2058,11 +2053,8 @@ impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if NaiveDate::MAX - self.value < OldDuration::weeks(1) { - return None; - } let current = self.value; - self.value = current + OldDuration::weeks(1); + self.value = current.checked_add_signed(OldDuration::weeks(1))?; Some(current) } @@ -2076,11 +2068,8 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - NaiveDate::MIN < OldDuration::weeks(1) { - return None; - } let current = self.value; - self.value = current - OldDuration::weeks(1); + self.value = current.checked_sub_signed(OldDuration::weeks(1))?; Some(current) } } From 0d2c9338f98b06418c843826e6f3f9c8dd4dba1f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:20:33 +0200 Subject: [PATCH 326/999] Remove TODO --- src/naive/date.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 1162684911..9745e08093 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2076,10 +2076,6 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { impl FusedIterator for NaiveDateWeeksIterator {} -// TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator, -// TrustedLen, and Step once they becomes stable. -// See: https://github.com/chronotope/chrono/issues/208 - /// The `Debug` output of the naive date `d` is the same as /// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). /// From 4ca58b74f6affc75757c277735bc710954051345 Mon Sep 17 00:00:00 2001 From: LingMan Date: Thu, 15 Dec 2022 19:05:46 +0100 Subject: [PATCH 327/999] Specify licenses in SPDX format The use of `/` as a separator is deprecated. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2745c67ebc..c5ba2eaabf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/chronotope/chrono" keywords = ["date", "time", "calendar"] categories = ["date-and-time"] readme = "README.md" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2021" rust-version = "1.56.0" From afb1f1f2148a4034e4b0fa40ea7f6576d4551bcc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Jun 2023 14:02:25 +0200 Subject: [PATCH 328/999] Remove unnecessary wasm-specific code --- src/offset/local/mod.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 372fd49762..04f9221424 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -95,33 +95,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 { From 4dceb645e9275f7e1d8e55ab5f5922bd509249e2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Jun 2023 14:17:19 +0200 Subject: [PATCH 329/999] Fixes to wasm test --- tests/wasm.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/wasm.rs b/tests/wasm.rs index f003d4db9d..3cc3dc0390 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() { @@ -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); From 582d2aa554cdb35d0570fbce82bcfff3d13013b3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Jun 2023 14:35:53 +0200 Subject: [PATCH 330/999] [CI] Test wamsbind --- .github/workflows/test.yml | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c97c0b1608..a485fbe99c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -117,7 +117,6 @@ jobs: os: [ubuntu-latest] target: [ - wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, @@ -130,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] @@ -154,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: From 15f81167bceaf86dfb8c59697612f08f66bd52e2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Jun 2023 16:28:10 +0200 Subject: [PATCH 331/999] Correct offset calculation on wasm_bindgen --- src/offset/local/mod.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 04f9221424..9e7d30335a 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()) } } From e1bbab007a3328181ebf0a00b7da57511327488b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 17:05:11 +0200 Subject: [PATCH 332/999] Remove old comment from `DateTime::from_utc` and `Date::from_utc` --- src/date.rs | 2 -- src/datetime/mod.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/date.rs b/src/date.rs index 9625556519..aba0cbee46 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 { diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 23a5cde8c7..44a89da6bc 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -109,8 +109,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 { From cbdc9f786a180495f5a8f24eb08ac73fd12f8edf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 17:29:31 +0200 Subject: [PATCH 333/999] Correct documentation of `Date::and_time` --- src/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/date.rs b/src/date.rs index aba0cbee46..d831745a43 100644 --- a/src/date.rs +++ b/src/date.rs @@ -83,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> { From 90a9af029c46fcfb87012e3a2b0660ab9be22007 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 18:51:38 +0200 Subject: [PATCH 334/999] Consider `%s` to have be a timestamp in UTC --- src/datetime/tests.rs | 4 ++++ src/format/parsed.rs | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 7b3c164406..a8bc90c8eb 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -603,6 +603,10 @@ fn test_datetime_parse_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() + ); } #[test] diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 9526785ee2..809a5ae782 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -627,7 +627,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)?; From 2c7a24067cc7f13d6d66e10f23bb708dc11b6f40 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 12:50:28 +0200 Subject: [PATCH 335/999] Fix clippy warnings in tests --- src/datetime/tests.rs | 1 + tests/dateutils.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index a8bc90c8eb..42d1b4703b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -314,6 +314,7 @@ fn test_datetime_offset() { } #[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(); diff --git a/tests/dateutils.rs b/tests/dateutils.rs index f49659d2e6..36784ac1c6 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -58,10 +58,10 @@ 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)] From 6a7ef9fb823ca82c49a8b8d76c2c95ef76c2c308 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:20:59 +0200 Subject: [PATCH 336/999] Elide lifetime in format_item_localized --- src/format/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 93c9f86d09..c8bb1e3a33 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -994,12 +994,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(); From 52a46c728a097c75cf76cea2261b8472c9bc022d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:27:23 +0200 Subject: [PATCH 337/999] Don't use deprecated methods in `test_strftime_docs_localized` --- src/format/strftime.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 24bae20c0a..7c1174e980 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -671,14 +671,14 @@ fn test_strftime_docs() { #[cfg(feature = "unstable-locales")] #[test] fn test_strftime_docs_localized() { - use crate::{FixedOffset, NaiveDate, TimeZone}; + use crate::{FixedOffset, NaiveDate, TimeZone, Timelike}; - let dt = FixedOffset::east_opt(34200).unwrap().ymd_opt(2001, 7, 8).unwrap().and_hms_nano( - 0, - 34, - 59, - 1_026_490_708, - ); + 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(); // date specifiers assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); From 0c3ffe4320e0d8792a822cbf872942c91ff961aa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:19:45 +0200 Subject: [PATCH 338/999] Remove `#[must_use]` from serialize methods --- src/datetime/serde.rs | 16 ---------------- src/naive/datetime/serde.rs | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 72ada0ccaa..cead01fd66 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -174,7 +174,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, @@ -202,7 +201,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>, @@ -299,7 +297,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, @@ -330,7 +327,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>, @@ -431,7 +427,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, @@ -459,7 +454,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>, @@ -555,7 +549,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, @@ -586,7 +579,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>, @@ -687,7 +679,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, @@ -715,7 +706,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>, @@ -808,7 +798,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, @@ -850,7 +839,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>, @@ -952,7 +940,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, @@ -980,7 +967,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>, @@ -1070,7 +1056,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, @@ -1101,7 +1086,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>, diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 8107f384d9..6d75c9df28 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>, From 2a22e1edf6657f38d9f2c9828de24a5fef8a074b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 12:55:39 +0200 Subject: [PATCH 339/999] Move tests in `datetime` to `test` module --- src/datetime/mod.rs | 28 ---------------------------- src/datetime/tests.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 44a89da6bc..bf9c16ad5d 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1423,34 +1423,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, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 42d1b4703b..3aeed665c0 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1027,3 +1027,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); +} From 5759abf959a5c40402cd7e9bf6e97a5d9acb691b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:16:42 +0200 Subject: [PATCH 340/999] Move tests in `datetime::serde` to `test` module --- src/datetime/serde.rs | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index cead01fd66..002ded0cca 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1128,30 +1128,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}; + + #[test] + fn test_serde_serialize() { + test_encodable_json(serde_json::to_string, serde_json::to_string); + } -#[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), - ); -} + #[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), + ); + } -#[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()); + #[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()); + } } From 98a54574b0007c75805f90180776830376edaf4a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 12:56:15 +0200 Subject: [PATCH 341/999] Move test and related constants in `naive::date` to `test` module --- src/naive/date.rs | 104 ++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9745e08093..208ea2cb8a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -33,29 +33,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)] @@ -235,34 +212,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 `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 - ); -} - 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 @@ -2389,13 +2338,39 @@ mod serde { #[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::oldtime::Duration; 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 @@ -3219,4 +3194,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; } From ed21cadd36cdeaadd6bdffd21ef1bbd7008cdaac Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:33:59 +0200 Subject: [PATCH 342/999] Move tests in `format::strftime` to `test` module --- src/format/strftime.rs | 411 +++++++++++++++++++++-------------------- 1 file changed, 206 insertions(+), 205 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 7c1174e980..b4275573b6 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -509,212 +509,213 @@ 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. - 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. + 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(" \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("[%F]"), parse_and_collect("[%Y-%m-%d]")); - 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("%%%%"), [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"), [internal_fix!(TimezoneOffsetPermissive)]); - assert_eq!(parse_and_collect("%#m"), [Item::Error]); -} + assert_eq!(parse_and_collect(""), []); + 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("[%F]"), parse_and_collect("[%Y-%m-%d]")); + 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("%%%%"), [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"), [internal_fix!(TimezoneOffsetPermissive)]); + assert_eq!(parse_and_collect("%#m"), [Item::Error]); + } -#[cfg(test)] -#[test] -fn test_strftime_docs() { - use crate::NaiveDate; - use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - - 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(); - - // 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"); - - // 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() - ); - 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() - ); - - 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"); - - // special specifiers - assert_eq!(dt.format("%t").to_string(), "\t"); - assert_eq!(dt.format("%n").to_string(), "\n"); - assert_eq!(dt.format("%%").to_string(), "%"); -} + #[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(); + + // 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"); + + // 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() + ); + 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() + ); + + 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"); + + // special specifiers + assert_eq!(dt.format("%t").to_string(), "\t"); + assert_eq!(dt.format("%n").to_string(), "\n"); + assert_eq!(dt.format("%%").to_string(), "%"); + } -#[cfg(feature = "unstable-locales")] -#[test] -fn test_strftime_docs_localized() { - use crate::{FixedOffset, NaiveDate, TimeZone, Timelike}; - - 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(); - - // 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"); + #[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(); + + // 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"); + } } From 9e735110b253bbbc152dc8287ea944544c55d6b3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:40:47 +0200 Subject: [PATCH 343/999] Move tests in `naive::datetime::serde` to `test` module --- src/naive/datetime/serde.rs | 93 +++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 6d75c9df28..1a97fb23d4 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1047,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 @@ -1139,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); + } +} From d1c5266126aef230fc17717cd07af3905f848c37 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:43:37 +0200 Subject: [PATCH 344/999] Move tests in `month::serde` to `test` module --- src/month.rs | 120 ++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/src/month.rs b/src/month.rs index 4c6321e152..161c43c85b 100644 --- a/src/month.rs +++ b/src/month.rs @@ -283,8 +283,68 @@ 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_primitive_parse() { + use num_traits::FromPrimitive; + + let jan_opt = Month::from_u32(1); + let feb_opt = Month::from_u64(2); + let dec_opt = Month::from_i64(12); + let no_month = Month::from_u32(13); + assert_eq!(jan_opt, Some(Month::January)); + assert_eq!(feb_opt, Some(Month::February)); + assert_eq!(dec_opt, Some(Month::December)); + assert_eq!(no_month, None); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::from_u32(date.month()), 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); + } #[test] + #[cfg(feature = "serde")] fn test_serde_serialize() { use serde_json::to_string; use Month::*; @@ -311,6 +371,7 @@ mod month_serde { } #[test] + #[cfg(feature = "serde")] fn test_serde_deserialize() { use serde_json::from_str; use Month::*; @@ -345,62 +406,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(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_primitive_parse() { - use num_traits::FromPrimitive; - - let jan_opt = Month::from_u32(1); - let feb_opt = Month::from_u64(2); - let dec_opt = Month::from_i64(12); - let no_month = Month::from_u32(13); - assert_eq!(jan_opt, Some(Month::January)); - assert_eq!(feb_opt, Some(Month::February)); - assert_eq!(dec_opt, Some(Month::December)); - assert_eq!(no_month, None); - - let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); - assert_eq!(Month::from_u32(date.month()), 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); - } -} From be93cf2d6fdc9c75fc7ce132c2f63dbb048ecd4f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:44:36 +0200 Subject: [PATCH 345/999] Move test module to end of file --- src/weekday.rs | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/weekday.rs b/src/weekday.rs index e2973cf798..890273b517 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -229,43 +229,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")] @@ -368,3 +331,40 @@ mod weekday_serde { } } } + +#[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 + ); + } + } +} From 76fc03737a693d4126ba02ec49f34debba05aae9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:46:00 +0200 Subject: [PATCH 346/999] Move tests in `weekday::serde` to `test` module --- src/weekday.rs | 76 ++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/weekday.rs b/src/weekday.rs index 890273b517..f51e2178a0 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -272,8 +272,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::*; @@ -295,6 +333,7 @@ mod weekday_serde { } #[test] + #[cfg(feature = "serde")] fn test_serde_deserialize() { use serde_json::from_str; use Weekday::*; @@ -331,40 +370,3 @@ mod weekday_serde { } } } - -#[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 - ); - } - } -} From 70df5134418f26883c0944518e9c0c9a94bce440 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:56:17 +0200 Subject: [PATCH 347/999] Move tests in `naive::time::serde` to `test` module --- src/naive/time/serde.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index c7394fb575..cf3c4e3d5e 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)); + } + + #[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); + 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); + } } From 33bf2fce57e68b8c62e2313cbdd59a1855ae1f38 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:59:29 +0200 Subject: [PATCH 348/999] Move tests in `naive::date::serde` to `test` module --- src/naive/date.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 208ea2cb8a..43cb4363c5 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2313,26 +2313,32 @@ 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}; - - 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_deserialize() { + test_decodable_json(|input| serde_json::from_str(input)); + } + + #[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); + } } } From 06440840c17a4888ad20dbab3d90f3069b447fb4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 15:02:41 +0200 Subject: [PATCH 349/999] Move tests in `naive::date::rustc_serialize` to `test` module --- src/naive/date.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 43cb4363c5..f34d855a20 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2237,16 +2237,19 @@ mod rustc_serialize { } #[cfg(test)] - use rustc_serialize::json; + mod tests { + use crate::naive::date::{test_decodable_json, test_encodable_json}; + use rustc_serialize::json; - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } + #[test] + fn test_encodable() { + test_encodable_json(json::encode); + } - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); + #[test] + fn test_decodable() { + test_decodable_json(json::decode); + } } } From 4aa6c9a99530e0541dc589a51ba367651453530f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 14:07:37 +0200 Subject: [PATCH 350/999] Move tests in `datetime::rustc_serialize` to `test` module --- src/datetime/rustc_serialize.rs | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 18a67d6222..8e75350d92 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -103,21 +103,25 @@ impl Decodable for TsSeconds { } #[cfg(test)] -use rustc_serialize::json; - -#[test] -fn test_encodable() { - super::test_encodable_json(json::encode, json::encode); -} +mod tests { + use crate::datetime::test_encodable_json; + use crate::datetime::{test_decodable_json, test_decodable_json_timestamps}; + use rustc_serialize::json; + + #[test] + fn test_encodable() { + test_encodable_json(json::encode, json::encode); + } -#[cfg(feature = "clock")] -#[test] -fn test_decodable() { - super::test_decodable_json(json::decode, json::decode, json::decode); -} + #[cfg(feature = "clock")] + #[test] + fn test_decodable() { + test_decodable_json(json::decode, json::decode, json::decode); + } -#[cfg(feature = "clock")] -#[test] -fn test_decodable_timestamps() { - super::test_decodable_json_timestamps(json::decode, json::decode, json::decode); + #[cfg(feature = "clock")] + #[test] + fn test_decodable_timestamps() { + test_decodable_json_timestamps(json::decode, json::decode, json::decode); + } } From e0bc28c7992d5a459cde8b581a6821467de3955a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 14:09:26 +0200 Subject: [PATCH 351/999] Move tests in `naive::datetime::rustc_serialize` to `test` module --- src/naive/datetime/rustc_serialize.rs | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs index 6e33829e0a..201fe7aa26 100644 --- a/src/naive/datetime/rustc_serialize.rs +++ b/src/naive/datetime/rustc_serialize.rs @@ -55,19 +55,23 @@ impl Decodable for TsSeconds { } #[cfg(test)] -use rustc_serialize::json; +mod tests { + use crate::naive::datetime::test_encodable_json; + use crate::naive::datetime::{test_decodable_json, test_decodable_json_timestamp}; + use rustc_serialize::json; -#[test] -fn test_encodable() { - super::test_encodable_json(json::encode); -} + #[test] + fn test_encodable() { + test_encodable_json(json::encode); + } -#[test] -fn test_decodable() { - super::test_decodable_json(json::decode); -} + #[test] + fn test_decodable() { + test_decodable_json(json::decode); + } -#[test] -fn test_decodable_timestamps() { - super::test_decodable_json_timestamp(json::decode); + #[test] + fn test_decodable_timestamps() { + test_decodable_json_timestamp(json::decode); + } } From 4b4e694f2442e1ad799a7211c2b9404df8fa7da0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 14:14:31 +0200 Subject: [PATCH 352/999] Move tests in `naive::time::rustc_serialize` to `test` module --- src/naive/time/rustc_serialize.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs index 9eaf68219e..128fcec8ab 100644 --- a/src/naive/time/rustc_serialize.rs +++ b/src/naive/time/rustc_serialize.rs @@ -16,14 +16,17 @@ impl Decodable for NaiveTime { } #[cfg(test)] -use rustc_serialize::json; +mod tests { + use crate::naive::time::{test_decodable_json, test_encodable_json}; + use rustc_serialize::json; -#[test] -fn test_encodable() { - super::test_encodable_json(json::encode); -} + #[test] + fn test_encodable() { + test_encodable_json(json::encode); + } -#[test] -fn test_decodable() { - super::test_decodable_json(json::decode); + #[test] + fn test_decodable() { + test_decodable_json(json::decode); + } } From 03dd894a7ffd9a7cb2e805c3476067041fb88f1f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 11:51:28 +0200 Subject: [PATCH 353/999] Deny tests outside `test` module --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 3737d1a9b2..2eb1a010dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -388,6 +388,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)] // can remove this if/when rustc-serialize support is removed // keeps clippy happy in the meantime From 2665e3091dd3eee2427e74d83ca85e70d839c13a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 13:14:45 +0200 Subject: [PATCH 354/999] [CI] Run clippy on all features and targets --- .github/workflows/lint.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a0cf75d9de..6209a0ea65 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 \ From aa8a23785e2cc71352ab3e123bf043cb63c55c0d Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Thu, 8 Jun 2023 15:27:23 +0200 Subject: [PATCH 355/999] fix: no panic when formatting with %#z This changes the "don't use me" panic into an error when using the parsing-only "%#z" formatter, consistent with other invalid formatters. --- src/format/mod.rs | 2 +- src/format/strftime.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index c8bb1e3a33..7c9c3b7dd0 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -675,7 +675,7 @@ fn format_inner( off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) } Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - panic!("Do not try to write %#z it is undefined") + return Err(fmt::Error); } RFC2822 => // same as `%a, %d %b %Y %H:%M:%S %z` diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b4275573b6..9dae6e7e09 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -718,4 +718,28 @@ mod tests { 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"); } + + /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does + /// not cause a panic. + /// + /// See . + #[test] + fn test_parse_only_timezone_offset_permissive_no_panic() { + use crate::NaiveDate; + use crate::{FixedOffset, TimeZone}; + use std::fmt::Write; + + 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(); + + let mut buf = String::new(); + let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); + } } From 29e728ae9fea7da5db62b20c64f90ca44a670e52 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:16:09 -0700 Subject: [PATCH 356/999] Add parse tests focused on timezone signage Add parse tests focused on timezone siganges "+", "-", and bad offsets --- src/format/parse.rs | 481 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 434 insertions(+), 47 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index ed3f91f6df..d34195f9d4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -592,10 +592,31 @@ fn test_parse() { check!("", [lit!("a")]; TOO_SHORT); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); + check!("+", [lit!("+")]; ); + check!("-", [lit!("-")]; ); + check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); check!("A", [lit!("a")]; INVALID); check!("xy", [lit!("xy")]; ); check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("-1234", [lit!("-1234")]; ); + check!("PST", [lit!("PST")]; ); + check!("🤠", [lit!("🤠")]; ); + check!("🤠a", [lit!("🤠"), lit!("a")]; ); + check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); + check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); + // literals can be together + check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart + check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); @@ -771,48 +792,371 @@ fn test_parse() { check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); + check!("+1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); + check!("-1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); + check!("-12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); + check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -878,7 +1222,9 @@ fn test_rfc2822() { ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second + ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), + ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name @@ -911,6 +1257,14 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -925,6 +1279,8 @@ fn test_rfc2822() { // Test against test data above for &(date, checkdate) in testdates.iter() { + eprintln!("Test input: {:?}", date); + eprintln!(" Expect: {:?}", checkdate); let d = rfc2822_to_datetime(date); // parse a date let dt = match d { // did we get a value? @@ -998,11 +1354,42 @@ fn test_rfc3339() { ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { From b9a0a251c5e19a17c2e5382aaa2b9c1613767d92 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:20:17 -0700 Subject: [PATCH 357/999] Add tests for RFC 850 failures --- src/format/parse.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/format/parse.rs b/src/format/parse.rs index d34195f9d4..6cd23a2758 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1336,6 +1336,25 @@ fn parse_rfc850() { for val in &testdates { assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); } + + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; + + for val in &test_dates_fail { + assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); + } } #[cfg(test)] From cf177ee8cae87f7024a81f707e73f4187a643333 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:21:14 -0700 Subject: [PATCH 358/999] Replace test_tz_ascii_str with test_timezonename_new Replace test `test_tz_ascii_str` with more systemtatic test `test_timezonename_new`. --- src/offset/local/tz_info/timezone.rs | 66 ++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index d2de060237..550910d044 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -741,20 +741,58 @@ mod tests { } #[test] - fn test_tz_ascii_str() -> Result<(), Error> { - assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); - assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); - assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); - assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); - assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); - assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); - assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); - - assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); + fn test_timezonename_new() -> Result<(), Error> { + // expect Error::LocalTimeType() + const INPUT_ERR: &[&str] = &[ + "", + "1", + "+", + "-", + "12", + "--", + "AB", + "ab", + "12345678", + "ABCDEFGH", + "123456789", + "1234567890", + "--------", + "123\0\0\0", + "\0\0\0", + "\x00123", + "123\0", + ]; + for input_ in INPUT_ERR.iter() { + eprintln!("TimeZoneName::new({:?}) (expect Error::LocalTimeType)", input_); + let input_ = input_.as_bytes(); + let err = TimeZoneName::new(input_); + eprintln!("err = {:?}", err); + assert!(matches!(err, Err(Error::LocalTimeType(_)))); + } + // expect Ok + const INPUT_OK_EXPECT: &[(&str, &str)] = &[ + ("123", "123"), + ("abc", "abc"), + ("ABC", "ABC"), + ("1234", "1234"), + ("12345", "12345"), + ("123456", "123456"), + ("1234567", "1234567"), + ("+1234", "+1234"), + ("+1234", "+1234"), + ("-1234", "-1234"), + // Ok nonsense + ("+++", "+++"), + ("-----", "-----"), + ]; + for (input_, expect) in INPUT_OK_EXPECT.iter() { + eprintln!("TimeZoneName::new({:?})", input_); + let output = TimeZoneName::new(input_.as_bytes()); + match output { + Ok(output) => assert_eq!(output.as_bytes(), expect.as_bytes()), + Err(error) => panic!("Failed: input {:?}, error {}", input_, error), + } + } Ok(()) } From ed24478e4eb73ea4b547fa8ebcc5da6cb14264fe Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:24:25 -0700 Subject: [PATCH 359/999] timezone_offset_internal and TimeZoneName::new parse by chars Function `timezone_offset_internal` and `TimeZoneName::new` iterate over the input data by `chars()` instead of iterating `as_bytes()`. --- src/format/scan.rs | 15 +++++++++---- src/offset/local/tz_info/timezone.rs | 32 +++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 2962ef162b..f2b807fb52 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -230,13 +230,20 @@ where Ok((b[0], b[1])) } } - let negative = match s.as_bytes().first() { - Some(&b'+') => false, - Some(&b'-') => true, + let negative = match s.chars().next() { + Some('+') => { + s = &s['+'.len_utf8()..]; + + false + } + Some('-') => { + s = &s['-'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; - s = &s[1..]; // hours (00--99) let hours = match digits(s)? { diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 550910d044..81bcee73be 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -487,28 +487,36 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name fn new(input: &[u8]) -> Result { - let len = input.len(); + let s = match str::from_utf8(input) { + Ok(s) => s, + Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), + }; + let schars = s.chars().count(); - if !(3..=7).contains(&len) { + if !(3..=7).contains(&schars) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); } let mut bytes = [0; 8]; - bytes[0] = input.len() as u8; - - let mut i = 0; - while i < len { - let b = input[i]; - match b { - b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} + let mut copied = 0; + for (i, c) in s.chars().enumerate() { + match c { + '0'..='9' | 'A'..='Z' | 'a'..='z' + // ISO 8601 / RFC 3339 proscribes use of `+` (U+2B) PLUS SIGN + // in timezone + | '+' + // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) + // in timezone + | '-' => { + bytes[i + 1] = c as u8; + } _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } - - bytes[i + 1] = b; - i += 1; + copied += 1; } + bytes[0] = copied as u8; Ok(Self { bytes }) } From 7a4da413cfaaf1060e74141f5f426baf93be5441 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:32:16 -0700 Subject: [PATCH 360/999] timezone allows leading MINUS SIGN (U+2212) Timezone signage also allows MINUS SIGN (U+2212) as specified by ISO 8601 and RFC 3339. Not for RFC 2822 format or RFC 8536 transition string. Issue #835 --- src/format/parse.rs | 62 +++++++++++++++++++++++++--- src/format/scan.rs | 32 ++++++++++++-- src/offset/local/tz_info/timezone.rs | 23 +++++++++-- 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 6cd23a2758..c542bcb3b0 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -594,6 +594,8 @@ fn test_parse() { check!("a", [lit!("a")]; ); check!("+", [lit!("+")]; ); check!("-", [lit!("-")]; ); + check!("−", [lit!("−")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); check!("A", [lit!("a")]; INVALID); @@ -603,6 +605,7 @@ fn test_parse() { check!("1234", [lit!("1234")]; ); check!("+1234", [lit!("+1234")]; ); check!("-1234", [lit!("-1234")]; ); + check!("−1234", [lit!("−1234")]; ); // MINUS SIGN (U+2212) check!("PST", [lit!("PST")]; ); check!("🤠", [lit!("🤠")]; ); check!("🤠a", [lit!("🤠"), lit!("a")]; ); @@ -651,11 +654,17 @@ fn test_parse() { check!("-0042", [num!(Year)]; year: -42); check!("+0042", [num!(Year)]; year: 42); check!("-42195", [num!(Year)]; year: -42195); + check!("−42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) check!("+42195", [num!(Year)]; year: 42195); check!(" -42195", [num!(Year)]; year: -42195); check!(" +42195", [num!(Year)]; year: 42195); check!(" - 42", [num!(Year)]; INVALID); check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" −42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); check!("-", [num!(Year)]; TOO_SHORT); check!("+", [num!(Year)]; TOO_SHORT); @@ -664,6 +673,13 @@ fn test_parse() { check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!("−345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num!(Ordinal)]; TOO_LONG); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); + check!("345🤠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); check!(" +345", [num!(Ordinal)]; INVALID); check!(" -345", [num!(Ordinal)]; INVALID); @@ -813,6 +829,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); @@ -832,12 +849,14 @@ fn test_parse() { check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("−00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("−24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); @@ -847,6 +866,8 @@ fn test_parse() { check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" −12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); @@ -858,7 +879,6 @@ fn test_parse() { check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); check!("", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); @@ -869,13 +889,16 @@ fn test_parse() { check!("X12:34", [fix!(TimezoneOffset)]; INVALID); check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X−12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); check!("+1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); check!("-1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("−1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); check!("-12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("−12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); check!("Z", [fix!(TimezoneOffset)]; INVALID); check!("A", [fix!(TimezoneOffset)]; INVALID); @@ -904,6 +927,7 @@ fn test_parse() { check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("−1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -920,6 +944,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -928,6 +953,8 @@ fn test_parse() { check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("−12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); @@ -988,6 +1015,7 @@ fn test_parse() { check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("−1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1004,6 +1032,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1074,6 +1103,7 @@ fn test_parse() { check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1090,6 +1120,7 @@ fn test_parse() { check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1118,6 +1149,7 @@ fn test_parse() { check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" −12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); @@ -1164,6 +1196,16 @@ fn test_parse() { num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: 32400); + check!("2015-02-04T14:37:05-09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); check!("20150204143705567", [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; @@ -1367,15 +1409,21 @@ fn test_rfc3339() { // Test data - (input, Ok(expected result after parse and format) or Err(error code)) let testdates = [ ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case + ("2015-01-20T17:35:20−08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), + ("2015-01-20T17:35:20.001−08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), + ("2015-01-20T17:35:20.000000004−08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small + ("2015-01-20T17:35:20.000000000452−08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value @@ -1404,11 +1452,13 @@ fn test_rfc3339() { ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T", Err(TOO_SHORT)), // missing HMS - ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S - ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20−08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { diff --git a/src/format/scan.rs b/src/format/scan.rs index f2b807fb52..2f5498cc43 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -211,13 +211,28 @@ pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str where F: FnMut(&str) -> ParseResult<&str>, { - timezone_offset_internal(s, consume_colon, false) + timezone_offset_internal(s, consume_colon, false, true) } +/// Parse a timezone from `s` and return the offset in seconds. +/// +/// The `consume_colon` function is used to parse a mandatory or optional `:` +/// separator between hours offset and minutes offset. +/// +/// The `allow_missing_minutes` flag allows the timezone minutes offset to be +/// missing from `s`. +/// +/// The `allow_tz_minus_sign` flag allows the timezone offset negative character +/// to also be `−` MINUS SIGN (U+2212) in addition to the typical +/// ASCII-compatible `-` HYPHEN-MINUS (U+2D). +/// This is part of [RFC 3339 & ISO 8601]. +/// +/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn timezone_offset_internal( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, + allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, @@ -232,15 +247,26 @@ where } let negative = match s.chars().next() { Some('+') => { + // PLUS SIGN (U+2B) s = &s['+'.len_utf8()..]; false } Some('-') => { + // HYPHEN-MINUS (U+2D) s = &s['-'.len_utf8()..]; true } + Some('−') => { + // MINUS SIGN (U+2212) + if !allow_tz_minus_sign { + return Err(INVALID); + } + s = &s['−'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; @@ -309,7 +335,7 @@ where { match s.as_bytes().first() { Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true), + _ => timezone_offset_internal(s, colon, true, true), } } @@ -347,7 +373,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s))?; + let (s_, offset) = timezone_offset_internal(s, |s| Ok(s), false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 81bcee73be..ac7c3f2616 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,14 +486,19 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name + /// + /// Note: Converts `−` MINUS SIGN (U+2212) to `-` HYPHEN-MINUS (U+2D). + /// Multi-byte MINUS SIGN is allowed in [ISO 8601 / RFC 3339]. But + /// working with single-byte HYPHEN-MINUS is easier and more common. + /// + /// [ISO 8601 / RFC 3339]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn new(input: &[u8]) -> Result { let s = match str::from_utf8(input) { Ok(s) => s, Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), }; - let schars = s.chars().count(); - if !(3..=7).contains(&schars) { + if !(3..=7).contains(&s.chars().count()) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); @@ -504,7 +509,7 @@ impl TimeZoneName { for (i, c) in s.chars().enumerate() { match c { '0'..='9' | 'A'..='Z' | 'a'..='z' - // ISO 8601 / RFC 3339 proscribes use of `+` (U+2B) PLUS SIGN + // ISO 8601 / RFC 3339 proscribes use of `+` PLUS SIGN (U+2B) // in timezone | '+' // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) @@ -512,6 +517,13 @@ impl TimeZoneName { | '-' => { bytes[i + 1] = c as u8; } + // ISO 8601 / RFC 3339 recommends the use of + // `−` MINUS SIGN (U+2212) in timezone. + // But replace with single-byte `-` HYPHEN-MINUS (U+2D) for + // easier byte <-> char conversions later on. + | '−' => { + bytes[i + 1] = b'-'; + } _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } copied += 1; @@ -756,8 +768,10 @@ mod tests { "1", "+", "-", + "−", // MINUS SIGN (U+2212) "12", "--", + "−−", // MINUS SIGN (U+2212) "AB", "ab", "12345678", @@ -789,9 +803,12 @@ mod tests { ("+1234", "+1234"), ("+1234", "+1234"), ("-1234", "-1234"), + ("−1234", "-1234"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) // Ok nonsense ("+++", "+++"), ("-----", "-----"), + ("−−−", "---"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) + ("−−−−−−−", "-------"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) ]; for (input_, expect) in INPUT_OK_EXPECT.iter() { eprintln!("TimeZoneName::new({:?})", input_); From 8f2e94d9304ee9942f1a98850466248011477202 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Jun 2023 13:54:49 +0200 Subject: [PATCH 361/999] Remove `#![allow(deprecated)]` --- src/format/parse.rs | 22 ++++++++++------------ src/format/scan.rs | 10 ++++------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index c542bcb3b0..298622a580 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -4,8 +4,6 @@ //! Date and time parsing routines. -#![allow(deprecated)] - use core::borrow::Borrow; use core::str; use core::usize; @@ -100,7 +98,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // since we do not directly go to a `DateTime` so one can recover // the offset information from `Parsed` anyway. - s = s.trim_left(); + s = s.trim_start(); if let Ok((s_, weekday)) = scan::short_weekday(s) { if !s_.starts_with(',') { @@ -110,7 +108,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_weekday(weekday)?; } - s = s.trim_left(); + s = s.trim_start(); parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; s = scan::space(s)?; // mandatory parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; @@ -136,9 +134,9 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st s = scan::space(s)?; // mandatory parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S + s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - if let Ok(s_) = scan::char(s.trim_left(), b':') { + if let Ok(s_) = scan::char(s.trim_start(), b':') { // [ ":" *S 2DIGIT ] parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; } @@ -323,12 +321,12 @@ where } Item::Space(_) => { - s = s.trim_left(); + s = s.trim_start(); } #[cfg(any(feature = "alloc", feature = "std", test))] Item::OwnedSpace(_) => { - s = s.trim_left(); + s = s.trim_start(); } Item::Numeric(ref spec, ref _pad) => { @@ -361,7 +359,7 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); + s = s.trim_start(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -455,7 +453,7 @@ where | &TimezoneOffsetTripleColon | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; @@ -463,7 +461,7 @@ where &TimezoneOffsetColonZ | &TimezoneOffsetZ => { let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; @@ -472,7 +470,7 @@ where val: InternalInternal::TimezoneOffsetPermissive, }) => { let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; diff --git a/src/format/scan.rs b/src/format/scan.rs index 2f5498cc43..fc67484317 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -5,8 +5,6 @@ * Various scanning routines for the parser. */ -#![allow(deprecated)] - use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; @@ -79,7 +77,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| c.is_ascii_digit()); + let s = s.trim_start_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } @@ -188,7 +186,7 @@ pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { /// Tries to consume one or more whitespace. pub(super) fn space(s: &str) -> ParseResult<&str> { - let s_ = s.trim_left(); + let s_ = s.trim_start(); if s_.len() < s.len() { Ok(s_) } else if s.is_empty() { @@ -200,7 +198,7 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { /// Consumes any number (including zero) of colon or spaces. pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) + Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -381,7 +379,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> /// Tries to consume everything until next whitespace-like symbol. /// Does not provide any offset information from the consumed data. pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) + Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ())) } /// Tries to consume an RFC2822 comment including preceding ` `. From 67c59fd08e8cd5d4f0c8748753840f20483eddac Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Jun 2023 13:55:10 +0200 Subject: [PATCH 362/999] Move test in `format::scan` to `test` module --- src/format/scan.rs | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index fc67484317..2273dec7d6 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -413,36 +413,41 @@ 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("")), - ]; - - 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 - ); +mod tests { + use super::comment_2822; + use crate::format::{INVALID, TOO_SHORT}; + + #[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 + ); + } } } From b2cb0fdf7bf0b145e85e0877de14ff846cafc3f0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Jun 2023 11:39:59 +0200 Subject: [PATCH 363/999] Move test in `format::parse` to `test` module --- src/format/parse.rs | 1849 ++++++++++++++++++++++--------------------- 1 file changed, 925 insertions(+), 924 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 298622a580..f1737d7dd5 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -551,946 +551,947 @@ impl str::FromStr for DateTime { } #[cfg(test)] -#[test] -fn test_parse() { - use super::IMPOSSIBLE; - use super::*; +mod tests { + use crate::format::*; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + + #[test] + fn test_parse() { + // workaround for Rust issue #22255 + fn parse_all(s: &str, items: &[Item]) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, items.iter())?; + Ok(parsed) + } - // workaround for Rust issue #22255 - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } + macro_rules! check { + ($fmt:expr, $items:expr; $err:tt) => ( + assert_eq!(parse_all($fmt, &$items), Err($err)) + ); + ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + let mut expected = Parsed::new(); + $(expected.$k = Some($v);)* + assert_eq!(parse_all($fmt, &$items), Ok(expected)) + }); + } - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { - let mut expected = Parsed::new(); - $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) - }); + // empty string + check!("", []; ); + check!(" ", []; TOO_LONG); + check!("a", []; TOO_LONG); + + // whitespaces + check!("", [sp!("")]; ); + check!(" ", [sp!("")]; ); + check!("\t", [sp!("")]; ); + check!(" \n\r \n", [sp!("")]; ); + check!("a", [sp!("")]; TOO_LONG); + + // literal + check!("", [lit!("a")]; TOO_SHORT); + check!(" ", [lit!("a")]; INVALID); + check!("a", [lit!("a")]; ); + check!("+", [lit!("+")]; ); + check!("-", [lit!("-")]; ); + check!("−", [lit!("−")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [lit!(" ")]; ); + check!("aa", [lit!("a")]; TOO_LONG); + check!("A", [lit!("a")]; INVALID); + check!("xy", [lit!("xy")]; ); + check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("-1234", [lit!("-1234")]; ); + check!("−1234", [lit!("−1234")]; ); // MINUS SIGN (U+2212) + check!("PST", [lit!("PST")]; ); + check!("🤠", [lit!("🤠")]; ); + check!("🤠a", [lit!("🤠"), lit!("a")]; ); + check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); + check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); + // literals can be together + check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart + check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // + check!("x y", [lit!("x"), lit!("y")]; INVALID); + check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + + // numeric + check!("1987", [num!(Year)]; year: 1987); + check!("1987 ", [num!(Year)]; TOO_LONG); + check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed + check!("x123", [num!(Year)]; INVALID); + check!("2015", [num!(Year)]; year: 2015); + check!("0000", [num!(Year)]; year: 0); + check!("9999", [num!(Year)]; year: 9999); + check!(" \t987", [num!(Year)]; year: 987); + check!("5", [num!(Year)]; year: 5); + check!("5\0", [num!(Year)]; TOO_LONG); + check!("\x005", [num!(Year)]; INVALID); + check!("", [num!(Year)]; TOO_SHORT); + check!("12345", [num!(Year), lit!("5")]; year: 1234); + check!("12345", [nums!(Year), lit!("5")]; year: 1234); + check!("12345", [num0!(Year), lit!("5")]; year: 1234); + check!("12341234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); + check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); + check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + + // signed numeric + check!("-42", [num!(Year)]; year: -42); + check!("+42", [num!(Year)]; year: 42); + check!("-0042", [num!(Year)]; year: -42); + check!("+0042", [num!(Year)]; year: 42); + check!("-42195", [num!(Year)]; year: -42195); + check!("−42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!("+42195", [num!(Year)]; year: 42195); + check!(" -42195", [num!(Year)]; year: -42195); + check!(" +42195", [num!(Year)]; year: 42195); + check!(" - 42", [num!(Year)]; INVALID); + check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" −42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); + check!("-", [num!(Year)]; TOO_SHORT); + check!("+", [num!(Year)]; TOO_SHORT); + + // unsigned numeric + check!("345", [num!(Ordinal)]; ordinal: 345); + check!("+345", [num!(Ordinal)]; INVALID); + check!("-345", [num!(Ordinal)]; INVALID); + check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!("−345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num!(Ordinal)]; TOO_LONG); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); + check!("345🤠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); + check!(" +345", [num!(Ordinal)]; INVALID); + check!(" -345", [num!(Ordinal)]; INVALID); + + // various numeric fields + check!("1234 5678", + [num!(Year), num!(IsoYear)]; + year: 1234, isoyear: 5678); + check!("12 34 56 78", + [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); + check!("1 2 3 4 5 6", + [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), + num!(NumDaysFromSun)]; + month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); + check!("7 89 01", + [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); + check!("23 45 6 78901234 567890123", + [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, + timestamp: 567_890_123); + + // fixed: month and weekday names + check!("apr", [fix!(ShortMonthName)]; month: 4); + check!("Apr", [fix!(ShortMonthName)]; month: 4); + check!("APR", [fix!(ShortMonthName)]; month: 4); + check!("ApR", [fix!(ShortMonthName)]; month: 4); + check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fix!(ShortMonthName)]; TOO_SHORT); + check!("Sol", [fix!(ShortMonthName)]; INVALID); + check!("Apr", [fix!(LongMonthName)]; month: 4); + check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fix!(LongMonthName)]; month: 4); + check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); + check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); + check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); + check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); + check!("The", [fix!(ShortWeekdayName)]; INVALID); + check!("Nop", [fix!(ShortWeekdayName)]; INVALID); + check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); + check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); + check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack + + // fixed: am/pm + check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); + check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); + check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); + check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); + check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); + check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); + check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); + check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fix!(LowerAmPm)]; INVALID); + check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fix!(LowerAmPm)]; TOO_SHORT); + check!("p", [fix!(LowerAmPm)]; TOO_SHORT); + check!("x", [fix!(LowerAmPm)]; TOO_SHORT); + check!("xx", [fix!(LowerAmPm)]; INVALID); + check!("", [fix!(LowerAmPm)]; TOO_SHORT); + + // fixed: dot plus nanoseconds + check!("", [fix!(Nanosecond)]; ); // no field set, but not an error + check!(".", [fix!(Nanosecond)]; TOO_SHORT); + check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); + check!(".0", [fix!(Nanosecond)]; nanosecond: 0); + check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); + check!(".", [fix!(Nanosecond)]; TOO_SHORT); + check!(".4x", [fix!(Nanosecond)]; TOO_LONG); + check!(". 4", [fix!(Nanosecond)]; INVALID); + check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + + // fixed: nanoseconds without the dot + check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); + check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); + + check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); + check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); + + check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); + + // fixed: timezone offsets + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("−00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("−24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" −12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X−12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); + check!("+1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); + check!("-1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("−1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); + check!("-12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); + check!("−12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("−1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("−12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("−1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("−12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" −12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); + check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); + + // some practical examples + check!("2015-02-04T14:37:05+09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: 32400); + check!("2015-02-04T14:37:05-09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("20150204143705567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); + check!("Mon, 10 Jun 2013 09:32:37 GMT", + [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), + fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("Sun Aug 02 13:39:15 CEST 2020", + [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), + num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), + num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; + year: 2020, month: 8, day: 2, weekday: Weekday::Sun, + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); + check!("20060102150405", + [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); + check!("3:14PM", + [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; + hour_div_12: 1, hour_mod_12: 3, minute: 14); + check!("12345678901234.56789", + [num!(Timestamp), lit!("."), num!(Nanosecond)]; + nanosecond: 56_789, timestamp: 12_345_678_901_234); + check!("12345678901234.56789", + [num!(Timestamp), fix!(Nanosecond)]; + nanosecond: 567_890_000, timestamp: 12_345_678_901_234); } - // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); - - // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); - - // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("+", [lit!("+")]; ); - check!("-", [lit!("-")]; ); - check!("−", [lit!("−")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [lit!(" ")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("1", [lit!("1")]; ); - check!("1234", [lit!("1234")]; ); - check!("+1234", [lit!("+1234")]; ); - check!("-1234", [lit!("-1234")]; ); - check!("−1234", [lit!("−1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [lit!("PST")]; ); - check!("🤠", [lit!("🤠")]; ); - check!("🤠a", [lit!("🤠"), lit!("a")]; ); - check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); - check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); - // literals can be together - check!("xy", [lit!("xy")]; ); - check!("xyz", [lit!("xyz")]; ); - // or literals can be apart - check!("xy", [lit!("x"), lit!("y")]; ); - check!("xyz", [lit!("x"), lit!("yz")]; ); - check!("xyz", [lit!("xy"), lit!("z")]; ); - check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); - // - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); - - // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\x005", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - - // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("−42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); - check!(" −42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); - check!(" - 42", [sp!(" "), num!(Year)]; INVALID); - check!(" + 42", [sp!(" "), num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); - - // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!("−345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num!(Ordinal)]; TOO_LONG); - check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); - check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); - check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); - check!("345🤠", [num!(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num!(Ordinal)]; INVALID); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); - - // various numeric fields - check!("1234 5678", - [num!(Year), num!(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); - - // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack - - // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); - - // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming - - // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); - - // fixed: timezone offsets - - // TimezoneOffset - check!("1", [fix!(TimezoneOffset)]; INVALID); - check!("12", [fix!(TimezoneOffset)]; INVALID); - check!("123", [fix!(TimezoneOffset)]; INVALID); - check!("1234", [fix!(TimezoneOffset)]; INVALID); - check!("12345", [fix!(TimezoneOffset)]; INVALID); - check!("123456", [fix!(TimezoneOffset)]; INVALID); - check!("1234567", [fix!(TimezoneOffset)]; INVALID); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("−00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); - check!("−24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" −12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X−12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); - check!("+1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); - check!("-1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); - check!("−1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); - check!("-12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); - check!("−12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("A", [fix!(TimezoneOffset)]; INVALID); - check!("PST", [fix!(TimezoneOffset)]; INVALID); - check!("#Z", [fix!(TimezoneOffset)]; INVALID); - check!(":Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z:", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!(" :Z", [fix!(TimezoneOffset)]; INVALID); - check!(" Z", [fix!(TimezoneOffset)]; INVALID); - check!(" z", [fix!(TimezoneOffset)]; INVALID); - - // TimezoneOffsetColon - check!("1", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("−1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("−12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); - check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("A", [fix!(TimezoneOffsetColon)]; INVALID); - check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); - // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` - // and `TimezoneOffsetTripleColon` for function `parse_internal`. - // No need for separate tests for `TimezoneOffsetDoubleColon` and - // `TimezoneOffsetTripleColon`. - - // TimezoneOffsetZ - check!("1", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("−1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fix!(TimezoneOffsetZ)]; INVALID); - check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` - // in function `parse_internal`. - // No need for separate tests for `TimezoneOffsetColonZ`. - - // TimezoneOffsetPermissive - check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" −12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); - check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); - check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - - // TimezoneName - check!("CEST", [fix!(TimezoneName)]; ); - check!("cest", [fix!(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); - check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); - check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); - - // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("2015-02-04T14:37:05-09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; - year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); -} + #[test] + fn test_rfc2822() { + // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let testdates = [ + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ( + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // complex trailing comment + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment + ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second + ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), + ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month + ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields + ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name + ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour + ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour + ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute + ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset + ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone + // named timezones that have specific timezone offsets + // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + // named single-letter military timezones must fallback to +0000 + ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + // named single-letter timezone "J" is specifically not valid + ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! + ]; -#[cfg(test)] -#[test] -fn test_rfc2822() { - use super::NOT_ENOUGH; - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment - ( - r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), - ), // complex trailing comment - (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses - ( - "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), - ), // multiple comments - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month - ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields - ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name - ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour - ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour - ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute - ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset - ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone - // named timezones that have specific timezone offsets - // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 - ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), - ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - // named single-letter military timezones must fallback to +0000 - ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - // named single-letter timezone "J" is specifically not valid - ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes - ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 - ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone - ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! - ]; - - fn rfc2822_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; - parsed.to_datetime() - } + fn rfc2822_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; + parsed.to_datetime() + } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() - } + fn fmt_rfc2822_datetime(dt: DateTime) -> String { + dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() + } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - eprintln!("Test input: {:?}", date); - eprintln!(" Expect: {:?}", checkdate); - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + // Test against test data above + for &(date, checkdate) in testdates.iter() { + eprintln!("Test input: {:?}", date); + eprintln!(" Expect: {:?}", checkdate); + let d = rfc2822_to_datetime(date); // parse a date + let dt = match d { + // did we get a value? + Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on + Err(e) => Err(e), // otherwise keep an error for the comparison + }; + if dt != checkdate.map(|s| s.to_string()) { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } } } -} -#[cfg(test)] -#[test] -fn parse_rfc850() { - use crate::{TimeZone, Utc}; - - static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; - - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); - - // Check that the format is what we expect - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); - - // Check that it parses correctly - assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); - - // Check that the rest of the weekdays parse correctly (this test originally failed because - // Sunday parsed incorrectly). - let testdates = [ - (Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), "Monday, 07-Nov-94 08:49:37 GMT"), - (Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), "Tuesday, 08-Nov-94 08:49:37 GMT"), - ( - Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), - "Wednesday, 09-Nov-94 08:49:37 GMT", - ), - ( - Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), - "Thursday, 10-Nov-94 08:49:37 GMT", - ), - (Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), "Friday, 11-Nov-94 08:49:37 GMT"), - ( - Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), - "Saturday, 12-Nov-94 08:49:37 GMT", - ), - ]; - - for val in &testdates { - assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); - } + #[test] + fn parse_rfc850() { + static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; + + let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); + + // Check that the format is what we expect + assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); + + // Check that it parses correctly + assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); + + // Check that the rest of the weekdays parse correctly (this test originally failed because + // Sunday parsed incorrectly). + let testdates = [ + ( + Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), + "Monday, 07-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), + "Tuesday, 08-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), + "Friday, 11-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), + ]; - let test_dates_fail = [ - "Saturday, 12-Nov-94 08:49:37", - "Saturday, 12-Nov-94 08:49:37 Z", - "Saturday, 12-Nov-94 08:49:37 GMTTTT", - "Saturday, 12-Nov-94 08:49:37 gmt", - "Saturday, 12-Nov-94 08:49:37 +08:00", - "Caturday, 12-Nov-94 08:49:37 GMT", - "Saturday, 99-Nov-94 08:49:37 GMT", - "Saturday, 12-Nov-2000 08:49:37 GMT", - "Saturday, 12-Mop-94 08:49:37 GMT", - "Saturday, 12-Nov-94 28:49:37 GMT", - "Saturday, 12-Nov-94 08:99:37 GMT", - "Saturday, 12-Nov-94 08:49:99 GMT", - ]; - - for val in &test_dates_fail { - assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); - } -} + for val in &testdates { + assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); + } -#[cfg(test)] -#[test] -fn test_rfc3339() { - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("2015-01-20T17:35:20−08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.001−08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000004−08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-01-20T17:35:20.000000000452−08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) - ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' - ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD - ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS - ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year - ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value - ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format - ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign - ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes - ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes - ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes - ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20−08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T", Err(TOO_SHORT)), // missing HMS - ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S - ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S - ]; - - fn rfc3339_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; - parsed.to_datetime() - } + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() + for val in &test_dates_fail { + assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); + } } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + #[cfg(test)] + #[test] + fn test_rfc3339() { + use super::*; + use crate::offset::FixedOffset; + use crate::DateTime; + + // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let testdates = [ + ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case + ("2015-01-20T17:35:20−08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day + ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), + ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), + ("2015-01-20T17:35:20.001−08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), + ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), + ("2015-01-20T17:35:20.000000004−08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small + ("2015-01-20T17:35:20.000000000452−08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20−08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ]; + + fn rfc3339_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; + parsed.to_datetime() + } + + fn fmt_rfc3339_datetime(dt: DateTime) -> String { + dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() + } + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + let d = rfc3339_to_datetime(date); // parse a date + let dt = match d { + // did we get a value? + Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on + Err(e) => Err(e), // otherwise keep an error for the comparison + }; + if dt != checkdate.map(|s| s.to_string()) { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } } } -} -#[cfg(test)] -#[test] -fn test_issue_1010() { - let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", - "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); - assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + #[test] + fn test_issue_1010() { + let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); + assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + } } From fb220c8ab9f0a627fd3230700ac2fbc50538d2d7 Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:47:12 -0500 Subject: [PATCH 364/999] Fix NaiveTime doc typo `3:00:59 + 60s = 3:01:59`, not `3:02:00` --- src/naive/time/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 328f2cbf76..bff4543f7f 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -100,7 +100,8 @@ mod tests; /// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): /// /// - `03:00:00 + 1s = 03:00:01`. -/// - `03:00:59 + 60s = 03:02:00`. +/// - `03:00:59 + 60s = 03:01:59`. +/// - `03:00:59 + 61s = 03:02:00`. /// - `03:00:59 + 1s = 03:01:00`. /// - `03:00:60 + 1s = 03:01:00`. /// Note that the sum is identical to the previous. From edbdfc1e817e6c19159a0924573d04b190f68f0e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 22 Jun 2023 08:03:52 +0200 Subject: [PATCH 365/999] Add test for formatting type sizes --- src/format/strftime.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 9dae6e7e09..0a61a859e9 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -742,4 +742,21 @@ mod tests { let mut buf = String::new(); let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); } + + #[test] + #[cfg(not(feature = "unstable-locales"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 80); + } + + #[test] + #[cfg(feature = "unstable-locales")] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 2); + } } From f6b453ea60d78103f2f244589453b79655c0fd69 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:48:10 +0200 Subject: [PATCH 366/999] Add format string parsing benchmarks --- benches/chrono.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/benches/chrono.rs b/benches/chrono.rs index 216616b824..a097ce6ea5 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -3,7 +3,10 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use chrono::format::StrftimeItems; use chrono::prelude::*; +#[cfg(feature = "unstable-locales")] +use chrono::Locale; use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags}; fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { @@ -122,6 +125,27 @@ fn bench_num_days_from_ce(c: &mut Criterion) { } } +fn bench_parse_strftime(c: &mut Criterion) { + c.bench_function("bench_parse_strftime", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new(str); + black_box(items.collect::>()); + }) + }); +} + +#[cfg(feature = "unstable-locales")] +fn bench_parse_strftime_localized(c: &mut Criterion) { + c.bench_function("bench_parse_strftime_localized", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new_with_locale(str, Locale::nl_NL); + black_box(items.collect::>()); + }) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -132,6 +156,13 @@ criterion_group!( bench_year_flags_from_year, bench_num_days_from_ce, bench_get_local_time, + bench_parse_strftime, ); +#[cfg(feature = "unstable-locales")] +criterion_group!(unstable_locales, bench_parse_strftime_localized,); + +#[cfg(not(feature = "unstable-locales"))] criterion_main!(benches); +#[cfg(feature = "unstable-locales")] +criterion_main!(benches, unstable_locales); From 91b8db8c23206c2d782ad698a23c0a5d561ed05a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:06:33 +0200 Subject: [PATCH 367/999] Rename `recons` to `queue` --- src/format/strftime.rs | 65 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 0a61a859e9..2569040632 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -220,9 +220,8 @@ pub struct StrftimeItems<'a> { /// Remaining portion of the string. remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), - /// parser refers to the statically reconstructed slice of them. - /// If `recons` is not empty they have to be returned earlier than the `remainder`. - recons: Fmt<'a>, + /// `queue` stores a slice of `Item`s that have to be returned one by one. + queue: Fmt<'a>, /// Date format d_fmt: Fmt<'a>, /// Date and time format @@ -247,7 +246,7 @@ impl<'a> StrftimeItems<'a> { let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt } + StrftimeItems { remainder: s, queue: Vec::new(), d_fmt, d_t_fmt, t_fmt } } #[cfg(not(feature = "unstable-locales"))] @@ -256,7 +255,7 @@ impl<'a> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: FMT_NONE, + queue: FMT_NONE, d_fmt: D_FMT, d_t_fmt: D_T_FMT, t_fmt: T_FMT, @@ -267,7 +266,7 @@ impl<'a> StrftimeItems<'a> { fn with_remainer(s: &'a str) -> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: Vec::new(), + queue: Vec::new(), d_fmt: D_FMT.to_vec(), d_t_fmt: D_T_FMT.to_vec(), t_fmt: T_FMT.to_vec(), @@ -281,17 +280,17 @@ impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; fn next(&mut self) -> Option> { - // we have some reconstructed items to return - if !self.recons.is_empty() { + // We have items queued to return from a specifier composed of multiple formatting items. + if !self.queue.is_empty() { let item; #[cfg(feature = "unstable-locales")] { - item = self.recons.remove(0); + item = self.queue.remove(0); } #[cfg(not(feature = "unstable-locales"))] { - item = self.recons[0].clone(); - self.recons = &self.recons[1..]; + item = self.queue[0].clone(); + self.queue = &self.queue[1..]; } return Some(item); } @@ -329,32 +328,32 @@ impl<'a> Iterator for StrftimeItems<'a> { return Some(Item::Error); } - macro_rules! recons { + macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ #[cfg(feature = "unstable-locales")] { - self.recons.clear(); - $(self.recons.push($tail);)+ + self.queue.clear(); + $(self.queue.push($tail);)+ } #[cfg(not(feature = "unstable-locales"))] { - const RECONS: &'static [Item<'static>] = &[$($tail),+]; - self.recons = RECONS; + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; } $head }) } - macro_rules! recons_from_slice { + macro_rules! queue_from_slice { ($slice:expr) => {{ #[cfg(feature = "unstable-locales")] { - self.recons.clear(); - self.recons.extend_from_slice(&$slice[1..]); + self.queue.clear(); + self.queue.extend_from_slice(&$slice[1..]); } #[cfg(not(feature = "unstable-locales"))] { - self.recons = &$slice[1..]; + self.queue = &$slice[1..]; } $slice[0].clone() }}; @@ -365,26 +364,26 @@ impl<'a> Iterator for StrftimeItems<'a> { 'B' => fix!(LongMonthName), 'C' => num0!(YearDiv100), 'D' => { - recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] + queue![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] } - 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], + 'F' => queue![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], 'G' => num0!(IsoYear), 'H' => num0!(Hour), 'I' => num0!(Hour12), 'M' => num0!(Minute), 'P' => fix!(LowerAmPm), - 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)], + 'R' => queue![num0!(Hour), lit!(":"), num0!(Minute)], 'S' => num0!(Second), - 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], + 'T' => queue![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], 'U' => num0!(WeekFromSun), 'V' => num0!(IsoWeek), 'W' => num0!(WeekFromMon), - 'X' => recons_from_slice!(self.t_fmt), + 'X' => queue_from_slice!(self.t_fmt), 'Y' => num0!(Year), 'Z' => fix!(TimezoneName), 'a' => fix!(ShortWeekdayName), 'b' | 'h' => fix!(ShortMonthName), - 'c' => recons_from_slice!(self.d_t_fmt), + 'c' => queue_from_slice!(self.d_t_fmt), 'd' => num0!(Day), 'e' => nums!(Day), 'f' => num0!(Nanosecond), @@ -395,7 +394,7 @@ impl<'a> Iterator for StrftimeItems<'a> { 'm' => num0!(Month), 'n' => sp!("\n"), 'p' => fix!(UpperAmPm), - 'r' => recons![ + 'r' => queue![ num0!(Hour12), lit!(":"), num0!(Minute), @@ -408,10 +407,10 @@ impl<'a> Iterator for StrftimeItems<'a> { 't' => sp!("\t"), 'u' => num!(WeekdayFromMon), 'v' => { - recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] + queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] } 'w' => num!(NumDaysFromSun), - 'x' => recons_from_slice!(self.d_fmt), + 'x' => queue_from_slice!(self.d_fmt), 'y' => num0!(YearMod100), 'z' => { if is_alternate { @@ -467,13 +466,15 @@ impl<'a> Iterator for StrftimeItems<'a> { _ => Item::Error, // no such specifier }; - // adjust `item` if we have any padding modifier + // Adjust `item` if we have any padding modifier. + // Not allowed on non-numeric items or on specifiers composed out of multiple + // formatting items. if let Some(new_pad) = pad_override { match item { - Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { + Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { Some(Item::Numeric(kind.clone(), new_pad)) } - _ => Some(Item::Error), // no reconstructed or non-numeric item allowed + _ => Some(Item::Error), } } else { Some(item) From 38ce59114c508b1e992e71cd46139ea4fcc1868e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:18:27 +0200 Subject: [PATCH 368/999] Factor out `parse_next_item` --- src/format/strftime.rs | 62 +++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 2569040632..fce2ac416c 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -295,22 +295,31 @@ impl<'a> Iterator for StrftimeItems<'a> { return Some(item); } - match self.remainder.chars().next() { + // Normal: we are parsing the formatting string. + let (remainder, item) = self.parse_next_item(self.remainder)?; + self.remainder = remainder; + Some(item) + } +} + +impl<'a> StrftimeItems<'a> { + fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + match remainder.chars().next() { // we are done None => None, // the next item is a specifier Some('%') => { - self.remainder = &self.remainder[1..]; + remainder = &remainder[1..]; macro_rules! next { () => { - match self.remainder.chars().next() { + match remainder.chars().next() { Some(x) => { - self.remainder = &self.remainder[x.len_utf8()..]; + remainder = &remainder[x.len_utf8()..]; x } - None => return Some(Item::Error), // premature end of string + None => return Some((remainder, Item::Error)), // premature end of string } }; } @@ -325,7 +334,7 @@ impl<'a> Iterator for StrftimeItems<'a> { let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(Item::Error); + return Some((remainder, Item::Error)); } macro_rules! queue { @@ -421,14 +430,14 @@ impl<'a> Iterator for StrftimeItems<'a> { } '+' => fix!(RFC3339), ':' => { - if self.remainder.starts_with("::z") { - self.remainder = &self.remainder[3..]; + if remainder.starts_with("::z") { + remainder = &remainder[3..]; fix!(TimezoneOffsetTripleColon) - } else if self.remainder.starts_with(":z") { - self.remainder = &self.remainder[2..]; + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; fix!(TimezoneOffsetDoubleColon) - } else if self.remainder.starts_with('z') { - self.remainder = &self.remainder[1..]; + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; fix!(TimezoneOffsetColon) } else { Item::Error @@ -472,38 +481,35 @@ impl<'a> Iterator for StrftimeItems<'a> { if let Some(new_pad) = pad_override { match item { Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { - Some(Item::Numeric(kind.clone(), new_pad)) + Some((remainder, Item::Numeric(kind.clone(), new_pad))) } - _ => Some(Item::Error), + _ => Some((remainder, Item::Error)), } } else { - Some(item) + Some((remainder, item)) } } // the next item is space Some(c) if c.is_whitespace() => { // `%` is not a whitespace, so `c != '%'` is redundant - let nextspec = self - .remainder - .find(|c: char| !c.is_whitespace()) - .unwrap_or(self.remainder.len()); + let nextspec = + remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = sp!(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } // the next item is literal _ => { - let nextspec = self - .remainder + let nextspec = remainder .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or(self.remainder.len()); + .unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = lit!(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } } } From d8ec23def837c897d6e939466341237033b24f09 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:24:48 +0200 Subject: [PATCH 369/999] Delay parsing of localized formatting string until use --- src/format/strftime.rs | 143 +++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index fce2ac416c..51f4762da8 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -180,21 +180,10 @@ Notes: China Daylight Time. */ -#[cfg(feature = "unstable-locales")] -extern crate alloc; - -#[cfg(feature = "unstable-locales")] -use alloc::vec::Vec; - #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; -#[cfg(feature = "unstable-locales")] -type Fmt<'a> = Vec>; -#[cfg(not(feature = "unstable-locales"))] -type Fmt<'a> = &'static [Item<'static>]; - static D_FMT: &[Item<'static>] = &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; static D_T_FMT: &[Item<'static>] = &[ @@ -221,20 +210,25 @@ pub struct StrftimeItems<'a> { remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), /// `queue` stores a slice of `Item`s that have to be returned one by one. - queue: Fmt<'a>, - /// Date format - d_fmt: Fmt<'a>, - /// Date and time format - d_t_fmt: Fmt<'a>, - /// Time format - t_fmt: Fmt<'a>, + queue: &'static [Item<'static>], + #[cfg(feature = "unstable-locales")] + locale_str: &'a str, + #[cfg(feature = "unstable-locales")] + locale: Option, } impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[must_use] pub fn new(s: &'a str) -> StrftimeItems<'a> { - Self::with_remainer(s) + #[cfg(not(feature = "unstable-locales"))] + { + StrftimeItems { remainder: s, queue: &[] } + } + #[cfg(feature = "unstable-locales")] + { + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None } + } } /// Creates a new parsing iterator from the `strftime`-like format string. @@ -242,35 +236,7 @@ impl<'a> StrftimeItems<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { - let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); - let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); - let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - - StrftimeItems { remainder: s, queue: Vec::new(), d_fmt, d_t_fmt, t_fmt } - } - - #[cfg(not(feature = "unstable-locales"))] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &[Item<'static>; 0] = &[]; - - StrftimeItems { - remainder: s, - queue: FMT_NONE, - d_fmt: D_FMT, - d_t_fmt: D_T_FMT, - t_fmt: T_FMT, - } - } - - #[cfg(feature = "unstable-locales")] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - StrftimeItems { - remainder: s, - queue: Vec::new(), - d_fmt: D_FMT.to_vec(), - d_t_fmt: D_T_FMT.to_vec(), - t_fmt: T_FMT.to_vec(), - } + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } } @@ -281,17 +247,16 @@ impl<'a> Iterator for StrftimeItems<'a> { fn next(&mut self) -> Option> { // We have items queued to return from a specifier composed of multiple formatting items. - if !self.queue.is_empty() { - let item; - #[cfg(feature = "unstable-locales")] - { - item = self.queue.remove(0); - } - #[cfg(not(feature = "unstable-locales"))] - { - item = self.queue[0].clone(); - self.queue = &self.queue[1..]; - } + if let Some((item, remainder)) = self.queue.split_first() { + self.queue = remainder; + return Some(item.clone()); + } + + // We are in the middle of parsing the localized formatting string of a specifier. + #[cfg(feature = "unstable-locales")] + if !self.locale_str.is_empty() { + let (remainder, item) = self.parse_next_item(self.locale_str)?; + self.locale_str = remainder; return Some(item); } @@ -339,31 +304,15 @@ impl<'a> StrftimeItems<'a> { macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ - #[cfg(feature = "unstable-locales")] - { - self.queue.clear(); - $(self.queue.push($tail);)+ - } - #[cfg(not(feature = "unstable-locales"))] - { - const QUEUE: &'static [Item<'static>] = &[$($tail),+]; - self.queue = QUEUE; - } + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; $head }) } - + #[cfg(not(feature = "unstable-locales"))] macro_rules! queue_from_slice { ($slice:expr) => {{ - #[cfg(feature = "unstable-locales")] - { - self.queue.clear(); - self.queue.extend_from_slice(&$slice[1..]); - } - #[cfg(not(feature = "unstable-locales"))] - { - self.queue = &$slice[1..]; - } + self.queue = &$slice[1..]; $slice[0].clone() }}; } @@ -387,12 +336,18 @@ impl<'a> StrftimeItems<'a> { 'U' => num0!(WeekFromSun), 'V' => num0!(IsoWeek), 'W' => num0!(WeekFromMon), - 'X' => queue_from_slice!(self.t_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'X' => queue_from_slice!(T_FMT), + #[cfg(feature = "unstable-locales")] + 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), 'Y' => num0!(Year), 'Z' => fix!(TimezoneName), 'a' => fix!(ShortWeekdayName), 'b' | 'h' => fix!(ShortMonthName), - 'c' => queue_from_slice!(self.d_t_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'c' => queue_from_slice!(D_T_FMT), + #[cfg(feature = "unstable-locales")] + 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), 'd' => num0!(Day), 'e' => nums!(Day), 'f' => num0!(Nanosecond), @@ -419,7 +374,10 @@ impl<'a> StrftimeItems<'a> { queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] } 'w' => num!(NumDaysFromSun), - 'x' => queue_from_slice!(self.d_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'x' => queue_from_slice!(D_FMT), + #[cfg(feature = "unstable-locales")] + 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), 'y' => num0!(YearMod100), 'z' => { if is_alternate { @@ -513,6 +471,23 @@ impl<'a> StrftimeItems<'a> { } } } + + #[cfg(feature = "unstable-locales")] + fn switch_to_locale_str( + &mut self, + localized_fmt_str: impl Fn(Locale) -> &'static str, + fallback: &'static [Item<'static>], + ) -> Item<'a> { + if let Some(locale) = self.locale { + assert!(self.locale_str.is_empty()); + let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap(); + self.locale_str = fmt_str; + item + } else { + self.queue = &fallback[1..]; + fallback[0].clone() + } + } } #[cfg(test)] @@ -755,7 +730,7 @@ mod tests { fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 32); } #[test] @@ -763,7 +738,7 @@ mod tests { fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 2); } } From 13ad948ef9d0426d82a44a9be5fc1370edfea5a1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:25:33 +0200 Subject: [PATCH 370/999] Make `StrftimeItems::new` const --- src/format/strftime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 51f4762da8..94e315b5d1 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -220,7 +220,7 @@ pub struct StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[must_use] - pub fn new(s: &'a str) -> StrftimeItems<'a> { + pub const fn new(s: &'a str) -> StrftimeItems<'a> { #[cfg(not(feature = "unstable-locales"))] { StrftimeItems { remainder: s, queue: &[] } @@ -235,7 +235,7 @@ impl<'a> StrftimeItems<'a> { #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] - pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { + pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } } From ea9398eb0d8fa55fe0064093af32f45443e5c0e0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Jun 2023 21:44:41 +0200 Subject: [PATCH 371/999] Add formatting benchmarks --- benches/chrono.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/benches/chrono.rs b/benches/chrono.rs index a097ce6ea5..d0babf3e19 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -146,6 +146,36 @@ fn bench_parse_strftime_localized(c: &mut Criterion) { }); } +fn bench_format(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format", |b| b.iter(|| format!("{}", dt.format("%Y-%m-%d %H-%M-%S")))); +} + +fn bench_format_with_items(c: &mut Criterion) { + let dt = Local::now(); + let items: Vec<_> = StrftimeItems::new("%Y-%m-%d %H-%M-%S").collect(); + c.bench_function("bench_format_with_items", |b| { + b.iter(|| format!("{}", dt.format_with_items(items.iter()))) + }); +} + +fn bench_format_manual(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format_manual", |b| { + b.iter(|| { + format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + ) + }) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -157,6 +187,9 @@ criterion_group!( bench_num_days_from_ce, bench_get_local_time, bench_parse_strftime, + bench_format, + bench_format_with_items, + bench_format_manual, ); #[cfg(feature = "unstable-locales")] From 1a3c43a03e088cff247f86da126888726de52065 Mon Sep 17 00:00:00 2001 From: Mike Cronce Date: Thu, 29 Jun 2023 07:02:26 -0400 Subject: [PATCH 372/999] Add FromStr for FixedOffset (#1157) Co-authored-by: Yuxuan Shui --- src/format/mod.rs | 4 ++-- src/format/scan.rs | 4 ++-- src/offset/fixed.rs | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 7c9c3b7dd0..9591fa4d16 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -420,7 +420,7 @@ impl Error for ParseError { } // to be used in this module and submodules -const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); +pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); @@ -838,7 +838,7 @@ mod parsed; // due to the size of parsing routines, they are in separate modules. mod parse; -mod scan; +pub(crate) mod scan; pub mod strftime; diff --git a/src/format/scan.rs b/src/format/scan.rs index 2273dec7d6..00e06c7c13 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -197,7 +197,7 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { +pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } @@ -205,7 +205,7 @@ pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +pub(crate) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 246d6666ea..fcf445fd97 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -5,14 +5,18 @@ use core::fmt; use core::ops::{Add, Sub}; +use core::str::FromStr; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; +use crate::format::scan; +use crate::format::OUT_OF_RANGE; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; +use crate::ParseError; use crate::Timelike; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. @@ -113,6 +117,15 @@ impl FixedOffset { } } +/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). +impl FromStr for FixedOffset { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space)?; + Self::east_opt(offset).ok_or(OUT_OF_RANGE) + } +} + impl TimeZone for FixedOffset { type Offset = FixedOffset; @@ -246,6 +259,7 @@ impl Sub for DateTime { mod tests { use super::FixedOffset; use crate::offset::TimeZone; + use std::str::FromStr; #[test] fn test_date_extreme_offset() { @@ -292,4 +306,14 @@ mod tests { "2012-03-04T05:06:07-23:59:59".to_string() ); } + + #[test] + fn test_parse_offset() { + let offset = FixedOffset::from_str("-0500").unwrap(); + assert_eq!(offset.local_minus_utc, -5 * 3600); + let offset = FixedOffset::from_str("-08:00").unwrap(); + assert_eq!(offset.local_minus_utc, -8 * 3600); + let offset = FixedOffset::from_str("+06:30").unwrap(); + assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); + } } From 224933ec512a0cf0d428152c8a730abcae9fb9e1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:13:06 +0200 Subject: [PATCH 373/999] Remove indirection for parsing timezone offset --- src/format/parse.rs | 4 +++- src/format/scan.rs | 19 ++++--------------- src/offset/fixed.rs | 2 +- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index f1737d7dd5..4c1c0b9eae 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -454,7 +454,9 @@ where | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + false, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 00e06c7c13..35e39e7a85 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -201,17 +201,6 @@ pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } -/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. -/// -/// The additional `colon` may be used to parse a mandatory or optional `:` -/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(crate) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset_internal(s, consume_colon, false, true) -} - /// Parse a timezone from `s` and return the offset in seconds. /// /// The `consume_colon` function is used to parse a mandatory or optional `:` @@ -226,7 +215,7 @@ where /// This is part of [RFC 3339 & ISO 8601]. /// /// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC -fn timezone_offset_internal( +pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, @@ -321,7 +310,7 @@ where Err(INVALID) } } - _ => timezone_offset(s, colon), + _ => timezone_offset(s, colon, false, true), } } @@ -333,7 +322,7 @@ where { match s.as_bytes().first() { Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true, true), + _ => timezone_offset(s, colon, true, true), } } @@ -371,7 +360,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset_internal(s, |s| Ok(s), false, false)?; + let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index fcf445fd97..dbcc9ebb07 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -121,7 +121,7 @@ impl FixedOffset { impl FromStr for FixedOffset { type Err = ParseError; fn from_str(s: &str) -> Result { - let (_, offset) = scan::timezone_offset(s, scan::colon_or_space)?; + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, true)?; Self::east_opt(offset).ok_or(OUT_OF_RANGE) } } From 3330c186b2ab55470db67aabadb22367058cd942 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:14:28 +0200 Subject: [PATCH 374/999] Group imports from the same module --- src/offset/fixed.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index dbcc9ebb07..df9f27680f 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -11,13 +11,10 @@ use core::str::FromStr; use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; -use crate::format::scan; -use crate::format::OUT_OF_RANGE; +use crate::format::{scan, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::DateTime; -use crate::ParseError; -use crate::Timelike; +use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// From 11a63a07d1980bc5cf8490e073d3e12518784725 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:27:37 +0200 Subject: [PATCH 375/999] Inline trivial single-use function --- src/format/parse.rs | 2 +- src/format/scan.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 4c1c0b9eae..b9f93b7d1a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -445,7 +445,7 @@ where } &TimezoneName => { - try_consume!(scan::timezone_name_skip(s)); + try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); } &TimezoneOffsetColon diff --git a/src/format/scan.rs b/src/format/scan.rs index 35e39e7a85..5e0b610cf0 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -365,12 +365,6 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> } } -/// Tries to consume everything until next whitespace-like symbol. -/// Does not provide any offset information from the consumed data. -pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ())) -} - /// Tries to consume an RFC2822 comment including preceding ` `. /// /// Returns the remaining string after the closing parenthesis. From cc6671ffeae4418ec7f7ffe3c4b178002a22e676 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:43:33 +0200 Subject: [PATCH 376/999] Use std API for case-insensitive ASCII comparison --- src/format/scan.rs | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 5e0b610cf0..8bd31d8117 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -8,24 +8,6 @@ use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; -/// Returns true when two slices are equal case-insensitively (in ASCII). -/// Assumes that the `pattern` is already converted to lower case. -fn equals(s: &[u8], pattern: &str) -> bool { - let mut xs = s.iter().map(|&c| match c { - b'A'..=b'Z' => c + 32, - _ => c, - }); - let mut ys = pattern.as_bytes().iter().cloned(); - loop { - match (xs.next(), ys.next()) { - (None, None) => return true, - (None, _) | (_, None) => return false, - (Some(x), Some(y)) if x != y => return false, - _ => (), - } - } -} - /// Tries to parse the non-negative number from `min` to `max` digits. /// /// The absence of digits at all is an unconditional error. @@ -143,14 +125,16 @@ pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// It prefers long month names to short month names when both are possible. pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&str; 12] = - ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; + static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [ + b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember", + b"ember", + ]; let (mut s, month0) = short_month0(s)?; // tries to consume the suffix if possible let suffix = LONG_MONTH_SUFFIXES[month0 as usize]; - if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -161,14 +145,14 @@ pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// It prefers long weekday names to short weekday names when both are possible. pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&str; 7] = - ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; + static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] = + [b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"]; let (mut s, weekday) = short_weekday(s)?; // tries to consume the suffix if possible let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize]; - if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -338,17 +322,17 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> let name = &s.as_bytes()[..upto]; let s = &s[upto..]; let offset_hours = |o| Ok((s, Some(o * 3600))); - if equals(name, "gmt") || equals(name, "ut") { + if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") { offset_hours(0) - } else if equals(name, "edt") { + } else if name.eq_ignore_ascii_case(b"edt") { offset_hours(-4) - } else if equals(name, "est") || equals(name, "cdt") { + } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { offset_hours(-5) - } else if equals(name, "cst") || equals(name, "mdt") { + } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { offset_hours(-6) - } else if equals(name, "mst") || equals(name, "pdt") { + } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { offset_hours(-7) - } else if equals(name, "pst") { + } else if name.eq_ignore_ascii_case(b"pst") { offset_hours(-8) } else if name.len() == 1 { match name[0] { From 5293ed77467620a11008d0734c1a72939262b103 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:48:58 +0200 Subject: [PATCH 377/999] Fold zulu parsing into timezone_offset() --- src/format/parse.rs | 10 ++++++--- src/format/scan.rs | 50 ++++++++++++++++++++------------------------- src/offset/fixed.rs | 2 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index b9f93b7d1a..77293ff614 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -213,7 +213,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_nanosecond(nanosecond)?; } - let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':'))); + let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); if offset <= -86_400 || offset >= 86_400 { return Err(OUT_OF_RANGE); } @@ -456,15 +456,19 @@ where s.trim_start(), scan::colon_or_space, false, + false, true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( + let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + true, + false, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 8bd31d8117..7479e26824 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -202,12 +202,32 @@ pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, + allow_zulu: bool, allow_missing_minutes: bool, allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { + if allow_zulu { + let bytes = s.as_bytes(); + match bytes.first() { + Some(&b'z') | Some(&b'Z') => return Ok((&s[1..], 0)), + Some(&b'u') | Some(&b'U') => { + if bytes.len() >= 3 { + let (b, c) = (bytes[1], bytes[2]); + match (b | 32, c | 32) { + (b't', b'c') => return Ok((&s[3..], 0)), + _ => return Err(INVALID), + } + } else { + return Err(INVALID); + } + } + _ => {} + } + } + const fn digits(s: &str) -> ParseResult<(u8, u8)> { let b = s.as_bytes(); if b.len() < 2 { @@ -275,39 +295,13 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - let bytes = s.as_bytes(); - match bytes.first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - Some(&b'u') | Some(&b'U') => { - if bytes.len() >= 3 { - let (b, c) = (bytes[1], bytes[2]); - match (b | 32, c | 32) { - (b't', b'c') => Ok((&s[3..], 0)), - _ => Err(INVALID), - } - } else { - Err(INVALID) - } - } - _ => timezone_offset(s, colon, false, true), - } -} - /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as /// `+00:00`, and allows missing minutes entirely. pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { - match s.as_bytes().first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset(s, colon, true, true), - } + timezone_offset(s, colon, true, true, true) } /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. @@ -344,7 +338,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false)?; + let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index df9f27680f..f7f0425dde 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -118,7 +118,7 @@ impl FixedOffset { impl FromStr for FixedOffset { type Err = ParseError; fn from_str(s: &str) -> Result { - let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, true)?; + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; Self::east_opt(offset).ok_or(OUT_OF_RANGE) } } From 69456af23cd420c98587c09817ff21f26d02ec6b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:49:58 +0200 Subject: [PATCH 378/999] Fold permissive parsing into timezone_offset() --- src/format/parse.rs | 7 +++++-- src/format/scan.rs | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 77293ff614..619d0a9ef4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -475,9 +475,12 @@ where &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( + let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + true, + true, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 7479e26824..8f11e195d9 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -295,15 +295,6 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as -/// `+00:00`, and allows missing minutes entirely. -pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset(s, colon, true, true, true) -} - /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). /// See [RFC 2822 Section 4.3]. From f489ee12919d21d11caee8ddb03e45aa6330b913 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:12:57 +0200 Subject: [PATCH 379/999] Move formatting to `format/format.rs` --- src/format/formatting.rs | 590 +++++++++++++++++++++++++++++++++++++++ src/format/mod.rs | 584 +------------------------------------- 2 files changed, 598 insertions(+), 576 deletions(-) create mode 100644 src/format/formatting.rs diff --git a/src/format/formatting.rs b/src/format/formatting.rs new file mode 100644 index 0000000000..452d7b76b8 --- /dev/null +++ b/src/format/formatting.rs @@ -0,0 +1,590 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! Date and time formatting routines. + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use core::borrow::Borrow; +use core::fmt; +use core::fmt::Write; + +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::naive::{NaiveDate, NaiveTime}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::offset::{FixedOffset, Offset}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::{Datelike, Timelike, Weekday}; + +#[cfg(feature = "unstable-locales")] +use super::locales; +#[cfg(any(feature = "alloc", feature = "std", test))] +use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; + +#[cfg(any(feature = "alloc", feature = "std", test))] +struct Locales { + short_months: &'static [&'static str], + long_months: &'static [&'static str], + short_weekdays: &'static [&'static str], + long_weekdays: &'static [&'static str], + am_pm: &'static [&'static str], +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl Locales { + fn new(_locale: Option) -> Self { + #[cfg(feature = "unstable-locales")] + { + let locale = _locale.unwrap_or(Locale::POSIX); + Self { + short_months: locales::short_months(locale), + long_months: locales::long_months(locale), + short_weekdays: locales::short_weekdays(locale), + long_weekdays: locales::long_weekdays(locale), + am_pm: locales::am_pm(locale), + } + } + #[cfg(not(feature = "unstable-locales"))] + Self { + short_months: &[ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ], + long_months: &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + long_weekdays: &[ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + am_pm: &["AM", "PM"], + } + } +} + +/// A *temporary* object which can be used as an argument to `format!` or others. +/// This is normally constructed via `format` methods of each date and time type. +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +#[derive(Debug)] +pub struct DelayedFormat { + /// The date view, if any. + date: Option, + /// The time view, if any. + time: Option, + /// The name and local-to-UTC difference for the offset (timezone), if any. + off: Option<(String, FixedOffset)>, + /// An iterator returning formatting items. + items: I, + /// Locale used for text. + // TODO: Only used with the locale feature. We should make this property + // only present when the feature is enabled. + #[cfg(feature = "unstable-locales")] + locale: Option, +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { + /// Makes a new `DelayedFormat` value out of local date and time. + #[must_use] + pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { + DelayedFormat { + date, + time, + off: None, + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } + } + + /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + #[must_use] + pub fn new_with_offset( + date: Option, + time: Option, + offset: &Off, + items: I, + ) -> DelayedFormat + where + Off: Offset + fmt::Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { + date, + time, + off: Some(name_and_diff), + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } + } + + /// Makes a new `DelayedFormat` value out of local date and time and locale. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] + pub fn new_with_locale( + date: Option, + time: Option, + items: I, + locale: Locale, + ) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale: Some(locale) } + } + + /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] + pub fn new_with_offset_and_locale( + date: Option, + time: Option, + offset: &Off, + items: I, + locale: Locale, + ) -> DelayedFormat + where + Off: Offset + fmt::Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } + } +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[cfg(feature = "unstable-locales")] + { + if let Some(locale) = self.locale { + return format_localized( + f, + self.date.as_ref(), + self.time.as_ref(), + self.off.as_ref(), + self.items.clone(), + locale, + ); + } + } + + format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) + } +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +pub fn format<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + let mut result = String::new(); + for item in items { + format_inner(&mut result, date, time, off, item.borrow(), None)?; + } + w.pad(&result) +} +/// Formats single formatting item +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +pub fn format_item( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, +) -> fmt::Result { + let mut result = String::new(); + format_inner(&mut result, date, time, off, item, None)?; + w.pad(&result) +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] +pub fn format_localized<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, + locale: Locale, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + let mut result = String::new(); + for item in items { + format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; + } + w.pad(&result) +} + +/// Formats single formatting item +#[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] +pub fn format_item_localized( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, + locale: Locale, +) -> fmt::Result { + let mut result = String::new(); + format_inner(&mut result, date, time, off, item, Some(locale))?; + w.pad(&result) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +fn format_inner( + result: &mut String, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, + locale: Option, +) -> fmt::Result { + let locale = Locales::new(locale); + + match *item { + Item::Literal(s) | Item::Space(s) => result.push_str(s), + #[cfg(any(feature = "alloc", feature = "std", test))] + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), + + Item::Numeric(ref spec, ref pad) => { + use self::Numeric::*; + + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); + + let (width, v) = match *spec { + Year => (4, date.map(|d| i64::from(d.year()))), + YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), + IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), + IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), + Month => (2, date.map(|d| i64::from(d.month()))), + Day => (2, date.map(|d| i64::from(d.day()))), + WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), + WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), + IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), + NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), + WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), + Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), + Hour => (2, time.map(|t| i64::from(t.hour()))), + Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), + Minute => (2, time.map(|t| i64::from(t.minute()))), + Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), + Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), + Timestamp => ( + 1, + match (date, time, off) { + (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), + (Some(d), Some(t), Some(&(_, off))) => { + Some((d.and_time(*t) - off).timestamp()) + } + (_, _, _) => None, + }, + ), + + // for the future expansion + Internal(ref int) => match int._dummy {}, + }; + + if let Some(v) = v { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match *pad { + Pad::None => write!(result, "{:+}", v), + Pad::Zero => write!(result, "{:+01$}", v, width + 1), + Pad::Space => write!(result, "{:+1$}", v, width + 1), + } + } else { + match *pad { + Pad::None => write!(result, "{}", v), + Pad::Zero => write!(result, "{:01$}", v, width), + Pad::Space => write!(result, "{:1$}", v, width), + } + }? + } else { + return Err(fmt::Error); // insufficient arguments for given format + } + } + + Item::Fixed(ref spec) => { + use self::Fixed::*; + + let ret = + match *spec { + ShortMonthName => date.map(|d| { + result.push_str(locale.short_months[d.month0() as usize]); + Ok(()) + }), + LongMonthName => date.map(|d| { + result.push_str(locale.long_months[d.month0() as usize]); + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + result.push_str( + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LongWeekdayName => date.map(|d| { + result.push_str( + locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) + } + Ok(()) + }), + UpperAmPm => time.map(|t| { + result.push_str(if t.hour12().0 { + locale.am_pm[1] + } else { + locale.am_pm[0] + }); + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else if nano % 1_000_000 == 0 { + write!(result, ".{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(result, ".{:06}", nano / 1_000) + } else { + write!(result, ".{:09}", nano) + } + }), + Nanosecond3 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:03}", nano / 1_000_000) + }), + Nanosecond6 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:06}", nano / 1_000) + }), + Nanosecond9 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:03}", nano / 1_000_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:06}", nano / 1_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:09}", nano) + }), + TimezoneName => off.map(|(name, _)| { + result.push_str(name); + Ok(()) + }), + TimezoneOffsetColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), + TimezoneOffsetDoubleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), + TimezoneOffsetTripleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), + TimezoneOffsetColonZ => off + .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), + TimezoneOffset => { + off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) + } + TimezoneOffsetZ => { + off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) + } + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822_inner(result, d, t, off, locale)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) + } else { + None + } + } + }; + + match ret { + Some(ret) => ret?, + None => return Err(fmt::Error), // insufficient arguments for given format + } + } + + Item::Error => return Err(fmt::Error), + } + Ok(()) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum Colons { + None, + Single, + Double, + Triple, +} + +/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. +/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. +#[cfg(any(feature = "alloc", feature = "std", test))] +fn write_local_minus_utc( + result: &mut String, + off: FixedOffset, + allow_zulu: bool, + colon_type: Colons, +) -> fmt::Result { + let off = off.local_minus_utc(); + if allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + result.push(sign); + + write_hundreds(result, (off / 3600) as u8)?; + + match colon_type { + Colons::None => write_hundreds(result, (off / 60 % 60) as u8), + Colons::Single => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8) + } + Colons::Double => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8)?; + result.push(':'); + write_hundreds(result, (off % 60) as u8) + } + Colons::Triple => Ok(()), + } +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) fn write_rfc3339( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + // reuse `Debug` impls which already print ISO 8601 format. + // this is faster in this way. + write!(result, "{:?}", dt)?; + write_local_minus_utc(result, off, false, Colons::Single) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +fn write_rfc2822_inner( + result: &mut String, + d: &NaiveDate, + t: &NaiveTime, + off: FixedOffset, + locale: Locales, +) -> fmt::Result { + let year = d.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str(", "); + write_hundreds(result, d.day() as u8)?; + result.push(' '); + result.push_str(locale.short_months[d.month0() as usize]); + result.push(' '); + write_hundreds(result, (year / 100) as u8)?; + write_hundreds(result, (year % 100) as u8)?; + result.push(' '); + write_hundreds(result, t.hour() as u8)?; + result.push(':'); + write_hundreds(result, t.minute() as u8)?; + result.push(':'); + let sec = t.second() + t.nanosecond() / 1_000_000_000; + write_hundreds(result, sec as u8)?; + result.push(' '); + write_local_minus_utc(result, off, false, Colons::None) +} + +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} diff --git a/src/format/mod.rs b/src/format/mod.rs index 9591fa4d16..d0d88d9b01 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -35,27 +35,23 @@ extern crate alloc; #[cfg(feature = "alloc")] use alloc::boxed::Box; -#[cfg(feature = "alloc")] -use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; use core::fmt; -use core::fmt::Write; use core::str::FromStr; #[cfg(any(feature = "std", test))] use std::error::Error; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::{Datelike, Timelike}; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; #[cfg(feature = "unstable-locales")] pub(crate) mod locales; +pub(crate) use formatting::write_hundreds; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub use formatting::{format, format_item, DelayedFormat}; +#[cfg(feature = "unstable-locales")] +pub use formatting::{format_item_localized, format_localized}; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) use formatting::{write_rfc2822, write_rfc3339}; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; /// L10n locales. @@ -275,15 +271,6 @@ enum InternalInternal { Nanosecond9NoDot, } -#[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum Colons { - None, - Single, - Double, - Triple, -} - /// A single formatting item. This is used for both formatting and parsing. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { @@ -428,412 +415,8 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); -#[cfg(any(feature = "alloc", feature = "std", test))] -struct Locales { - short_months: &'static [&'static str], - long_months: &'static [&'static str], - short_weekdays: &'static [&'static str], - long_weekdays: &'static [&'static str], - am_pm: &'static [&'static str], -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl Locales { - fn new(_locale: Option) -> Self { - #[cfg(feature = "unstable-locales")] - { - let locale = _locale.unwrap_or(Locale::POSIX); - Self { - short_months: locales::short_months(locale), - long_months: locales::long_months(locale), - short_weekdays: locales::short_weekdays(locale), - long_weekdays: locales::long_weekdays(locale), - am_pm: locales::am_pm(locale), - } - } - #[cfg(not(feature = "unstable-locales"))] - Self { - short_months: &[ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - ], - long_months: &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - long_weekdays: &[ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - am_pm: &["AM", "PM"], - } - } -} - -/// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -pub fn format_item( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, None)?; - w.pad(&result) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner( - result: &mut String, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Option, -) -> fmt::Result { - let locale = Locales::new(locale); - - match *item { - Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some((d.and_time(*t) - off).timestamp()) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(result, "{:+}", v), - Pad::Zero => write!(result, "{:+01$}", v, width + 1), - Pad::Space => write!(result, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(result, "{}", v), - Pad::Zero => write!(result, "{:01$}", v, width), - Pad::Space => write!(result, "{:1$}", v, width), - } - }? - } else { - return Err(fmt::Error); // insufficient arguments for given format - } - } - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(locale.short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(locale.long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result.push_str( - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str( - locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; - for char in ampm.chars() { - result.extend(char.to_lowercase()) - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { - locale.am_pm[1] - } else { - locale.am_pm[0] - }); - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|(name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffsetColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), - TimezoneOffsetDoubleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), - TimezoneOffsetTripleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), - TimezoneOffsetColonZ => off - .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) - } - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, d, t, off, locale)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) - } else { - None - } - } - }; - - match ret { - Some(ret) => ret?, - None => return Err(fmt::Error), // insufficient arguments for given format - } - } - - Item::Error => return Err(fmt::Error), - } - Ok(()) -} - -/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. -/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std", test))] -fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - colon_type: Colons, -) -> fmt::Result { - let off = off.local_minus_utc(); - if allow_zulu && off == 0 { - result.push('Z'); - return Ok(()); - } - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - result.push(sign); - - write_hundreds(result, (off / 3600) as u8)?; - - match colon_type { - Colons::None => write_hundreds(result, (off / 60 % 60) as u8), - Colons::Single => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8) - } - Colons::Double => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8)?; - result.push(':'); - write_hundreds(result, (off % 60) as u8) - } - Colons::Triple => Ok(()), - } -} - -/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std", test))] -pub(crate) fn write_rfc3339( - result: &mut String, - dt: crate::NaiveDateTime, - off: FixedOffset, -) -> fmt::Result { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(result, "{:?}", dt)?; - write_local_minus_utc(result, off, false, Colons::Single) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -pub(crate) fn write_rfc2822( - result: &mut String, - dt: crate::NaiveDateTime, - off: FixedOffset, -) -> fmt::Result { - write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -fn write_rfc2822_inner( - result: &mut String, - d: &NaiveDate, - t: &NaiveTime, - off: FixedOffset, - locale: Locales, -) -> fmt::Result { - let year = d.year(); - // RFC2822 is only defined on years 0 through 9999 - if !(0..=9999).contains(&year) { - return Err(fmt::Error); - } - - result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); - result.push_str(", "); - write_hundreds(result, d.day() as u8)?; - result.push(' '); - result.push_str(locale.short_months[d.month0() as usize]); - result.push(' '); - write_hundreds(result, (year / 100) as u8)?; - write_hundreds(result, (year % 100) as u8)?; - result.push(' '); - write_hundreds(result, t.hour() as u8)?; - result.push(':'); - write_hundreds(result, t.minute() as u8)?; - result.push(':'); - let sec = t.second() + t.nanosecond() / 1_000_000_000; - write_hundreds(result, sec as u8)?; - result.push(' '); - write_local_minus_utc(result, off, false, Colons::None) -} - -/// Equivalent to `{:02}` formatting for n < 100. -pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { - if n >= 100 { - return Err(fmt::Error); - } - - let tens = b'0' + n / 10; - let ones = b'0' + n % 10; - w.write_char(tens as char)?; - w.write_char(ones as char) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -pub fn format<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), None)?; - } - w.pad(&result) -} +mod formatting; mod parsed; // due to the size of parsing routines, they are in separate modules. @@ -841,118 +424,6 @@ mod parse; pub(crate) mod scan; pub mod strftime; - -/// A *temporary* object which can be used as an argument to `format!` or others. -/// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -#[derive(Debug)] -pub struct DelayedFormat { - /// The date view, if any. - date: Option, - /// The time view, if any. - time: Option, - /// The name and local-to-UTC difference for the offset (timezone), if any. - off: Option<(String, FixedOffset)>, - /// An iterator returning formatting items. - items: I, - /// Locale used for text. - // TODO: Only used with the locale feature. We should make this property - // only present when the feature is enabled. - #[cfg(feature = "unstable-locales")] - locale: Option, -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { - /// Makes a new `DelayedFormat` value out of local date and time. - #[must_use] - pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { - date, - time, - off: None, - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. - #[must_use] - pub fn new_with_offset( - date: Option, - time: Option, - offset: &Off, - items: I, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date, - time, - off: Some(name_and_diff), - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and locale. - #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] - #[must_use] - pub fn new_with_locale( - date: Option, - time: Option, - items: I, - locale: Locale, - ) -> DelayedFormat { - DelayedFormat { date, time, off: None, items, locale: Some(locale) } - } - - /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. - #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] - #[must_use] - pub fn new_with_offset_and_locale( - date: Option, - time: Option, - offset: &Off, - items: I, - locale: Locale, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } - } -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - { - if let Some(locale) = self.locale { - return format_localized( - f, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - self.items.clone(), - locale, - ); - } - } - - format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) - } -} - // this implementation is here only because we need some private code from `scan` /// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). @@ -991,45 +462,6 @@ 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( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Locale, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, Some(locale))?; - w.pad(&result) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] -pub fn format_localized<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, - locale: Locale, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; - } - w.pad(&result) -} - /// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). /// /// # Example From d717526b4222fd4d69c8edaf28e69ada9aab6b30 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:22:41 +0200 Subject: [PATCH 380/999] Move static formatting definitions into `parse_next_item` --- src/format/strftime.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 94e315b5d1..0c2edef7a1 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -184,25 +184,6 @@ Notes: use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; -static D_FMT: &[Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &[Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), -]; -static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; - /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] pub struct StrftimeItems<'a> { @@ -269,6 +250,25 @@ impl<'a> Iterator for StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + static D_FMT: &[Item<'static>] = + &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; + static D_T_FMT: &[Item<'static>] = &[ + fix!(ShortWeekdayName), + sp!(" "), + fix!(ShortMonthName), + sp!(" "), + nums!(Day), + sp!(" "), + num0!(Hour), + lit!(":"), + num0!(Minute), + lit!(":"), + num0!(Second), + sp!(" "), + num0!(Year), + ]; + static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; + match remainder.chars().next() { // we are done None => None, From 7afc6a2600fea734d134cb595c7249320a5c8695 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:06:16 +0200 Subject: [PATCH 381/999] Replace macros for formatting items with functions --- src/format/mod.rs | 44 +- src/format/parse.rs | 1152 ++++++++++++++++++++-------------------- src/format/strftime.rs | 213 ++++---- 3 files changed, 710 insertions(+), 699 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d0d88d9b01..d9124fcd4c 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -295,40 +295,24 @@ pub enum Item<'a> { Error, } -macro_rules! lit { - ($x:expr) => { - Item::Literal($x) - }; +const fn num(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::None) } -macro_rules! sp { - ($x:expr) => { - Item::Space($x) - }; -} -macro_rules! num { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::None) - }; -} -macro_rules! num0 { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Zero) - }; + +const fn num0(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Zero) } -macro_rules! nums { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Space) - }; + +const fn nums(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Space) } -macro_rules! fix { - ($x:ident) => { - Item::Fixed(Fixed::$x) - }; + +const fn fixed(fixed: Fixed) -> Item<'static> { + Item::Fixed(fixed) } -macro_rules! internal_fix { - ($x:ident) => { - Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x })) - }; + +const fn internal_fixed(val: InternalInternal) -> Item<'static> { + Item::Fixed(Fixed::Internal(InternalFixed { val })) } /// An error from the `parse` function. diff --git a/src/format/parse.rs b/src/format/parse.rs index 619d0a9ef4..e390dd3ea3 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -566,6 +566,10 @@ mod tests { #[test] fn test_parse() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; + // workaround for Rust issue #22255 fn parse_all(s: &str, items: &[Item]) -> ParseResult { let mut parsed = Parsed::new(); @@ -590,658 +594,658 @@ mod tests { check!("a", []; TOO_LONG); // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); + check!("", [Space("")]; ); + check!(" ", [Space("")]; ); + check!("\t", [Space("")]; ); + check!(" \n\r \n", [Space("")]; ); + check!("a", [Space("")]; TOO_LONG); // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("+", [lit!("+")]; ); - check!("-", [lit!("-")]; ); - check!("−", [lit!("−")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [lit!(" ")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("1", [lit!("1")]; ); - check!("1234", [lit!("1234")]; ); - check!("+1234", [lit!("+1234")]; ); - check!("-1234", [lit!("-1234")]; ); - check!("−1234", [lit!("−1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [lit!("PST")]; ); - check!("🤠", [lit!("🤠")]; ); - check!("🤠a", [lit!("🤠"), lit!("a")]; ); - check!("🤠a🤠", [lit!("🤠"), lit!("a🤠")]; ); - check!("a🤠b", [lit!("a"), lit!("🤠"), lit!("b")]; ); + check!("", [Literal("a")]; TOO_SHORT); + check!(" ", [Literal("a")]; INVALID); + check!("a", [Literal("a")]; ); + check!("+", [Literal("+")]; ); + check!("-", [Literal("-")]; ); + check!("−", [Literal("−")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [Literal(" ")]; ); + check!("aa", [Literal("a")]; TOO_LONG); + check!("A", [Literal("a")]; INVALID); + check!("xy", [Literal("xy")]; ); + check!("xy", [Literal("x"), Literal("y")]; ); + check!("1", [Literal("1")]; ); + check!("1234", [Literal("1234")]; ); + check!("+1234", [Literal("+1234")]; ); + check!("-1234", [Literal("-1234")]; ); + check!("−1234", [Literal("−1234")]; ); // MINUS SIGN (U+2212) + check!("PST", [Literal("PST")]; ); + check!("🤠", [Literal("🤠")]; ); + check!("🤠a", [Literal("🤠"), Literal("a")]; ); + check!("🤠a🤠", [Literal("🤠"), Literal("a🤠")]; ); + check!("a🤠b", [Literal("a"), Literal("🤠"), Literal("b")]; ); // literals can be together - check!("xy", [lit!("xy")]; ); - check!("xyz", [lit!("xyz")]; ); + check!("xy", [Literal("xy")]; ); + check!("xyz", [Literal("xyz")]; ); // or literals can be apart - check!("xy", [lit!("x"), lit!("y")]; ); - check!("xyz", [lit!("x"), lit!("yz")]; ); - check!("xyz", [lit!("xy"), lit!("z")]; ); - check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + check!("xy", [Literal("x"), Literal("y")]; ); + check!("xyz", [Literal("x"), Literal("yz")]; ); + check!("xyz", [Literal("xy"), Literal("z")]; ); + check!("xyz", [Literal("x"), Literal("y"), Literal("z")]; ); // - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [Literal("x"), Literal("y")]; INVALID); + check!("xy", [Literal("x"), Space(""), Literal("y")]; ); + check!("x y", [Literal("x"), Space(""), Literal("y")]; ); // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\x005", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1987", [num(Year)]; year: 1987); + check!("1987 ", [num(Year)]; TOO_LONG); + check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed + check!("x123", [num(Year)]; INVALID); + check!("2015", [num(Year)]; year: 2015); + check!("0000", [num(Year)]; year: 0); + check!("9999", [num(Year)]; year: 9999); + check!(" \t987", [num(Year)]; year: 987); + check!("5", [num(Year)]; year: 5); + check!("5\0", [num(Year)]; TOO_LONG); + check!("\x005", [num(Year)]; INVALID); + check!("", [num(Year)]; TOO_SHORT); + check!("12345", [num(Year), Literal("5")]; year: 1234); + check!("12345", [nums(Year), Literal("5")]; year: 1234); + check!("12345", [num0(Year), Literal("5")]; year: 1234); + check!("12341234", [num(Year), num(Year)]; year: 1234); + check!("1234 1234", [num(Year), num(Year)]; year: 1234); + check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); + check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); + check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("−42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); - check!(" −42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); - check!(" - 42", [sp!(" "), num!(Year)]; INVALID); - check!(" + 42", [sp!(" "), num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); + check!("-42", [num(Year)]; year: -42); + check!("+42", [num(Year)]; year: 42); + check!("-0042", [num(Year)]; year: -42); + check!("+0042", [num(Year)]; year: 42); + check!("-42195", [num(Year)]; year: -42195); + check!("−42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) + check!("+42195", [num(Year)]; year: 42195); + check!(" -42195", [num(Year)]; year: -42195); + check!(" +42195", [num(Year)]; year: 42195); + check!(" - 42", [num(Year)]; INVALID); + check!(" + 42", [num(Year)]; INVALID); + check!(" -42195", [Space(" "), num(Year)]; year: -42195); + check!(" −42195", [Space(" "), num(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [Space(" "), num(Year)]; year: 42195); + check!(" - 42", [Space(" "), num(Year)]; INVALID); + check!(" + 42", [Space(" "), num(Year)]; INVALID); + check!("-", [num(Year)]; TOO_SHORT); + check!("+", [num(Year)]; TOO_SHORT); // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!("−345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num!(Ordinal)]; TOO_LONG); - check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); - check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); - check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); - check!("345🤠", [num!(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num!(Ordinal)]; INVALID); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); + check!("345", [num(Ordinal)]; ordinal: 345); + check!("+345", [num(Ordinal)]; INVALID); + check!("-345", [num(Ordinal)]; INVALID); + check!(" 345", [num(Ordinal)]; ordinal: 345); + check!("−345", [num(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num(Ordinal)]; TOO_LONG); + check!(" 345", [Space(" "), num(Ordinal)]; ordinal: 345); + check!("345 ", [num(Ordinal), Space(" ")]; ordinal: 345); + check!("345🤠 ", [num(Ordinal), Literal("🤠"), Space(" ")]; ordinal: 345); + check!("345🤠", [num(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num(Ordinal)]; INVALID); + check!(" +345", [num(Ordinal)]; INVALID); + check!(" -345", [num(Ordinal)]; INVALID); // various numeric fields check!("1234 5678", - [num!(Year), num!(IsoYear)]; + [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + [num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)]; year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; + [num(Month), num(Day), num(WeekFromSun), num(WeekFromMon), num(IsoWeek), + num(NumDaysFromSun)]; month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + [num(WeekdayFromMon), num(Ordinal), num(Hour12)]; weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack + check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("Apr", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("APR", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("ApR", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("April", [fixed(Fixed::ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fixed(Fixed::ShortMonthName)]; TOO_SHORT); + check!("Sol", [fixed(Fixed::ShortMonthName)]; INVALID); + check!("Apr", [fixed(Fixed::LongMonthName)]; month: 4); + check!("Apri", [fixed(Fixed::LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fixed(Fixed::LongMonthName)]; month: 4); + check!("Aprill", [fixed(Fixed::LongMonthName)]; TOO_LONG); + check!("Aprill", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); + check!("Aprl", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); + check!("April", [fixed(Fixed::LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fixed(Fixed::ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fixed(Fixed::ShortWeekdayName)]; TOO_SHORT); + check!("The", [fixed(Fixed::ShortWeekdayName)]; INVALID); + check!("Nop", [fixed(Fixed::ShortWeekdayName)]; INVALID); + check!("Thu", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thus", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thursday", [fixed(Fixed::LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); + check!("am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!("pm", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); + check!("AM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!("PM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); + check!("am", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); + check!("pm", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); + check!("AM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); + check!("PM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); + check!("Am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fixed(Fixed::LowerAmPm)]; INVALID); + check!("ame", [fixed(Fixed::LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("p", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); + check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error + check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); + check!("4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fixed(Fixed::Nanosecond), num(Second)]; second: 4); + check!(".0", [fixed(Fixed::Nanosecond)]; nanosecond: 0); + check!(".4", [fixed(Fixed::Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fixed(Fixed::Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fixed(Fixed::Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fixed(Fixed::Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fixed(Fixed::Nanosecond)]; nanosecond: 0); + check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); + check!(".4x", [fixed(Fixed::Nanosecond)]; TOO_LONG); + check!(". 4", [fixed(Fixed::Nanosecond)]; INVALID); + check!(" .4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); + check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); + + check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); + check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); + + check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets // TimezoneOffset - check!("1", [fix!(TimezoneOffset)]; INVALID); - check!("12", [fix!(TimezoneOffset)]; INVALID); - check!("123", [fix!(TimezoneOffset)]; INVALID); - check!("1234", [fix!(TimezoneOffset)]; INVALID); - check!("12345", [fix!(TimezoneOffset)]; INVALID); - check!("123456", [fix!(TimezoneOffset)]; INVALID); - check!("1234567", [fix!(TimezoneOffset)]; INVALID); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("−00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); - check!("−24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" −12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X−12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🤠+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:🤠34", [fix!(TimezoneOffset)]; INVALID); - check!("+1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); - check!("-1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); - check!("−1234🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: 45_240); - check!("-12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); - check!("−12:34🤠", [fix!(TimezoneOffset), lit!("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🤠+12:34", [lit!("🤠"), fix!(TimezoneOffset)]; offset: 45_240); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("A", [fix!(TimezoneOffset)]; INVALID); - check!("PST", [fix!(TimezoneOffset)]; INVALID); - check!("#Z", [fix!(TimezoneOffset)]; INVALID); - check!(":Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z:", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!(" :Z", [fix!(TimezoneOffset)]; INVALID); - check!(" Z", [fix!(TimezoneOffset)]; INVALID); - check!(" z", [fix!(TimezoneOffset)]; INVALID); + check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12345", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("−12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); + check!("-00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); + check!("−00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fixed(Fixed::TimezoneOffset)]; offset: 60); + check!("-00:01", [fixed(Fixed::TimezoneOffset)]; offset: -60); + check!("+00:30", [fixed(Fixed::TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fixed(Fixed::TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fixed(Fixed::TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); + check!("−24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fixed(Fixed::TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fixed(Fixed::TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!(" −12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffset), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("X12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("Z+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("X+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("X−12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🤠+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:🤠34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: 45_240); + check!("-1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); + check!("−1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: 45_240); + check!("-12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); + check!("−12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🤠+12:34", [Literal("🤠"), fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("A", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" z", [fixed(Fixed::TimezoneOffset)]; INVALID); // TimezoneOffsetColon - check!("1", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("−1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("−12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("−12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); - check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("A", [fix!(TimezoneOffsetColon)]; INVALID); - check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12345678", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("−1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:3", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("−12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("−12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("−12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon), Literal(":")]; offset: 45_240); + check!("Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("A", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("−1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("−12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fix!(TimezoneOffsetZ)]; INVALID); - check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+🙃", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("1", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12345678", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); + check!("−1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:3", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+12:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); + check!("−12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!("z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!(" z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Z ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(":z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+🙃", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Y", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("+1234ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("+12:34ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. // TimezoneOffsetPermissive - check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" −12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); - check!("🤠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🤠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fix!(TimezoneOffsetPermissive), lit!("🤠")]; offset: 45_240); - check!("🤠+12:34", [lit!("🤠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+🙃", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!("−12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" −12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); + check!("🤠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🤠34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🤠", [internal_fixed(TimezoneOffsetPermissive), Literal("🤠")]; offset: 45_240); + check!("🤠+12:34", [Literal("🤠"), internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+🙃", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); // TimezoneName - check!("CEST", [fix!(TimezoneName)]; ); - check!("cest", [fix!(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); - check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); - check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); + check!("CEST", [fixed(Fixed::TimezoneName)]; ); + check!("cest", [fixed(Fixed::TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fixed(Fixed::TimezoneName), Literal(" "), num(Day)]; day: 5); + check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: 32400); check!("2015-02-04T14:37:05-09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: -32400); check!("2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: -32400); check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; + [num(Year), num(Month), num(Day), + num(Hour), num(Minute), num(Second), internal_fixed(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + [fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), Literal(":"), + num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; + [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), + num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), + num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), num(Year)]; year: 2020, month: 8, day: 2, weekday: Weekday::Sun, hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; + [num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)]; year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; + [num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)]; hour_div_12: 1, hour_mod_12: 3, minute: 14); check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; + [num(Timestamp), Literal("."), num(Nanosecond)]; nanosecond: 56_789, timestamp: 12_345_678_901_234); check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; + [num(Timestamp), fixed(Fixed::Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 0c2edef7a1..cd57c90ed6 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -180,9 +180,10 @@ Notes: China Daylight Time. */ +use super::{fixed, internal_fixed, num, num0, nums}; #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; -use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; +use super::{Fixed, InternalInternal, Item, Numeric, Pad}; /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] @@ -250,24 +251,29 @@ impl<'a> Iterator for StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + use InternalInternal::*; + use Item::{Literal, Space}; + use Numeric::*; + static D_FMT: &[Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; + &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]; static D_T_FMT: &[Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), + fixed(Fixed::ShortWeekdayName), + Space(" "), + fixed(Fixed::ShortMonthName), + Space(" "), + nums(Day), + Space(" "), + num0(Hour), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + num0(Year), ]; - static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; + static T_FMT: &[Item<'static>] = + &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; match remainder.chars().next() { // we are done @@ -318,118 +324,126 @@ impl<'a> StrftimeItems<'a> { } let item = match spec { - 'A' => fix!(LongWeekdayName), - 'B' => fix!(LongMonthName), - 'C' => num0!(YearDiv100), + 'A' => fixed(Fixed::LongWeekdayName), + 'B' => fixed(Fixed::LongMonthName), + 'C' => num0(YearDiv100), 'D' => { - queue![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] + queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] + } + 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], + 'G' => num0(IsoYear), + 'H' => num0(Hour), + 'I' => num0(Hour12), + 'M' => num0(Minute), + 'P' => fixed(Fixed::LowerAmPm), + 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], + 'S' => num0(Second), + 'T' => { + queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] } - 'F' => queue![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], - 'G' => num0!(IsoYear), - 'H' => num0!(Hour), - 'I' => num0!(Hour12), - 'M' => num0!(Minute), - 'P' => fix!(LowerAmPm), - 'R' => queue![num0!(Hour), lit!(":"), num0!(Minute)], - 'S' => num0!(Second), - 'T' => queue![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], - 'U' => num0!(WeekFromSun), - 'V' => num0!(IsoWeek), - 'W' => num0!(WeekFromMon), + 'U' => num0(WeekFromSun), + 'V' => num0(IsoWeek), + 'W' => num0(WeekFromMon), #[cfg(not(feature = "unstable-locales"))] 'X' => queue_from_slice!(T_FMT), #[cfg(feature = "unstable-locales")] 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), - 'Y' => num0!(Year), - 'Z' => fix!(TimezoneName), - 'a' => fix!(ShortWeekdayName), - 'b' | 'h' => fix!(ShortMonthName), + 'Y' => num0(Year), + 'Z' => fixed(Fixed::TimezoneName), + 'a' => fixed(Fixed::ShortWeekdayName), + 'b' | 'h' => fixed(Fixed::ShortMonthName), #[cfg(not(feature = "unstable-locales"))] 'c' => queue_from_slice!(D_T_FMT), #[cfg(feature = "unstable-locales")] 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), - 'd' => num0!(Day), - 'e' => nums!(Day), - 'f' => num0!(Nanosecond), - 'g' => num0!(IsoYearMod100), - 'j' => num0!(Ordinal), - 'k' => nums!(Hour), - 'l' => nums!(Hour12), - 'm' => num0!(Month), - 'n' => sp!("\n"), - 'p' => fix!(UpperAmPm), + 'd' => num0(Day), + 'e' => nums(Day), + 'f' => num0(Nanosecond), + 'g' => num0(IsoYearMod100), + 'j' => num0(Ordinal), + 'k' => nums(Hour), + 'l' => nums(Hour12), + 'm' => num0(Month), + 'n' => Space("\n"), + 'p' => fixed(Fixed::UpperAmPm), 'r' => queue![ - num0!(Hour12), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - fix!(UpperAmPm) + num0(Hour12), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + fixed(Fixed::UpperAmPm) ], - 's' => num!(Timestamp), - 't' => sp!("\t"), - 'u' => num!(WeekdayFromMon), + 's' => num(Timestamp), + 't' => Space("\t"), + 'u' => num(WeekdayFromMon), 'v' => { - queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] + queue![ + nums(Day), + Literal("-"), + fixed(Fixed::ShortMonthName), + Literal("-"), + num0(Year) + ] } - 'w' => num!(NumDaysFromSun), + 'w' => num(NumDaysFromSun), #[cfg(not(feature = "unstable-locales"))] 'x' => queue_from_slice!(D_FMT), #[cfg(feature = "unstable-locales")] 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), - 'y' => num0!(YearMod100), + 'y' => num0(YearMod100), 'z' => { if is_alternate { - internal_fix!(TimezoneOffsetPermissive) + internal_fixed(TimezoneOffsetPermissive) } else { - fix!(TimezoneOffset) + fixed(Fixed::TimezoneOffset) } } - '+' => fix!(RFC3339), + '+' => fixed(Fixed::RFC3339), ':' => { if remainder.starts_with("::z") { remainder = &remainder[3..]; - fix!(TimezoneOffsetTripleColon) + fixed(Fixed::TimezoneOffsetTripleColon) } else if remainder.starts_with(":z") { remainder = &remainder[2..]; - fix!(TimezoneOffsetDoubleColon) + fixed(Fixed::TimezoneOffsetDoubleColon) } else if remainder.starts_with('z') { remainder = &remainder[1..]; - fix!(TimezoneOffsetColon) + fixed(Fixed::TimezoneOffsetColon) } else { Item::Error } } '.' => match next!() { '3' => match next!() { - 'f' => fix!(Nanosecond3), + 'f' => fixed(Fixed::Nanosecond3), _ => Item::Error, }, '6' => match next!() { - 'f' => fix!(Nanosecond6), + 'f' => fixed(Fixed::Nanosecond6), _ => Item::Error, }, '9' => match next!() { - 'f' => fix!(Nanosecond9), + 'f' => fixed(Fixed::Nanosecond9), _ => Item::Error, }, - 'f' => fix!(Nanosecond), + 'f' => fixed(Fixed::Nanosecond), _ => Item::Error, }, '3' => match next!() { - 'f' => internal_fix!(Nanosecond3NoDot), + 'f' => internal_fixed(Nanosecond3NoDot), _ => Item::Error, }, '6' => match next!() { - 'f' => internal_fix!(Nanosecond6NoDot), + 'f' => internal_fixed(Nanosecond6NoDot), _ => Item::Error, }, '9' => match next!() { - 'f' => internal_fix!(Nanosecond9NoDot), + 'f' => internal_fixed(Nanosecond9NoDot), _ => Item::Error, }, - '%' => lit!("%"), + '%' => Literal("%"), _ => Item::Error, // no such specifier }; @@ -454,7 +468,7 @@ impl<'a> StrftimeItems<'a> { let nextspec = remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = sp!(&remainder[..nextspec]); + let item = Space(&remainder[..nextspec]); remainder = &remainder[nextspec..]; Some((remainder, item)) } @@ -465,7 +479,7 @@ impl<'a> StrftimeItems<'a> { .find(|c: char| c.is_whitespace() || c == '%') .unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = lit!(&remainder[..nextspec]); + let item = Literal(&remainder[..nextspec]); remainder = &remainder[nextspec..]; Some((remainder, item)) } @@ -492,9 +506,12 @@ impl<'a> StrftimeItems<'a> { #[cfg(test)] mod tests { + use super::StrftimeItems; + use crate::format::Item::{self, Literal, Space}; #[cfg(feature = "unstable-locales")] - use super::Locale; - use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, StrftimeItems}; + use crate::format::Locale; + use crate::format::{fixed, internal_fixed, num, num0, nums}; + use crate::format::{Fixed, InternalInternal, Numeric::*}; use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] @@ -507,25 +524,28 @@ mod tests { } assert_eq!(parse_and_collect(""), []); - 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(" \t\n\r "), [Space(" \t\n\r ")]); + assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); assert_eq!( parse_and_collect("a b\t\nc"), - [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] + [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")] + ); + assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]); + assert_eq!( + parse_and_collect("100%% ok"), + [Literal("100"), Literal("%"), Space(" "), Literal("ok")] ); - 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("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]); assert_eq!( parse_and_collect("%Y-%m-%d"), - [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] + [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] ); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); - assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); + assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); - assert_eq!(parse_and_collect("%%"), [lit!("%")]); + assert_eq!(parse_and_collect("%%"), [Literal("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); - assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); + assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); 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]); @@ -536,16 +556,19 @@ mod tests { 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("%-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"), [internal_fix!(TimezoneOffsetPermissive)]); + 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"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!( + parse_and_collect("%#z"), + [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] + ); assert_eq!(parse_and_collect("%#m"), [Item::Error]); } From cc0e427ab22a58fbb5d05f0f812588b50053b735 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:24:18 +0200 Subject: [PATCH 382/999] Move module declaration to top --- src/format/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d9124fcd4c..cffd938c0f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -42,6 +42,15 @@ use std::error::Error; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; +mod formatting; +mod parsed; + +// due to the size of parsing routines, they are in separate modules. +mod parse; +pub(crate) mod scan; + +pub mod strftime; + #[cfg(feature = "unstable-locales")] pub(crate) mod locales; @@ -399,15 +408,6 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); - -mod formatting; -mod parsed; - -// due to the size of parsing routines, they are in separate modules. -mod parse; -pub(crate) mod scan; - -pub mod strftime; // this implementation is here only because we need some private code from `scan` /// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). From 807a9fb05b5bbacb65549fa9f7d9f41f6d52c34a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:24:46 +0200 Subject: [PATCH 383/999] Only require `std` feature for `std::error::Error` impl --- src/format/mod.rs | 4 ++-- src/round.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index cffd938c0f..007a2df9ae 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -37,7 +37,7 @@ extern crate alloc; use alloc::boxed::Box; use core::fmt; use core::str::FromStr; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::error::Error; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; @@ -390,7 +390,7 @@ impl fmt::Display for ParseError { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] diff --git a/src/round.rs b/src/round.rs index f43ab595c3..b6e771e0f0 100644 --- a/src/round.rs +++ b/src/round.rs @@ -100,11 +100,11 @@ const fn span_for_digits(digits: u16) -> u32 { /// will also fail if the `Duration` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std", test))] + #[cfg(any(feature = "std"))] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std", test)))] + #[cfg(not(any(feature = "std")))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by Duration. @@ -299,7 +299,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std"))] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] From 456948a20a0c3fb0272749c23b0cff07135a7563 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:32:49 +0200 Subject: [PATCH 384/999] Deduplicate test --- src/datetime/tests.rs | 72 +------------------------------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3aeed665c0..41f0b1e894 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -677,77 +677,7 @@ fn test_subsecond_part() { } #[test] -#[cfg(not(target_os = "windows"))] -fn test_from_system_time() { - use std::time::Duration; - - let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); - let nanos = 999_999_999; - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(2001, 9, 9) - .unwrap() - .and_hms_nano_opt(1, 46, 39, nanos) - .unwrap() - ) - .unwrap() - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() - ) - .unwrap() - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from( - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(2001, 9, 9) - .unwrap() - .and_hms_nano_opt(1, 46, 39, nanos) - .unwrap() - ) - .unwrap() - ), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from( - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(1938, 4, 24) - .unwrap() - .and_hms_nano_opt(22, 13, 20, 1) - .unwrap() - ) - .unwrap() - ), - UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!( - SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), - UNIX_EPOCH - ); - assert_eq!( - SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), - UNIX_EPOCH - ); -} - -#[test] -#[cfg(target_os = "windows")] +#[cfg(feature = "std")] fn test_from_system_time() { use std::time::Duration; From 59531e0cd354328598b40821d715abfe26887065 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:37:03 +0200 Subject: [PATCH 385/999] Require `std` feature for `From` --- src/datetime/mod.rs | 6 +++--- src/datetime/tests.rs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index bf9c16ad5d..b0c002b687 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -15,7 +15,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(feature = "alloc", feature = "std", test))] @@ -1301,7 +1301,7 @@ impl str::FromStr for DateTime { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { @@ -1330,7 +1330,7 @@ impl From for DateTime { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 41f0b1e894..e770843977 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,5 +1,3 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; @@ -679,7 +677,7 @@ fn test_subsecond_part() { #[test] #[cfg(feature = "std")] fn test_from_system_time() { - use std::time::Duration; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; let nanos = 999_999_000; From e15b897b9bcb3320235d06d2bcdf46925227efbc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:05:54 +0200 Subject: [PATCH 386/999] Make `test_rfc3339` work without formatting --- src/format/parse.rs | 55 +++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index e390dd3ea3..cd5250d666 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1411,26 +1411,36 @@ mod tests { } } - #[cfg(test)] #[test] fn test_rfc3339() { - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + // Test data - (input, Ok(expected result) or Err(error code)) let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("2015-01-20T17:35:20−08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.001−08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000004−08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-01-20T17:35:20.000000000452−08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("2015-01-20T17:35:20−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok(ymd_hmsn(1944, 6, 6, 4, 4, 0, 0, 0))), // D-day + ("2001-09-11T09:45:00-08:00", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -8))), + ("2015-01-20T17:35:20.001-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), + ("2015-01-20T17:35:20.001−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 31_000, -8))), + ("2015-01-20T17:35:20.000000004-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), + ("2015-01-20T17:35:20.000000004−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), // with MINUS SIGN (U+2212) + ( + "2015-01-20T17:35:20.000000000452-08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small + ( + "2015-01-20T17:35:20.000000000452−08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small with MINUS SIGN (U+2212) ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS @@ -1479,19 +1489,10 @@ mod tests { parsed.to_datetime() } - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() - } - // Test against test data above for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { + let dt = rfc3339_to_datetime(date); // parse a date + if dt != checkdate { // check for expected result panic!( "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", From 41a1b99b9ecfb628ba08c7ebda25dcefa76e880f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:08:35 +0200 Subject: [PATCH 387/999] Make `test_rfc2822` work without formatting --- src/format/parse.rs | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index cd5250d666..f5bebb0b1a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1251,28 +1251,37 @@ mod tests { #[test] fn test_rfc2822() { - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; + + // Test data - (input, Ok(expected result) or Err(error code)) let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // complex trailing comment (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses ( "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // multiple comments ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second + ("11 Sep 2001 09:45:00 +0000", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("11 Sep 2001 09:45:00 EST", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), + ("11 Sep 2001 09:45:00 GMT", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name @@ -1285,24 +1294,24 @@ mod tests { ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone // named timezones that have specific timezone offsets // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 - ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), - ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -4))), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // named single-letter military timezones must fallback to +0000 - ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 Z", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 A", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 a", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes @@ -1321,21 +1330,12 @@ mod tests { parsed.to_datetime() } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() - } - // Test against test data above for &(date, checkdate) in testdates.iter() { eprintln!("Test input: {:?}", date); eprintln!(" Expect: {:?}", checkdate); - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { + let dt = rfc2822_to_datetime(date); // parse a date + if dt != checkdate { // check for expected result panic!( "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", From eec5232a6985bb505abf4f4458488ff70cc82ed2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:06:01 +0200 Subject: [PATCH 388/999] Make formatting tests require `alloc` or `std` feature --- src/datetime/tests.rs | 4 ++++ src/format/parse.rs | 3 ++- src/format/strftime.rs | 5 ++++- src/naive/date.rs | 1 + src/naive/datetime/tests.rs | 11 +++++++---- src/naive/isoweek.rs | 2 ++ src/naive/time/tests.rs | 1 + 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index e770843977..2d273f605d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -356,6 +356,7 @@ fn test_datetime_with_timezone() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_rfc2822_and_rfc3339() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); assert_eq!( @@ -449,6 +450,7 @@ fn test_datetime_rfc2822_and_rfc3339() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_rfc3339_opts() { use crate::SecondsFormat::*; let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); @@ -479,6 +481,7 @@ fn test_rfc3339_opts() { #[test] #[should_panic] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); @@ -749,6 +752,7 @@ fn test_from_system_time() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_format_alignment() { let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap(); diff --git a/src/format/parse.rs b/src/format/parse.rs index f5bebb0b1a..f56771b7d1 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -562,7 +562,7 @@ impl str::FromStr for DateTime { #[cfg(test)] mod tests { use crate::format::*; - use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; #[test] fn test_parse() { @@ -1353,6 +1353,7 @@ mod tests { let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); // Check that it parses correctly diff --git a/src/format/strftime.rs b/src/format/strftime.rs index cd57c90ed6..ab3b5e16b2 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -512,6 +512,7 @@ mod tests { use crate::format::Locale; use crate::format::{fixed, internal_fixed, num, num0, nums}; use crate::format::{Fixed, InternalInternal, Numeric::*}; + #[cfg(any(feature = "alloc", feature = "std"))] use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] @@ -573,6 +574,7 @@ mod tests { } #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_strftime_docs() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -675,8 +677,8 @@ mod tests { assert_eq!(dt.format("%%").to_string(), "%"); } - #[cfg(feature = "unstable-locales")] #[test] + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] fn test_strftime_docs_localized() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -729,6 +731,7 @@ mod tests { /// /// See . #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_parse_only_timezone_offset_permissive_no_panic() { use crate::NaiveDate; use crate::{FixedOffset, TimeZone}; diff --git a/src/naive/date.rs b/src/naive/date.rs index f34d855a20..50fa4a210a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3040,6 +3040,7 @@ mod tests { } #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_date_format() { let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 53422c8c18..4656147d64 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -17,10 +17,11 @@ fn test_datetime_from_timestamp_millis() { (2034061609000, "2034-06-16 09:06:49.000000000"), ]; - for (timestamp_millis, formatted) in valid_map.iter().copied() { + for (timestamp_millis, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } let invalid = [i64::MAX, i64::MIN]; @@ -54,10 +55,11 @@ fn test_datetime_from_timestamp_micros() { (2034061609000000, "2034-06-16 09:06:49.000000000"), ]; - for (timestamp_micros, formatted) in valid_map.iter().copied() { + for (timestamp_micros, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } let invalid = [i64::MAX, i64::MIN]; @@ -287,6 +289,7 @@ fn test_datetime_parse_from_str() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_format() { let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index df5556a120..8edf738ba0 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -158,11 +158,13 @@ mod tests { assert_eq!(minweek.year(), internals::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 62c46a247e..72491fbd2c 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -285,6 +285,7 @@ fn test_time_parse_from_str() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_time_format() { let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); From 26bfd6c6474d4d43b545692caf281115d4ec24be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:25:07 +0200 Subject: [PATCH 389/999] Remove duplicate code --- src/naive/date.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 50fa4a210a..428a1a20a1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2290,15 +2290,6 @@ mod serde { formatter.write_str("a formatted date string") } - #[cfg(any(feature = "std", test))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - - #[cfg(not(any(feature = "std", test)))] fn visit_str(self, value: &str) -> Result where E: de::Error, From f3e556bd56c887f4f6521575ce09affbabd388a3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:27:52 +0200 Subject: [PATCH 390/999] Fix typo --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b0c002b687..105681a4d7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1495,7 +1495,7 @@ fn test_decodable_json( // we don't know the exact local offset but we can check that // the conversion didn't change the instant itself assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), + local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"), Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ); assert_eq!( From 697f5d7d81d091afd85162b36fff007c7082d7ec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:28:32 +0200 Subject: [PATCH 391/999] Require clock feature for `test_decodable_json` import --- src/datetime/serde.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 002ded0cca..a96f31a901 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1130,7 +1130,9 @@ pub mod ts_seconds_option { #[cfg(test)] mod tests { - use crate::datetime::{test_decodable_json, test_encodable_json}; + #[cfg(feature = "clock")] + use crate::datetime::test_decodable_json; + use crate::datetime::test_encodable_json; use crate::{DateTime, TimeZone, Utc}; #[test] From 72e1fba0d24b386e41df30900ab7014c0fd523bd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:49:36 +0200 Subject: [PATCH 392/999] Make formatting only available with `alloc` or `std` feature, not with `test` --- src/date.rs | 8 ++++---- src/datetime/mod.rs | 12 ++++++------ src/format/formatting.rs | 38 +++++++++++++++++++------------------- src/format/mod.rs | 8 ++++---- src/format/parse.rs | 7 +++---- src/naive/date.rs | 8 ++++---- src/naive/datetime/mod.rs | 8 ++++---- src/naive/time/mod.rs | 8 ++++---- 8 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/date.rs b/src/date.rs index d831745a43..15f3d436d1 100644 --- a/src/date.rs +++ b/src/date.rs @@ -4,7 +4,7 @@ //! ISO 8601 calendar date with time zone. #![allow(deprecated)] -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -15,7 +15,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; @@ -333,7 +333,7 @@ where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -348,7 +348,7 @@ where /// Formats the date with the specified format string. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 105681a4d7..a7bd7a9fa0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -18,7 +18,7 @@ use std::string::ToString; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; @@ -707,7 +707,7 @@ where /// /// 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(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc2822(&self) -> String { @@ -718,7 +718,7 @@ where } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339(&self) -> String { @@ -752,7 +752,7 @@ where /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { @@ -798,7 +798,7 @@ where } /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -823,7 +823,7 @@ where /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 452d7b76b8..955d684248 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -8,24 +8,24 @@ extern crate alloc; #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::fmt; use core::fmt::Write; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::{Datelike, Timelike, Weekday}; #[cfg(feature = "unstable-locales")] use super::locales; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] struct Locales { short_months: &'static [&'static str], long_months: &'static [&'static str], @@ -34,7 +34,7 @@ struct Locales { am_pm: &'static [&'static str], } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl Locales { fn new(_locale: Option) -> Self { #[cfg(feature = "unstable-locales")] @@ -84,7 +84,7 @@ impl Locales { /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat { @@ -103,7 +103,7 @@ pub struct DelayedFormat { locale: Option, } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] @@ -172,7 +172,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] @@ -195,7 +195,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for De /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, @@ -215,7 +215,7 @@ where w.pad(&result) } /// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format_item( w: &mut fmt::Formatter, @@ -268,7 +268,7 @@ pub fn format_item_localized( w.pad(&result) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] fn format_inner( result: &mut String, date: Option<&NaiveDate>, @@ -281,7 +281,7 @@ fn format_inner( match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), Item::Numeric(ref spec, ref pad) => { @@ -476,7 +476,7 @@ fn format_inner( Ok(()) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Colons { None, @@ -487,7 +487,7 @@ enum Colons { /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] fn write_local_minus_utc( result: &mut String, off: FixedOffset, @@ -521,7 +521,7 @@ fn write_local_minus_utc( } /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub(crate) fn write_rfc3339( result: &mut String, dt: crate::NaiveDateTime, @@ -533,7 +533,7 @@ pub(crate) fn write_rfc3339( write_local_minus_utc(result, off, false, Colons::Single) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( result: &mut String, @@ -543,7 +543,7 @@ pub(crate) fn write_rfc2822( write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( result: &mut String, diff --git a/src/format/mod.rs b/src/format/mod.rs index 007a2df9ae..7d5dfb8702 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -55,11 +55,11 @@ pub mod strftime; pub(crate) mod locales; pub(crate) use formatting::write_hundreds; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub(crate) use formatting::{write_rfc2822, write_rfc3339}; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; @@ -286,13 +286,13 @@ pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same as `Literal` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; diff --git a/src/format/parse.rs b/src/format/parse.rs index f56771b7d1..49742ce40c 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -309,7 +309,7 @@ where s = &s[prefix.len()..]; } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { return Err((s, TOO_SHORT)); @@ -324,7 +324,7 @@ where s = s.trim_start(); } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedSpace(_) => { s = s.trim_start(); } @@ -1349,12 +1349,11 @@ mod tests { fn parse_rfc850() { static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect #[cfg(any(feature = "alloc", feature = "std"))] - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); + assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); // Check that it parses correctly assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); diff --git a/src/naive/date.rs b/src/naive/date.rs index 428a1a20a1..d4d6b662e2 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,7 +3,7 @@ //! ISO 8601 calendar date without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "unstable-locales")] use pure_rust_locales::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, @@ -1246,7 +1246,7 @@ impl NaiveDate { /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -1290,7 +1290,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 1bd17d57be..6fa9bb31fb 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 date and time without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -12,7 +12,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; @@ -855,7 +855,7 @@ impl NaiveDateTime { /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -899,7 +899,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index bff4543f7f..b0f730feb6 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 time without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -11,7 +11,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, @@ -746,7 +746,7 @@ impl NaiveTime { /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -792,7 +792,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] From ea41eba87f430968418b64b88af9cd710a5fd2fb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 20:48:28 +0200 Subject: [PATCH 393/999] Remove `Tz::Offset: Display` requirement from `DateTime::to_rfc*` --- src/datetime/mod.rs | 193 ++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index a7bd7a9fa0..8686340e16 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -488,6 +488,103 @@ impl DateTime { } } + /// 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"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc2822(&self) -> String { + let mut result = String::with_capacity(32); + crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc2822 datetime to string should never fail"); + result + } + + /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc3339(&self) -> String { + let mut result = String::with_capacity(32); + crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Return an RFC 3339 and ISO 8601 date and time string with subseconds + /// formatted as per `SecondsFormat`. + /// + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses + /// [`Fixed::TimezoneOffsetColon`] + /// + /// # Examples + /// + /// ```rust + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), + /// "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), + /// "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T18:30:09Z"); + /// + /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T10:30:09+08:00"); + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { + use crate::format::Numeric::*; + use crate::format::Pad::Zero; + use crate::SecondsFormat::*; + + debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); + + const PREFIX: &[Item<'static>] = &[ + Item::Numeric(Year, Zero), + Item::Literal("-"), + Item::Numeric(Month, Zero), + Item::Literal("-"), + Item::Numeric(Day, Zero), + Item::Literal("T"), + Item::Numeric(Hour, Zero), + Item::Literal(":"), + Item::Numeric(Minute, Zero), + Item::Literal(":"), + Item::Numeric(Second, Zero), + ]; + + let ssitem = match secform { + Secs => None, + Millis => Some(Item::Fixed(Fixed::Nanosecond3)), + Micros => Some(Item::Fixed(Fixed::Nanosecond6)), + Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), + AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), + __NonExhaustive => unreachable!(), + }; + + let tzitem = Item::Fixed(if use_z { + Fixed::TimezoneOffsetColonZ + } else { + Fixed::TimezoneOffsetColon + }); + + let dt = self.fixed_offset(); + match ssitem { + None => dt.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), + Some(s) => dt.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), + } + } + /// The minimum possible `DateTime`. pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; /// The maximum possible `DateTime`. @@ -701,102 +798,6 @@ impl DateTime 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"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc2822(&self) -> String { - let mut result = String::with_capacity(32); - crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) - .expect("writing rfc2822 datetime to string should never fail"); - result - } - - /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc3339(&self) -> String { - let mut result = String::with_capacity(32); - crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) - .expect("writing rfc3339 datetime to string should never fail"); - result - } - - /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per `SecondsFormat`. - /// - /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as - /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses - /// [`Fixed::TimezoneOffsetColon`] - /// - /// # Examples - /// - /// ```rust - /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); - /// - /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use crate::format::Numeric::*; - use crate::format::Pad::Zero; - use crate::SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &[Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - match ssitem { - None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } - } - /// Formats the combined date and time with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] From 0d09acfdad12458aa844b5474a1e34ed9dbc8497 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 22:04:35 +0200 Subject: [PATCH 394/999] Parallelize `try_verify_against_date_command` --- tests/dateutils.rs | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 36784ac1c6..de100e52fc 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -3,10 +3,10 @@ use chrono::offset::TimeZone; #[cfg(unix)] use chrono::Local; #[cfg(unix)] -use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; +use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; #[cfg(unix)] -use std::{path, process}; +use std::{path, process, thread}; #[cfg(unix)] fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { @@ -90,31 +90,26 @@ fn try_verify_against_date_command() { } assert_run_date_version(); - let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); - eprintln!( - "Run command {:?} for every hour from {} to 2077, skipping some years...", + "Run command {:?} for every hour from 1975 to 2077, skipping some years...", DATE_PATH, - date.year() ); - let mut count: u64 = 0; - let mut year_at = date.year(); - while date.year() < 2078 { - if (1975..=1977).contains(&date.year()) - || (2020..=2022).contains(&date.year()) - || (2073..=2077).contains(&date.year()) - { - if date.year() != year_at { - eprintln!("at year {}...", date.year()); - year_at = date.year(); - } - verify_against_date_command_local(DATE_PATH, date); - count += 1; - } - date += chrono::Duration::hours(1); + let mut children = vec![]; + for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() { + children.push(thread::spawn(|| { + let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN); + let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); + while date <= end { + verify_against_date_command_local(DATE_PATH, date); + date += chrono::Duration::hours(1); + } + })); + } + for child in children { + // Wait for the thread to finish. Returns a result. + let _ = child.join(); } - eprintln!("Command {:?} was run {} times", DATE_PATH, count); } #[cfg(target_os = "linux")] From 3dda28cffdda58d3062ada194a133237fedca142 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 20 May 2023 13:28:13 +0200 Subject: [PATCH 395/999] New offset formatter `OffsetFormat` --- src/format/formatting.rs | 181 +++++++++++++++++++++++++++------------ src/format/mod.rs | 45 ++++++++++ 2 files changed, 172 insertions(+), 54 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 955d684248..3c2b479005 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -23,7 +23,10 @@ use crate::{Datelike, Timelike, Weekday}; #[cfg(feature = "unstable-locales")] use super::locales; #[cfg(any(feature = "alloc", feature = "std"))] -use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; +use super::{ + Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, + OffsetPrecision, Pad, +}; #[cfg(any(feature = "alloc", feature = "std"))] struct Locales { @@ -428,20 +431,42 @@ fn format_inner( result.push_str(name); Ok(()) }), - TimezoneOffsetColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), - TimezoneOffsetDoubleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), - TimezoneOffsetTripleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), - TimezoneOffsetColonZ => off - .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) - } + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { return Err(fmt::Error); } @@ -477,46 +502,82 @@ fn format_inner( } #[cfg(any(feature = "alloc", feature = "std"))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum Colons { - None, - Single, - Double, - Triple, -} - -/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. -/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std"))] -fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - colon_type: Colons, -) -> fmt::Result { - let off = off.local_minus_utc(); - if allow_zulu && off == 0 { - result.push('Z'); - return Ok(()); - } - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - result.push(sign); - - write_hundreds(result, (off / 3600) as u8)?; +impl OffsetFormat { + /// Writes an offset from UTC to `result` with the format defined by `self`. + fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result { + let off = off.local_minus_utc(); + if self.allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + + let hours; + let mut mins = 0; + let mut secs = 0; + let precision = match self.precision { + OffsetPrecision::Hours => { + // Minutes and seconds are simply truncated + hours = (off / 3600) as u8; + OffsetPrecision::Hours + } + OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { + // Round seconds to the nearest minute. + let minutes = (off + 30) / 60; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } + OffsetPrecision::Seconds + | OffsetPrecision::OptionalSeconds + | OffsetPrecision::OptionalMinutesAndSeconds => { + let minutes = off / 60; + secs = (off % 60) as u8; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision != OffsetPrecision::Seconds && secs == 0 { + if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } else { + OffsetPrecision::Seconds + } + } + }; + let colons = self.colons == Colons::Colon; - match colon_type { - Colons::None => write_hundreds(result, (off / 60 % 60) as u8), - Colons::Single => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8) + if hours < 10 { + if self.padding == Pad::Space { + result.push(' '); + } + result.push(sign); + if self.padding == Pad::Zero { + result.push('0'); + } + result.push((b'0' + hours) as char); + } else { + result.push(sign); + write_hundreds(result, hours)?; + } + if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision { + if colons { + result.push(':'); + } + write_hundreds(result, mins)?; } - Colons::Double => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8)?; - result.push(':'); - write_hundreds(result, (off % 60) as u8) + if let OffsetPrecision::Seconds = precision { + if colons { + result.push(':'); + } + write_hundreds(result, secs)?; } - Colons::Triple => Ok(()), + Ok(()) } } @@ -530,7 +591,13 @@ pub(crate) fn write_rfc3339( // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. write!(result, "{:?}", dt)?; - write_local_minus_utc(result, off, false, Colons::Single) + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) } #[cfg(any(feature = "alloc", feature = "std"))] @@ -574,7 +641,13 @@ fn write_rfc2822_inner( let sec = t.second() + t.nanosecond() / 1_000_000_000; write_hundreds(result, sec as u8)?; result.push(' '); - write_local_minus_utc(result, off, false, Colons::None) + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) } /// Equivalent to `{:02}` formatting for n < 100. diff --git a/src/format/mod.rs b/src/format/mod.rs index 7d5dfb8702..e31c8a8f4f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -280,6 +280,51 @@ enum InternalInternal { Nanosecond9NoDot, } +/// Type for specifying the format of UTC offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct OffsetFormat { + /// See `OffsetPrecision`. + pub precision: OffsetPrecision, + /// Separator between hours, minutes and seconds. + pub colons: Colons, + /// Represent `+00:00` as `Z`. + pub allow_zulu: bool, + /// Pad the hour value to two digits. + pub padding: Pad, +} + +/// The precision of an offset from UTC formatting item. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum OffsetPrecision { + /// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to + /// have an offset of 30 minutes, 15 minutes, etc. + /// Any minutes and seconds get truncated. + Hours, + /// Format offset from UTC as hours and minutes. + /// Any seconds will be rounded to the nearest minute. + Minutes, + /// Format offset from UTC as hours, minutes and seconds. + Seconds, + /// Format offset from UTC as hours, and optionally with minutes. + /// Any seconds will be rounded to the nearest minute. + OptionalMinutes, + /// Format offset from UTC as hours and minutes, and optionally seconds. + OptionalSeconds, + /// Format offset from UTC as hours and optionally minutes and seconds. + OptionalMinutesAndSeconds, +} + +/// The separator between hours and minutes in an offset. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Colons { + /// No separator + None, + /// Colon (`:`) as separator + Colon, + /// No separator when formatting, colon allowed when parsing. + Maybe, +} + /// A single formatting item. This is used for both formatting and parsing. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { From 29019fbf8b88d1921bdce0884541955983703329 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 12:31:32 +0200 Subject: [PATCH 396/999] Test offset formatter --- src/format/formatting.rs | 160 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 3c2b479005..298b3e9f30 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -661,3 +661,163 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { w.write_char(tens as char)?; w.write_char(ones as char) } + +#[cfg(test)] +#[cfg(any(feature = "alloc", feature = "std"))] +mod tests { + use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; + use crate::FixedOffset; + + #[test] + fn test_offset_formatting() { + fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) { + fn check( + precision: OffsetPrecision, + colons: Colons, + padding: Pad, + allow_zulu: bool, + offsets: [FixedOffset; 7], + expected: [&str; 7], + ) { + let offset_format = OffsetFormat { precision, colons, allow_zulu, padding }; + for (offset, expected) in offsets.iter().zip(expected.iter()) { + let mut output = String::new(); + offset_format.format(&mut output, *offset).unwrap(); + assert_eq!(&output, expected); + } + } + // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00 + let offsets = [ + FixedOffset::east_opt(13_500).unwrap(), + FixedOffset::east_opt(-12_600).unwrap(), + FixedOffset::east_opt(39_600).unwrap(), + FixedOffset::east_opt(-39_622).unwrap(), + FixedOffset::east_opt(9266).unwrap(), + FixedOffset::east_opt(-45270).unwrap(), + FixedOffset::east_opt(0).unwrap(), + ]; + check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]); + check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]); + check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]); + check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]); + check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]); + check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]); + check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::None, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::None, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::None, Pad::None, false, offsets, expected[10]); + check(precision, Colons::None, Pad::None, true, offsets, expected[11]); + // `Colons::Maybe` should format the same as `Colons::None` + check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]); + check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]); + } + check_all( + OffsetPrecision::Hours, + [ + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ], + ); + check_all( + OffsetPrecision::Minutes, + [ + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"], + ], + ); + #[rustfmt::skip] + check_all( + OffsetPrecision::Seconds, + [ + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"], + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutes, + [ + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"], + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"], + [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"], + [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalSeconds, + [ + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutesAndSeconds, + [ + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"], + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"], + ], + ); + } +} From edf413d07e5c1a972e382e8ff9f42cf8aefd4d8a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 22:40:51 +0200 Subject: [PATCH 397/999] Rustfmt --- src/format/formatting.rs | 260 +++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 131 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 298b3e9f30..61cf4dc5e6 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -351,144 +351,142 @@ fn format_inner( Item::Fixed(ref spec) => { use self::Fixed::*; - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(locale.short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(locale.long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result.push_str( - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str( - locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; - for char in ampm.chars() { - result.extend(char.to_lowercase()) - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { - locale.am_pm[1] - } else { - locale.am_pm[0] - }); + let ret = match *spec { + ShortMonthName => date.map(|d| { + result.push_str(locale.short_months[d.month0() as usize]); + Ok(()) + }), + LongMonthName => date.map(|d| { + result.push_str(locale.long_months[d.month0() as usize]); + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + result.push_str( + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LongWeekdayName => date.map(|d| { + result.push_str( + locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) + } + Ok(()) + }), + UpperAmPm => time.map(|t| { + result.push_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }); + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else if nano % 1_000_000 == 0 { write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else if nano % 1_000 == 0 { write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else { write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|(name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, - } - .format(result, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, d, t, off, locale)) - } else { - None - } + }), + Nanosecond3 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:03}", nano / 1_000_000) + }), + Nanosecond6 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:06}", nano / 1_000) + }), + Nanosecond9 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:03}", nano / 1_000_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:06}", nano / 1_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:09}", nano) + }) + } + TimezoneName => off.map(|(name, _)| { + result.push_str(name); + Ok(()) + }), + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) - } else { - None - } + .format(result, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, } - }; + .format(result, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822_inner(result, d, t, off, locale)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) + } else { + None + } + } + }; match ret { Some(ret) => ret?, From 7cc0ca8c8b744620ef24c944b2a084655dad06b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 16:16:32 +0200 Subject: [PATCH 398/999] Require `clock` and `std` feature for `dateutils` tests --- tests/dateutils.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index de100e52fc..52181e8316 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -1,14 +1,8 @@ -#[cfg(unix)] -use chrono::offset::TimeZone; -#[cfg(unix)] -use chrono::Local; -#[cfg(unix)] -use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; - -#[cfg(unix)] +#![cfg(all(unix, feature = "clock", feature = "std"))] + +use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; use std::{path, process, thread}; -#[cfg(unix)] fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { let output = process::Command::new(path) .arg("-d") @@ -64,7 +58,6 @@ const DATE_PATH: &str = "/usr/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() { @@ -82,7 +75,6 @@ fn assert_run_date_version() { } #[test] -#[cfg(unix)] fn try_verify_against_date_command() { if !path::Path::new(DATE_PATH).exists() { eprintln!("date command {:?} not found, skipping", DATE_PATH); From bd23f5b437c3edf387cd9451668b3bb736ed46ff Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 16:16:46 +0200 Subject: [PATCH 399/999] Require `clock` feature for `wasm` tests --- tests/wasm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wasm.rs b/tests/wasm.rs index 3cc3dc0390..b534f0a315 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -8,6 +8,7 @@ #![cfg(all( target_arch = "wasm32", feature = "wasmbind", + feature = "clock", not(any(target_os = "emscripten", target_os = "wasi")) ))] From 771169016187d9d5f58624dbf0abd5a63cdca384 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 18:30:18 +0200 Subject: [PATCH 400/999] Also run integration tests with `no_std` on CI --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a485fbe99c..3e8fe52aa1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,8 +93,7 @@ jobs: # run using `bash` on all platforms for consistent # line-continuation marks shell: bash - - run: cargo test --lib --no-default-features - - run: cargo test --doc --no-default-features + - run: cargo test --no-default-features no_std: strategy: From 3b49eb6e438718282b598bc6a1c9c4c784026068 Mon Sep 17 00:00:00 2001 From: scarf Date: Thu, 9 Mar 2023 21:24:15 +0900 Subject: [PATCH 401/999] test: locale's datetime and 12 hour clock time `%c`: did not take account of `%x` and `%X` `%r`: did not use locale's format and used `%I:%M:%S %p` --- src/format/strftime.rs | 57 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index ab3b5e16b2..51d4fc0869 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -752,11 +752,58 @@ mod tests { } #[test] - #[cfg(not(feature = "unstable-locales"))] - fn test_type_sizes() { - use core::mem::size_of; - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 32); + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + fn test_strftime_localized_korean() { + 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(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월"); + assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일"); + assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일"); + assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일"); + assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001"); + assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ko_KR).to_string(), + "2001년 07월 08일 (일) 오전 12시 34분 60초" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + fn test_strftime_localized_japanese() { + 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(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月"); + assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日"); + assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日"); + assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日"); + assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001"); + assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒"); + + // date & time specifiers + assert_eq!(dt.format_localized("%c", Locale::ja_JP).to_string(), "2001年07月08日 00時34分60秒"); } #[test] From 3c50b428a9cb5d03d0259d7d5acfdd6f3b03bf26 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 4 Jul 2023 18:23:49 +0200 Subject: [PATCH 402/999] Update `pure-rust-locales` to 0.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5ba2eaabf..acc61634df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ time = { version = "0.1.43", optional = true } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.5.2", optional = true } +pure-rust-locales = { version = "0.6", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } From dfd3cf5e509b45d13ee42a3ced23658495a70cfa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 14:44:17 +0200 Subject: [PATCH 403/999] Make `%r` use locale's 12 hour clock time --- src/format/locales.rs | 4 ++++ src/format/strftime.rs | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/format/locales.rs b/src/format/locales.rs index d6fecc978b..7311d661b1 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -31,3 +31,7 @@ pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { pub(crate) const fn t_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT) } + +pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT_AMPM) +} diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 51d4fc0869..3a0d573710 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -65,7 +65,7 @@ The following specifiers are available both to formatting and parsing. | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). | -| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | +| `%r` | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. | | | | | | | | **TIME ZONE SPECIFIERS:** | | `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] | @@ -274,6 +274,15 @@ impl<'a> StrftimeItems<'a> { ]; static T_FMT: &[Item<'static>] = &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; + static T_FMT_AMPM: &[Item<'static>] = &[ + num0(Hour12), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + fixed(Fixed::UpperAmPm), + ]; match remainder.chars().next() { // we are done @@ -366,15 +375,19 @@ impl<'a> StrftimeItems<'a> { 'm' => num0(Month), 'n' => Space("\n"), 'p' => fixed(Fixed::UpperAmPm), - 'r' => queue![ - num0(Hour12), - Literal(":"), - num0(Minute), - Literal(":"), - num0(Second), - Space(" "), - fixed(Fixed::UpperAmPm) - ], + #[cfg(not(feature = "unstable-locales"))] + 'r' => queue_from_slice!(T_FMT_AMPM), + #[cfg(feature = "unstable-locales")] + 'r' => { + if self.locale.is_some() + && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() + { + // 12-hour clock not supported by this locale. Switch to 24-hour format. + self.switch_to_locale_str(locales::t_fmt, T_FMT) + } else { + self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) + } + } 's' => num(Timestamp), 't' => Space("\t"), 'u' => num(WeekdayFromMon), @@ -704,7 +717,7 @@ mod tests { 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 "); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60"); // date & time specifiers assert_eq!( @@ -803,7 +816,10 @@ mod tests { assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒"); // date & time specifiers - assert_eq!(dt.format_localized("%c", Locale::ja_JP).to_string(), "2001年07月08日 00時34分60秒"); + assert_eq!( + dt.format_localized("%c", Locale::ja_JP).to_string(), + "2001年07月08日 00時34分60秒" + ); } #[test] From ac802520f7f3457b2f93198bc42b799c0721c135 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 23:23:11 +0200 Subject: [PATCH 404/999] Clarify nanosecond formatting specifier doc --- src/format/strftime.rs | 50 +++++++++++------------------------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 3a0d573710..5ca7507043 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -53,14 +53,14 @@ The following specifiers are available both to formatting and parsing. | | | | | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | | `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | -| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] | -| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] | -| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] | -| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] | -| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] | -| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] | -| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] | -| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] | +| `%f` | `26490000` | Number of nanoseconds since last whole second. [^7] | +| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7] | +| `%.3f`| `.026` | Decimal fraction of a second with a fixed length of 3. | +| `%.6f`| `.026490` | Decimal fraction of a second with a fixed length of 6. | +| `%.9f`| `.026490000` | Decimal fraction of a second with a fixed length of 9. | +| `%3f` | `026` | Decimal fraction of a second like `%.3f` but without the leading dot. | +| `%6f` | `026490` | Decimal fraction of a second like `%.6f` but without the leading dot. | +| `%9f` | `026490000` | Decimal fraction of a second like `%.9f` but without the leading dot. | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | @@ -132,36 +132,12 @@ Notes: For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -[^7]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: +[^7]: `%f`, `%.f`:
- The default `%f` is right-aligned and always zero-padded to 9 digits - for the compatibility with glibc and others, - so it always counts the number of nanoseconds since the last whole second. - E.g. 7ms after the last second will print `007000000`, - and parsing `7000000` will yield the same. -
-
- The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits - according to the precision. - E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can print or read nothing if the fractional part is zero or - the next character is not `.`. -
-
- The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`. - E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero or - the next character is not `.` however will print with the specified length. -
-
- The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`, but without the leading dot. - E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), - and parsing `07`, `070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero. + `%f` and `%.f` are notably different formatting specifiers.
+ `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a + second.
+ Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`. [^8]: `%Z`: Since `chrono` is not aware of timezones beyond their offsets, this specifier From 26d03c9f50bf146ef29bf3ab950e64af5f70de0a Mon Sep 17 00:00:00 2001 From: Kevin Amado Date: Wed, 5 Jul 2023 14:21:20 -0600 Subject: [PATCH 405/999] const from_ymd_opt --- src/naive/date.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index d4d6b662e2..3acbc1fe03 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -285,9 +285,14 @@ impl NaiveDate { /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); /// ``` #[must_use] - pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { + pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { let flags = YearFlags::from_year(year); - NaiveDate::from_mdf(year, Mdf::new(month, day, flags)?) + + if let Some(mdf) = Mdf::new(month, day, flags) { + NaiveDate::from_mdf(year, mdf) + } else { + None + } } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) From ee3d61b17976aa852d0e09fbdcfe0b4160b662be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:32:37 +0200 Subject: [PATCH 406/999] Rust issue #22255 is no longer relevant --- src/format/parse.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 49742ce40c..4a1c4d6009 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -570,7 +570,6 @@ mod tests { use crate::format::Item::{Literal, Space}; use crate::format::Numeric::*; - // workaround for Rust issue #22255 fn parse_all(s: &str, items: &[Item]) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, items.iter())?; From 33c69f8d8a6dae1fbb3a8ce57e61f112f6d6ef26 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:45:59 +0200 Subject: [PATCH 407/999] Split up `test_parse` --- src/format/parse.rs | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 4a1c4d6009..8dfd9740c8 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -564,28 +564,26 @@ mod tests { use crate::format::*; use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - #[test] - fn test_parse() { - use crate::format::InternalInternal::*; - use crate::format::Item::{Literal, Space}; - use crate::format::Numeric::*; + fn parse_all(s: &str, items: &[Item]) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, items.iter())?; + Ok(parsed) + } - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } + macro_rules! check { + ($fmt:expr, $items:expr; $err:tt) => ( + assert_eq!(parse_all($fmt, &$items), Err($err)) + ); + ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + let mut expected = Parsed::new(); + $(expected.$k = Some($v);)* + assert_eq!(parse_all($fmt, &$items), Ok(expected)) + }); + } - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { - let mut expected = Parsed::new(); - $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) - }); - } + #[test] + fn test_parse_whitespace_and_literal() { + use crate::format::Item::{Literal, Space}; // empty string check!("", []; ); @@ -634,6 +632,12 @@ mod tests { check!("x y", [Literal("x"), Literal("y")]; INVALID); check!("xy", [Literal("x"), Space(""), Literal("y")]; ); check!("x y", [Literal("x"), Space(""), Literal("y")]; ); + } + + #[test] + fn test_parse_numeric() { + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; // numeric check!("1987", [num(Year)]; year: 1987); @@ -712,6 +716,12 @@ mod tests { [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); + } + + #[test] + fn test_parse_fixed() { + use crate::format::Fixed; + use crate::format::Item::Literal; // fixed: month and weekday names check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); @@ -762,6 +772,13 @@ mod tests { check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + } + + #[test] + fn test_parse_fixed_nanosecond() { + use crate::format::Fixed; + use crate::format::InternalInternal::*; + use crate::format::Numeric::*; // fixed: dot plus nanoseconds check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error @@ -817,8 +834,14 @@ mod tests { check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); + } - // fixed: timezone offsets + #[test] + fn test_parse_fixed_timezone_offset() { + use crate::format::Fixed; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + use crate::format::Numeric::*; // TimezoneOffset check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); @@ -1200,6 +1223,13 @@ mod tests { check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + } + + #[test] + fn test_parse_practical_examples() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; // some practical examples check!("2015-02-04T14:37:05+09:00", From 838ba6813ad85a825b8de88c277e33dda04d4111 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:53:24 +0200 Subject: [PATCH 408/999] Import `Fixed` variants --- src/format/parse.rs | 689 ++++++++++++++++++++++---------------------- 1 file changed, 344 insertions(+), 345 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 8dfd9740c8..1b6773fa8f 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -720,84 +720,84 @@ mod tests { #[test] fn test_parse_fixed() { - use crate::format::Fixed; + use crate::format::Fixed::*; use crate::format::Item::Literal; // fixed: month and weekday names - check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("Apr", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("APR", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("ApR", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("April", [fixed(Fixed::ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fixed(Fixed::ShortMonthName)]; TOO_SHORT); - check!("Sol", [fixed(Fixed::ShortMonthName)]; INVALID); - check!("Apr", [fixed(Fixed::LongMonthName)]; month: 4); - check!("Apri", [fixed(Fixed::LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fixed(Fixed::LongMonthName)]; month: 4); - check!("Aprill", [fixed(Fixed::LongMonthName)]; TOO_LONG); - check!("Aprill", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); - check!("Aprl", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); - check!("April", [fixed(Fixed::LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fixed(Fixed::ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fixed(Fixed::ShortWeekdayName)]; TOO_SHORT); - check!("The", [fixed(Fixed::ShortWeekdayName)]; INVALID); - check!("Nop", [fixed(Fixed::ShortWeekdayName)]; INVALID); - check!("Thu", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thus", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thursday", [fixed(Fixed::LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack + check!("apr", [fixed(ShortMonthName)]; month: 4); + check!("Apr", [fixed(ShortMonthName)]; month: 4); + check!("APR", [fixed(ShortMonthName)]; month: 4); + check!("ApR", [fixed(ShortMonthName)]; month: 4); + check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fixed(ShortMonthName)]; TOO_SHORT); + check!("Sol", [fixed(ShortMonthName)]; INVALID); + check!("Apr", [fixed(LongMonthName)]; month: 4); + check!("Apri", [fixed(LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fixed(LongMonthName)]; month: 4); + check!("Aprill", [fixed(LongMonthName)]; TOO_LONG); + check!("Aprill", [fixed(LongMonthName), Literal("l")]; month: 4); + check!("Aprl", [fixed(LongMonthName), Literal("l")]; month: 4); + check!("April", [fixed(LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fixed(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fixed(ShortWeekdayName)]; TOO_SHORT); + check!("The", [fixed(ShortWeekdayName)]; INVALID); + check!("Nop", [fixed(ShortWeekdayName)]; INVALID); + check!("Thu", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fixed(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fixed(LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fixed(LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thus", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thursday", [fixed(LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack // fixed: am/pm - check!("am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!("pm", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); - check!("AM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!("PM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); - check!("am", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); - check!("pm", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); - check!("AM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); - check!("PM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); - check!("Am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fixed(Fixed::LowerAmPm)]; INVALID); - check!("ame", [fixed(Fixed::LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("p", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); - check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!("pm", [fixed(LowerAmPm)]; hour_div_12: 1); + check!("AM", [fixed(LowerAmPm)]; hour_div_12: 0); + check!("PM", [fixed(LowerAmPm)]; hour_div_12: 1); + check!("am", [fixed(UpperAmPm)]; hour_div_12: 0); + check!("pm", [fixed(UpperAmPm)]; hour_div_12: 1); + check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); + check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); + check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fixed(LowerAmPm)]; INVALID); + check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fixed(LowerAmPm)]; TOO_SHORT); + check!("p", [fixed(LowerAmPm)]; TOO_SHORT); + check!("x", [fixed(LowerAmPm)]; TOO_SHORT); + check!("xx", [fixed(LowerAmPm)]; INVALID); + check!("", [fixed(LowerAmPm)]; TOO_SHORT); } #[test] fn test_parse_fixed_nanosecond() { - use crate::format::Fixed; + use crate::format::Fixed::Nanosecond; use crate::format::InternalInternal::*; - use crate::format::Numeric::*; + use crate::format::Numeric::Second; // fixed: dot plus nanoseconds - check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error - check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); - check!("4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fixed(Fixed::Nanosecond), num(Second)]; second: 4); - check!(".0", [fixed(Fixed::Nanosecond)]; nanosecond: 0); - check!(".4", [fixed(Fixed::Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fixed(Fixed::Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fixed(Fixed::Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fixed(Fixed::Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fixed(Fixed::Nanosecond)]; nanosecond: 0); - check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); - check!(".4x", [fixed(Fixed::Nanosecond)]; TOO_LONG); - check!(". 4", [fixed(Fixed::Nanosecond)]; INVALID); - check!(" .4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // no automatic trimming + check!("", [fixed(Nanosecond)]; ); // no field set, but not an error + check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!("4", [fixed(Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fixed(Nanosecond), num(Second)]; second: 4); + check!(".0", [fixed(Nanosecond)]; nanosecond: 0); + check!(".4", [fixed(Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); + check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!(".4x", [fixed(Nanosecond)]; TOO_LONG); + check!(". 4", [fixed(Nanosecond)]; INVALID); + check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); @@ -838,287 +838,286 @@ mod tests { #[test] fn test_parse_fixed_timezone_offset() { - use crate::format::Fixed; + use crate::format::Fixed::*; use crate::format::InternalInternal::*; use crate::format::Item::Literal; - use crate::format::Numeric::*; // TimezoneOffset - check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12345", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("−12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); - check!("-00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); - check!("−00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fixed(Fixed::TimezoneOffset)]; offset: 60); - check!("-00:01", [fixed(Fixed::TimezoneOffset)]; offset: -60); - check!("+00:30", [fixed(Fixed::TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fixed(Fixed::TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fixed(Fixed::TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); - check!("−24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fixed(Fixed::TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fixed(Fixed::TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!(" −12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffset), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("X12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("Z+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("X+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("X−12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🤠+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:🤠34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: 45_240); - check!("-1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); - check!("−1234🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: 45_240); - check!("-12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); - check!("−12:34🤠", [fixed(Fixed::TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🤠+12:34", [Literal("🤠"), fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("A", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1", [fixed(TimezoneOffset)]; INVALID); + check!("12", [fixed(TimezoneOffset)]; INVALID); + check!("123", [fixed(TimezoneOffset)]; INVALID); + check!("1234", [fixed(TimezoneOffset)]; INVALID); + check!("12345", [fixed(TimezoneOffset)]; INVALID); + check!("123456", [fixed(TimezoneOffset)]; INVALID); + check!("1234567", [fixed(TimezoneOffset)]; INVALID); + check!("+1", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("−12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fixed(TimezoneOffset)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffset)]; INVALID); + check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fixed(TimezoneOffset)]; offset: 0); + check!("-00:00", [fixed(TimezoneOffset)]; offset: 0); + check!("−00:00", [fixed(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fixed(TimezoneOffset)]; offset: 60); + check!("-00:01", [fixed(TimezoneOffset)]; offset: -60); + check!("+00:30", [fixed(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fixed(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fixed(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fixed(TimezoneOffset)]; offset: -86_400); + check!("−24:00", [fixed(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fixed(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fixed(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fixed(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fixed(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fixed(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fixed(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!(" −12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); + check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); + check!("", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffset), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(TimezoneOffset)]; INVALID); + check!("X12:34", [fixed(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("X+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("X−12:34", [fixed(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🤠+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("+12:34🤠", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:🤠34", [fixed(TimezoneOffset)]; INVALID); + check!("+1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: 45_240); + check!("-1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); + check!("−1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: 45_240); + check!("-12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); + check!("−12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🤠+12:34", [Literal("🤠"), fixed(TimezoneOffset)]; offset: 45_240); + check!("Z", [fixed(TimezoneOffset)]; INVALID); + check!("A", [fixed(TimezoneOffset)]; INVALID); + check!("PST", [fixed(TimezoneOffset)]; INVALID); + check!("#Z", [fixed(TimezoneOffset)]; INVALID); + check!(":Z", [fixed(TimezoneOffset)]; INVALID); + check!("+Z", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffset)]; INVALID); + check!("+Z:", [fixed(TimezoneOffset)]; INVALID); + check!("z", [fixed(TimezoneOffset)]; INVALID); + check!(" :Z", [fixed(TimezoneOffset)]; INVALID); + check!(" Z", [fixed(TimezoneOffset)]; INVALID); + check!(" z", [fixed(TimezoneOffset)]; INVALID); // TimezoneOffsetColon - check!("1", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12345678", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("−1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:3", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("−12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("−12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("−12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon), Literal(":")]; offset: 45_240); - check!("Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("A", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12", [fixed(TimezoneOffsetColon)]; INVALID); + check!("123", [fixed(TimezoneOffsetColon)]; INVALID); + check!("1234", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12345", [fixed(TimezoneOffsetColon)]; INVALID); + check!("123456", [fixed(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+1", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("−1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("−12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("−12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("−12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fixed(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffsetColon), Literal(":")]; offset: 45_240); + check!("Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("A", [fixed(TimezoneOffsetColon)]; INVALID); + check!("PST", [fixed(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" z", [fixed(TimezoneOffsetColon)]; INVALID); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12345678", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); - check!("−1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:3", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+12:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); - check!("−12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!("z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!(" z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Z ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(":z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+🙃", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Y", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("+1234ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); - check!("+12:34ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("1", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12", [fixed(TimezoneOffsetZ)]; INVALID); + check!("123", [fixed(TimezoneOffsetZ)]; INVALID); + check!("1234", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12345", [fixed(TimezoneOffsetZ)]; INVALID); + check!("123456", [fixed(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+1", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); + check!("−1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); + check!("−12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffsetZ), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!("z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fixed(TimezoneOffsetZ)]; INVALID); + check!("PST", [fixed(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(":z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+🙃", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Y", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("+1234ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("+12:34ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. @@ -1184,8 +1183,8 @@ mod tests { check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); check!(" −12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); check!("🤠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); check!("+12:34🤠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1215,14 +1214,14 @@ mod tests { check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); // TimezoneName - check!("CEST", [fixed(Fixed::TimezoneName)]; ); - check!("cest", [fixed(Fixed::TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fixed(Fixed::TimezoneName), Literal(" "), num(Day)]; day: 5); - check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); - check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); - check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!("CEST", [fixed(TimezoneName)]; ); + check!("cest", [fixed(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fixed(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fixed(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fixed(TimezoneName), Literal(" "), num(Numeric::Day)]; day: 5); + check!("CEST ", [fixed(TimezoneName)]; TOO_LONG); + check!(" CEST", [fixed(TimezoneName)]; TOO_LONG); + check!("CE ST", [fixed(TimezoneName)]; TOO_LONG); } #[test] From 823e98190dac4822d084e73d9469f97d78cd9469 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 15:55:34 +0200 Subject: [PATCH 409/999] Backport more checks in `test_parse` from main --- src/format/parse.rs | 146 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 6 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 1b6773fa8f..7483280298 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -589,15 +589,66 @@ mod tests { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); + check!("abc", []; TOO_LONG); + check!("🤠", []; TOO_LONG); // whitespaces check!("", [Space("")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); check!(" ", [Space("")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!("", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" "), Space(" ")]; ); check!("\t", [Space("")]; ); check!(" \n\r \n", [Space("")]; ); + check!("\t", [Space("\t")]; ); + check!("\t", [Space(" ")]; ); + check!(" ", [Space("\t")]; ); + check!("\t\r", [Space("\t\r")]; ); + check!("\t\r ", [Space("\t\r ")]; ); + check!("\t \r", [Space("\t \r")]; ); + check!(" \t\r", [Space(" \t\r")]; ); + check!(" \n\r \n", [Space(" \n\r \n")]; ); + check!(" \t\n", [Space(" \t")]; ); + check!(" \n\t", [Space(" \t\n")]; ); + check!("\u{2002}", [Space("\u{2002}")]; ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; + ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [ + Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") + ]; + ); check!("a", [Space("")]; TOO_LONG); + check!("a", [Space(" ")]; TOO_LONG); + // a Space containing a literal can match a literal, but this should not be done + check!("a", [Space("a")]; TOO_LONG); + check!("abc", [Space("")]; TOO_LONG); + check!("abc", [Space(" ")]; TOO_LONG); + check!(" abc", [Space("")]; TOO_LONG); + check!(" abc", [Space(" ")]; TOO_LONG); + + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal + check!("", [Literal("")]; ); check!("", [Literal("a")]; TOO_SHORT); check!(" ", [Literal("a")]; INVALID); check!("a", [Literal("a")]; ); @@ -607,7 +658,14 @@ mod tests { // a Literal may contain whitespace and match whitespace, but this should not be done check!(" ", [Literal(" ")]; ); check!("aa", [Literal("a")]; TOO_LONG); + check!("🤠", [Literal("a")]; INVALID); check!("A", [Literal("a")]; INVALID); + check!("a", [Literal("z")]; INVALID); + check!("a", [Literal("🤠")]; TOO_SHORT); + check!("a", [Literal("\u{0363}a")]; TOO_SHORT); + check!("\u{0363}a", [Literal("a")]; INVALID); + check!("\u{0363}a", [Literal("\u{0363}a")]; ); + check!("a", [Literal("ab")]; TOO_SHORT); check!("xy", [Literal("xy")]; ); check!("xy", [Literal("x"), Literal("y")]; ); check!("1", [Literal("1")]; ); @@ -632,6 +690,16 @@ mod tests { check!("x y", [Literal("x"), Literal("y")]; INVALID); check!("xy", [Literal("x"), Space(""), Literal("y")]; ); check!("x y", [Literal("x"), Space(""), Literal("y")]; ); + check!("x y", [Literal("x"), Space(" "), Literal("y")]; ); + + // whitespaces + literals + check!("a\n", [Literal("a"), Space("\n")]; ); + check!("\tab\n", [Space("\t"), Literal("ab"), Space("\n")]; ); + check!("ab\tcd\ne", [Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")]; ); + check!("+1ab\tcd\r\n+,.", [Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")]; ); + // whitespace and literals can be intermixed + check!("a\tb", [Literal("a\tb")]; ); + check!("a\tb", [Literal("a"), Space("\t"), Literal("b")]; ); } #[test] @@ -644,10 +712,14 @@ mod tests { check!("1987 ", [num(Year)]; TOO_LONG); check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed check!("x123", [num(Year)]; INVALID); + check!("o123", [num(Year)]; INVALID); check!("2015", [num(Year)]; year: 2015); check!("0000", [num(Year)]; year: 0); check!("9999", [num(Year)]; year: 9999); check!(" \t987", [num(Year)]; year: 987); + check!(" \t987", [Space(" \t"), num(Year)]; year: 987); + check!(" \t987🤠", [Space(" \t"), num(Year), Literal("🤠")]; year: 987); + check!("987🤠", [num(Year), Literal("🤠")]; year: 987); check!("5", [num(Year)]; year: 5); check!("5\0", [num(Year)]; TOO_LONG); check!("\x005", [num(Year)]; INVALID); @@ -657,11 +729,15 @@ mod tests { check!("12345", [num0(Year), Literal("5")]; year: 1234); check!("12341234", [num(Year), num(Year)]; year: 1234); check!("1234 1234", [num(Year), num(Year)]; year: 1234); + check!("1234 1234", [num(Year), Space(" "), num(Year)]; year: 1234); check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); - check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234xx1234", [num(Year), Literal("xx"), num(Year)]; year: 1234); + check!("1234 x 1234", [num(Year), Space(" "), Literal("x"), Space(" "), num(Year)]; year: 1234); + check!("1234 x 1235", [num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")]; year: 1234); // signed numeric check!("-42", [num(Year)]; year: -42); @@ -672,7 +748,11 @@ mod tests { check!("−42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) check!("+42195", [num(Year)]; year: 42195); check!(" -42195", [num(Year)]; year: -42195); + check!(" +42195", [num(Year)]; year: 42195); + check!(" -42195", [num(Year)]; year: -42195); check!(" +42195", [num(Year)]; year: 42195); + check!("-42195 ", [num(Year)]; TOO_LONG); + check!("+42195 ", [num(Year)]; TOO_LONG); check!(" - 42", [num(Year)]; INVALID); check!(" + 42", [num(Year)]; INVALID); check!(" -42195", [Space(" "), num(Year)]; year: -42195); @@ -697,8 +777,12 @@ mod tests { check!("\u{0363}345", [num(Ordinal)]; INVALID); check!(" +345", [num(Ordinal)]; INVALID); check!(" -345", [num(Ordinal)]; INVALID); + check!("\t345", [Space("\t"), num(Ordinal)]; ordinal: 345); + check!(" +345", [Space(" "), num(Ordinal)]; INVALID); + check!(" -345", [Space(" "), num(Ordinal)]; INVALID); // various numeric fields + check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); @@ -721,13 +805,14 @@ mod tests { #[test] fn test_parse_fixed() { use crate::format::Fixed::*; - use crate::format::Item::Literal; + use crate::format::Item::{Literal, Space}; // fixed: month and weekday names check!("apr", [fixed(ShortMonthName)]; month: 4); check!("Apr", [fixed(ShortMonthName)]; month: 4); check!("APR", [fixed(ShortMonthName)]; month: 4); check!("ApR", [fixed(ShortMonthName)]; month: 4); + check!("\u{0363}APR", [fixed(ShortMonthName)]; INVALID); check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed check!("A", [fixed(ShortMonthName)]; TOO_SHORT); check!("Sol", [fixed(ShortMonthName)]; INVALID); @@ -765,7 +850,15 @@ mod tests { check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [Space(" "), fixed(LowerAmPm)]; hour_div_12: 0); + check!("Am🤠", [fixed(LowerAmPm), Literal("🤠")]; hour_div_12: 0); + check!("🤠Am", [Literal("🤠"), fixed(LowerAmPm)]; hour_div_12: 0); + check!("\u{0363}am", [fixed(LowerAmPm)]; INVALID); + check!("\u{0360}am", [fixed(LowerAmPm)]; INVALID); check!(" Am", [fixed(LowerAmPm)]; INVALID); + check!("Am ", [fixed(LowerAmPm)]; TOO_LONG); + check!("a.m.", [fixed(LowerAmPm)]; INVALID); + check!("A.M.", [fixed(LowerAmPm)]; INVALID); check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fixed(LowerAmPm)]; TOO_SHORT); check!("p", [fixed(LowerAmPm)]; TOO_SHORT); @@ -778,6 +871,7 @@ mod tests { fn test_parse_fixed_nanosecond() { use crate::format::Fixed::Nanosecond; use crate::format::InternalInternal::*; + use crate::format::Item::Literal; use crate::format::Numeric::Second; // fixed: dot plus nanoseconds @@ -790,11 +884,21 @@ mod tests { check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); + check!(".421951", [fixed(Nanosecond)]; nanosecond: 421_951_000); + check!(".4219512", [fixed(Nanosecond)]; nanosecond: 421_951_200); + check!(".42195123", [fixed(Nanosecond)]; nanosecond: 421_951_230); check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".4219508035", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".42195080354", [fixed(Nanosecond)]; nanosecond: 421_950_803); check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000031", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000035", [fixed(Nanosecond)]; nanosecond: 3); check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000009", [fixed(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); - check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!(".0000000009999999999999999999999999", [fixed(Nanosecond)]; nanosecond: 0); + check!(".4🤠", [fixed(Nanosecond), Literal("🤠")]; nanosecond: 400_000_000); check!(".4x", [fixed(Nanosecond)]; TOO_LONG); check!(". 4", [fixed(Nanosecond)]; INVALID); check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming @@ -806,8 +910,12 @@ mod tests { check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("4210", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); + check!("421🤠", [internal_fixed(Nanosecond3NoDot), Literal("🤠")]; nanosecond: 421_000_000); + check!("🤠421", [Literal("🤠"), internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); + check!("123456789", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); @@ -815,10 +923,13 @@ mod tests { check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("1234", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("12345", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); + check!("1234567", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); + check!("123456789", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); @@ -826,10 +937,11 @@ mod tests { check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("12345678", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); + check!("1234567890", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); @@ -900,7 +1012,6 @@ mod tests { check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); check!(" −12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); @@ -912,6 +1023,7 @@ mod tests { check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); check!("", [fixed(TimezoneOffset)]; TOO_SHORT); check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); @@ -1257,6 +1369,12 @@ mod tests { num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", + [Literal("🤠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Literal("🤠"), num(Year), Space(" "), num(Hour), Literal(":"), + num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT"), Literal("🤠")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), @@ -1275,6 +1393,22 @@ mod tests { check!("12345678901234.56789", [num(Timestamp), fixed(Fixed::Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + + // docstring examples from `impl str::FromStr` + check!("2000-01-02T03:04:05Z", + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); + check!("2000-01-02 03:04:05Z", + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); } #[test] From 20e7dd5f1d2a6bf80c16bee73227ebd769dbb43b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 6 Jul 2023 10:05:25 +0200 Subject: [PATCH 410/999] Convert macro to function --- src/format/parse.rs | 1636 +++++++++++++++++++++++-------------------- 1 file changed, 893 insertions(+), 743 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 7483280298..746772af6c 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -564,20 +564,11 @@ mod tests { use crate::format::*; use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } - - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + macro_rules! parsed { + ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { let mut expected = Parsed::new(); $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) + Ok(expected) }); } @@ -586,120 +577,125 @@ mod tests { use crate::format::Item::{Literal, Space}; // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); - check!("abc", []; TOO_LONG); - check!("🤠", []; TOO_LONG); + parses("", &[]); + check(" ", &[], Err(TOO_LONG)); + check("a", &[], Err(TOO_LONG)); + check("abc", &[], Err(TOO_LONG)); + check("🤠", &[], Err(TOO_LONG)); // whitespaces - check!("", [Space("")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space("")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!("", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" "), Space(" ")]; ); - check!("\t", [Space("")]; ); - check!(" \n\r \n", [Space("")]; ); - check!("\t", [Space("\t")]; ); - check!("\t", [Space(" ")]; ); - check!(" ", [Space("\t")]; ); - check!("\t\r", [Space("\t\r")]; ); - check!("\t\r ", [Space("\t\r ")]; ); - check!("\t \r", [Space("\t \r")]; ); - check!(" \t\r", [Space(" \t\r")]; ); - check!(" \n\r \n", [Space(" \n\r \n")]; ); - check!(" \t\n", [Space(" \t")]; ); - check!(" \n\t", [Space(" \t\n")]; ); - check!("\u{2002}", [Space("\u{2002}")]; ); + parses("", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses("", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" "), Space(" ")]); + parses("\t", &[Space("")]); + parses(" \n\r \n", &[Space("")]); + parses("\t", &[Space("\t")]); + parses("\t", &[Space(" ")]); + parses(" ", &[Space("\t")]); + parses("\t\r", &[Space("\t\r")]); + parses("\t\r ", &[Space("\t\r ")]); + parses("\t \r", &[Space("\t \r")]); + parses(" \t\r", &[Space(" \t\r")]); + parses(" \n\r \n", &[Space(" \n\r \n")]); + parses(" \t\n", &[Space(" \t")]); + parses(" \n\t", &[Space(" \t\n")]); + parses("\u{2002}", &[Space("\u{2002}")]); // most unicode whitespace characters - check!( + parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; + &[Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")] ); // most unicode whitespace characters - check!( + parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [ + &[ Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") - ]; + ] ); - check!("a", [Space("")]; TOO_LONG); - check!("a", [Space(" ")]; TOO_LONG); - // a Space containing a literal can match a literal, but this should not be done - check!("a", [Space("a")]; TOO_LONG); - check!("abc", [Space("")]; TOO_LONG); - check!("abc", [Space(" ")]; TOO_LONG); - check!(" abc", [Space("")]; TOO_LONG); - check!(" abc", [Space(" ")]; TOO_LONG); + check("a", &[Space("")], Err(TOO_LONG)); + check("a", &[Space(" ")], Err(TOO_LONG)); + // a Space containing a literal does not match a literal + check("a", &[Space("a")], Err(TOO_LONG)); + check("abc", &[Space("")], Err(TOO_LONG)); + check("abc", &[Space(" ")], Err(TOO_LONG)); + check(" abc", &[Space("")], Err(TOO_LONG)); + check(" abc", &[Space(" ")], Err(TOO_LONG)); // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal - check!("", [Literal("")]; ); - check!("", [Literal("a")]; TOO_SHORT); - check!(" ", [Literal("a")]; INVALID); - check!("a", [Literal("a")]; ); - check!("+", [Literal("+")]; ); - check!("-", [Literal("-")]; ); - check!("−", [Literal("−")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [Literal(" ")]; ); - check!("aa", [Literal("a")]; TOO_LONG); - check!("🤠", [Literal("a")]; INVALID); - check!("A", [Literal("a")]; INVALID); - check!("a", [Literal("z")]; INVALID); - check!("a", [Literal("🤠")]; TOO_SHORT); - check!("a", [Literal("\u{0363}a")]; TOO_SHORT); - check!("\u{0363}a", [Literal("a")]; INVALID); - check!("\u{0363}a", [Literal("\u{0363}a")]; ); - check!("a", [Literal("ab")]; TOO_SHORT); - check!("xy", [Literal("xy")]; ); - check!("xy", [Literal("x"), Literal("y")]; ); - check!("1", [Literal("1")]; ); - check!("1234", [Literal("1234")]; ); - check!("+1234", [Literal("+1234")]; ); - check!("-1234", [Literal("-1234")]; ); - check!("−1234", [Literal("−1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [Literal("PST")]; ); - check!("🤠", [Literal("🤠")]; ); - check!("🤠a", [Literal("🤠"), Literal("a")]; ); - check!("🤠a🤠", [Literal("🤠"), Literal("a🤠")]; ); - check!("a🤠b", [Literal("a"), Literal("🤠"), Literal("b")]; ); + parses("", &[Literal("")]); + check("", &[Literal("a")], Err(TOO_SHORT)); + check(" ", &[Literal("a")], Err(INVALID)); + parses("a", &[Literal("a")]); + parses("+", &[Literal("+")]); + parses("-", &[Literal("-")]); + parses("−", &[Literal("−")]); // MINUS SIGN (U+2212) + parses(" ", &[Literal(" ")]); // a Literal may contain whitespace and match whitespace + check("aa", &[Literal("a")], Err(TOO_LONG)); + check("🤠", &[Literal("a")], Err(INVALID)); + check("A", &[Literal("a")], Err(INVALID)); + check("a", &[Literal("z")], Err(INVALID)); + check("a", &[Literal("🤠")], Err(TOO_SHORT)); + check("a", &[Literal("\u{0363}a")], Err(TOO_SHORT)); + check("\u{0363}a", &[Literal("a")], Err(INVALID)); + parses("\u{0363}a", &[Literal("\u{0363}a")]); + check("a", &[Literal("ab")], Err(TOO_SHORT)); + parses("xy", &[Literal("xy")]); + parses("xy", &[Literal("x"), Literal("y")]); + parses("1", &[Literal("1")]); + parses("1234", &[Literal("1234")]); + parses("+1234", &[Literal("+1234")]); + parses("-1234", &[Literal("-1234")]); + parses("−1234", &[Literal("−1234")]); // MINUS SIGN (U+2212) + parses("PST", &[Literal("PST")]); + parses("🤠", &[Literal("🤠")]); + parses("🤠a", &[Literal("🤠"), Literal("a")]); + parses("🤠a🤠", &[Literal("🤠"), Literal("a🤠")]); + parses("a🤠b", &[Literal("a"), Literal("🤠"), Literal("b")]); // literals can be together - check!("xy", [Literal("xy")]; ); - check!("xyz", [Literal("xyz")]; ); + parses("xy", &[Literal("xy")]); + parses("xyz", &[Literal("xyz")]); // or literals can be apart - check!("xy", [Literal("x"), Literal("y")]; ); - check!("xyz", [Literal("x"), Literal("yz")]; ); - check!("xyz", [Literal("xy"), Literal("z")]; ); - check!("xyz", [Literal("x"), Literal("y"), Literal("z")]; ); + parses("xy", &[Literal("x"), Literal("y")]); + parses("xyz", &[Literal("x"), Literal("yz")]); + parses("xyz", &[Literal("xy"), Literal("z")]); + parses("xyz", &[Literal("x"), Literal("y"), Literal("z")]); // - check!("x y", [Literal("x"), Literal("y")]; INVALID); - check!("xy", [Literal("x"), Space(""), Literal("y")]; ); - check!("x y", [Literal("x"), Space(""), Literal("y")]; ); - check!("x y", [Literal("x"), Space(" "), Literal("y")]; ); + check("x y", &[Literal("x"), Literal("y")], Err(INVALID)); + parses("xy", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(" "), Literal("y")]); // whitespaces + literals - check!("a\n", [Literal("a"), Space("\n")]; ); - check!("\tab\n", [Space("\t"), Literal("ab"), Space("\n")]; ); - check!("ab\tcd\ne", [Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")]; ); - check!("+1ab\tcd\r\n+,.", [Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")]; ); + parses("a\n", &[Literal("a"), Space("\n")]); + parses("\tab\n", &[Space("\t"), Literal("ab"), Space("\n")]); + parses( + "ab\tcd\ne", + &[Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")], + ); + parses( + "+1ab\tcd\r\n+,.", + &[Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")], + ); // whitespace and literals can be intermixed - check!("a\tb", [Literal("a\tb")]; ); - check!("a\tb", [Literal("a"), Space("\t"), Literal("b")]; ); + parses("a\tb", &[Literal("a\tb")]); + parses("a\tb", &[Literal("a"), Space("\t"), Literal("b")]); } #[test] @@ -708,98 +704,110 @@ mod tests { use crate::format::Numeric::*; // numeric - check!("1987", [num(Year)]; year: 1987); - check!("1987 ", [num(Year)]; TOO_LONG); - check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num(Year)]; INVALID); - check!("o123", [num(Year)]; INVALID); - check!("2015", [num(Year)]; year: 2015); - check!("0000", [num(Year)]; year: 0); - check!("9999", [num(Year)]; year: 9999); - check!(" \t987", [num(Year)]; year: 987); - check!(" \t987", [Space(" \t"), num(Year)]; year: 987); - check!(" \t987🤠", [Space(" \t"), num(Year), Literal("🤠")]; year: 987); - check!("987🤠", [num(Year), Literal("🤠")]; year: 987); - check!("5", [num(Year)]; year: 5); - check!("5\0", [num(Year)]; TOO_LONG); - check!("\x005", [num(Year)]; INVALID); - check!("", [num(Year)]; TOO_SHORT); - check!("12345", [num(Year), Literal("5")]; year: 1234); - check!("12345", [nums(Year), Literal("5")]; year: 1234); - check!("12345", [num0(Year), Literal("5")]; year: 1234); - check!("12341234", [num(Year), num(Year)]; year: 1234); - check!("1234 1234", [num(Year), num(Year)]; year: 1234); - check!("1234 1234", [num(Year), Space(" "), num(Year)]; year: 1234); - check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); - check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); - check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234xx1234", [num(Year), Literal("xx"), num(Year)]; year: 1234); - check!("1234 x 1234", [num(Year), Space(" "), Literal("x"), Space(" "), num(Year)]; year: 1234); - check!("1234 x 1235", [num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")]; year: 1234); + check("1987", &[num(Year)], parsed!(year: 1987)); + check("1987 ", &[num(Year)], Err(TOO_LONG)); + check("0x12", &[num(Year)], Err(TOO_LONG)); // `0` is parsed + check("x123", &[num(Year)], Err(INVALID)); + check("o123", &[num(Year)], Err(INVALID)); + check("2015", &[num(Year)], parsed!(year: 2015)); + check("0000", &[num(Year)], parsed!(year: 0)); + check("9999", &[num(Year)], parsed!(year: 9999)); + check(" \t987", &[num(Year)], parsed!(year: 987)); + check(" \t987", &[Space(" \t"), num(Year)], parsed!(year: 987)); + check(" \t987🤠", &[Space(" \t"), num(Year), Literal("🤠")], parsed!(year: 987)); + check("987🤠", &[num(Year), Literal("🤠")], parsed!(year: 987)); + check("5", &[num(Year)], parsed!(year: 5)); + check("5\0", &[num(Year)], Err(TOO_LONG)); + check("\x005", &[num(Year)], Err(INVALID)); + check("", &[num(Year)], Err(TOO_SHORT)); + check("12345", &[num(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[nums(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[num0(Year), Literal("5")], parsed!(year: 1234)); + check("12341234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), Space(" "), num(Year)], parsed!(year: 1234)); + check("1234 1235", &[num(Year), num(Year)], Err(IMPOSSIBLE)); + check("1234 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234x1234", &[num(Year), Literal("x"), num(Year)], parsed!(year: 1234)); + check("1234 x 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("xx"), num(Year)], parsed!(year: 1234)); + check( + "1234 x 1234", + &[num(Year), Space(" "), Literal("x"), Space(" "), num(Year)], + parsed!(year: 1234), + ); + check( + "1234 x 1235", + &[num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")], + parsed!(year: 1234), + ); // signed numeric - check!("-42", [num(Year)]; year: -42); - check!("+42", [num(Year)]; year: 42); - check!("-0042", [num(Year)]; year: -42); - check!("+0042", [num(Year)]; year: 42); - check!("-42195", [num(Year)]; year: -42195); - check!("−42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num(Year)]; year: 42195); - check!(" -42195", [num(Year)]; year: -42195); - check!(" +42195", [num(Year)]; year: 42195); - check!(" -42195", [num(Year)]; year: -42195); - check!(" +42195", [num(Year)]; year: 42195); - check!("-42195 ", [num(Year)]; TOO_LONG); - check!("+42195 ", [num(Year)]; TOO_LONG); - check!(" - 42", [num(Year)]; INVALID); - check!(" + 42", [num(Year)]; INVALID); - check!(" -42195", [Space(" "), num(Year)]; year: -42195); - check!(" −42195", [Space(" "), num(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [Space(" "), num(Year)]; year: 42195); - check!(" - 42", [Space(" "), num(Year)]; INVALID); - check!(" + 42", [Space(" "), num(Year)]; INVALID); - check!("-", [num(Year)]; TOO_SHORT); - check!("+", [num(Year)]; TOO_SHORT); + check("-42", &[num(Year)], parsed!(year: -42)); + check("+42", &[num(Year)], parsed!(year: 42)); + check("-0042", &[num(Year)], parsed!(year: -42)); + check("+0042", &[num(Year)], parsed!(year: 42)); + check("-42195", &[num(Year)], parsed!(year: -42195)); + check("−42195", &[num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check("+42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check("-42195 ", &[num(Year)], Err(TOO_LONG)); + check("+42195 ", &[num(Year)], Err(TOO_LONG)); + check(" - 42", &[num(Year)], Err(INVALID)); + check(" + 42", &[num(Year)], Err(INVALID)); + check(" -42195", &[Space(" "), num(Year)], parsed!(year: -42195)); + check(" −42195", &[Space(" "), num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check(" +42195", &[Space(" "), num(Year)], parsed!(year: 42195)); + check(" - 42", &[Space(" "), num(Year)], Err(INVALID)); + check(" + 42", &[Space(" "), num(Year)], Err(INVALID)); + check("-", &[num(Year)], Err(TOO_SHORT)); + check("+", &[num(Year)], Err(TOO_SHORT)); // unsigned numeric - check!("345", [num(Ordinal)]; ordinal: 345); - check!("+345", [num(Ordinal)]; INVALID); - check!("-345", [num(Ordinal)]; INVALID); - check!(" 345", [num(Ordinal)]; ordinal: 345); - check!("−345", [num(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num(Ordinal)]; TOO_LONG); - check!(" 345", [Space(" "), num(Ordinal)]; ordinal: 345); - check!("345 ", [num(Ordinal), Space(" ")]; ordinal: 345); - check!("345🤠 ", [num(Ordinal), Literal("🤠"), Space(" ")]; ordinal: 345); - check!("345🤠", [num(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num(Ordinal)]; INVALID); - check!(" +345", [num(Ordinal)]; INVALID); - check!(" -345", [num(Ordinal)]; INVALID); - check!("\t345", [Space("\t"), num(Ordinal)]; ordinal: 345); - check!(" +345", [Space(" "), num(Ordinal)]; INVALID); - check!(" -345", [Space(" "), num(Ordinal)]; INVALID); + check("345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("+345", &[num(Ordinal)], Err(INVALID)); + check("-345", &[num(Ordinal)], Err(INVALID)); + check(" 345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("−345", &[num(Ordinal)], Err(INVALID)); // MINUS SIGN (U+2212) + check("345 ", &[num(Ordinal)], Err(TOO_LONG)); + check(" 345", &[Space(" "), num(Ordinal)], parsed!(ordinal: 345)); + check("345 ", &[num(Ordinal), Space(" ")], parsed!(ordinal: 345)); + check("345🤠 ", &[num(Ordinal), Literal("🤠"), Space(" ")], parsed!(ordinal: 345)); + check("345🤠", &[num(Ordinal)], Err(TOO_LONG)); + check("\u{0363}345", &[num(Ordinal)], Err(INVALID)); + check(" +345", &[num(Ordinal)], Err(INVALID)); + check(" -345", &[num(Ordinal)], Err(INVALID)); + check("\t345", &[Space("\t"), num(Ordinal)], parsed!(ordinal: 345)); + check(" +345", &[Space(" "), num(Ordinal)], Err(INVALID)); + check(" -345", &[Space(" "), num(Ordinal)], Err(INVALID)); // various numeric fields - check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); - check!("1234 5678", - [num(Year), num(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num(Month), num(Day), num(WeekFromSun), num(WeekFromMon), num(IsoWeek), - num(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num(WeekdayFromMon), num(Ordinal), num(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check( + "12 34 56 78", + &[num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)], + parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), + ); + check( + "1 2 3 4 5", + &[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)], + parsed!(month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), + ); + check( + "6 7 89 01", + &[num(WeekFromMon), num(WeekdayFromMon), num(Ordinal), num(Hour12)], + parsed!(week_from_mon: 6, weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1), + ); + check( + "23 45 6 78901234 567890123", + &[num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)], + parsed!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123), + ); } #[test] @@ -808,63 +816,63 @@ mod tests { use crate::format::Item::{Literal, Space}; // fixed: month and weekday names - check!("apr", [fixed(ShortMonthName)]; month: 4); - check!("Apr", [fixed(ShortMonthName)]; month: 4); - check!("APR", [fixed(ShortMonthName)]; month: 4); - check!("ApR", [fixed(ShortMonthName)]; month: 4); - check!("\u{0363}APR", [fixed(ShortMonthName)]; INVALID); - check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fixed(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fixed(ShortMonthName)]; INVALID); - check!("Apr", [fixed(LongMonthName)]; month: 4); - check!("Apri", [fixed(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fixed(LongMonthName)]; month: 4); - check!("Aprill", [fixed(LongMonthName)]; TOO_LONG); - check!("Aprill", [fixed(LongMonthName), Literal("l")]; month: 4); - check!("Aprl", [fixed(LongMonthName), Literal("l")]; month: 4); - check!("April", [fixed(LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fixed(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fixed(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fixed(ShortWeekdayName)]; INVALID); - check!("Nop", [fixed(ShortWeekdayName)]; INVALID); - check!("Thu", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fixed(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fixed(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fixed(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thus", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thursday", [fixed(LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack + check("apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("Apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("APR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("ApR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("\u{0363}APR", &[fixed(ShortMonthName)], Err(INVALID)); + check("April", &[fixed(ShortMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("A", &[fixed(ShortMonthName)], Err(TOO_SHORT)); + check("Sol", &[fixed(ShortMonthName)], Err(INVALID)); + check("Apr", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Apri", &[fixed(LongMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("April", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Aprill", &[fixed(LongMonthName)], Err(TOO_LONG)); + check("Aprill", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("Aprl", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("April", &[fixed(LongMonthName), Literal("il")], Err(TOO_SHORT)); // do not backtrack + check("thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("THU", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("tHu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(ShortWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("T", &[fixed(ShortWeekdayName)], Err(TOO_SHORT)); + check("The", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Nop", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Thu", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thur", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thurs", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thursday", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursdays", &[fixed(LongWeekdayName)], Err(TOO_LONG)); + check("Thursdays", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thus", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(LongWeekdayName), Literal("rsday")], Err(TOO_SHORT)); // do not backtrack // fixed: am/pm - check!("am", [fixed(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fixed(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fixed(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fixed(LowerAmPm)]; hour_div_12: 1); - check!("am", [fixed(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fixed(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [Space(" "), fixed(LowerAmPm)]; hour_div_12: 0); - check!("Am🤠", [fixed(LowerAmPm), Literal("🤠")]; hour_div_12: 0); - check!("🤠Am", [Literal("🤠"), fixed(LowerAmPm)]; hour_div_12: 0); - check!("\u{0363}am", [fixed(LowerAmPm)]; INVALID); - check!("\u{0360}am", [fixed(LowerAmPm)]; INVALID); - check!(" Am", [fixed(LowerAmPm)]; INVALID); - check!("Am ", [fixed(LowerAmPm)]; TOO_LONG); - check!("a.m.", [fixed(LowerAmPm)]; INVALID); - check!("A.M.", [fixed(LowerAmPm)]; INVALID); - check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fixed(LowerAmPm)]; TOO_SHORT); - check!("p", [fixed(LowerAmPm)]; TOO_SHORT); - check!("x", [fixed(LowerAmPm)]; TOO_SHORT); - check!("xx", [fixed(LowerAmPm)]; INVALID); - check!("", [fixed(LowerAmPm)]; TOO_SHORT); + check("am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("am", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("Am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check(" Am", &[Space(" "), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("Am🤠", &[fixed(LowerAmPm), Literal("🤠")], parsed!(hour_div_12: 0)); + check("🤠Am", &[Literal("🤠"), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("\u{0363}am", &[fixed(LowerAmPm)], Err(INVALID)); + check("\u{0360}am", &[fixed(LowerAmPm)], Err(INVALID)); + check(" Am", &[fixed(LowerAmPm)], Err(INVALID)); + check("Am ", &[fixed(LowerAmPm)], Err(TOO_LONG)); + check("a.m.", &[fixed(LowerAmPm)], Err(INVALID)); + check("A.M.", &[fixed(LowerAmPm)], Err(INVALID)); + check("ame", &[fixed(LowerAmPm)], Err(TOO_LONG)); // `am` is parsed + check("a", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("p", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("x", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("xx", &[fixed(LowerAmPm)], Err(INVALID)); + check("", &[fixed(LowerAmPm)], Err(TOO_SHORT)); } #[test] @@ -875,77 +883,93 @@ mod tests { use crate::format::Numeric::Second; // fixed: dot plus nanoseconds - check!("", [fixed(Nanosecond)]; ); // no field set, but not an error - check!(".", [fixed(Nanosecond)]; TOO_SHORT); - check!("4", [fixed(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fixed(Nanosecond), num(Second)]; second: 4); - check!(".0", [fixed(Nanosecond)]; nanosecond: 0); - check!(".4", [fixed(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); - check!(".421951", [fixed(Nanosecond)]; nanosecond: 421_951_000); - check!(".4219512", [fixed(Nanosecond)]; nanosecond: 421_951_200); - check!(".42195123", [fixed(Nanosecond)]; nanosecond: 421_951_230); - check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".4219508035", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".42195080354", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000031", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000035", [fixed(Nanosecond)]; nanosecond: 3); - check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000009", [fixed(Nanosecond)]; nanosecond: 0); - check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); - check!(".0000000009999999999999999999999999", [fixed(Nanosecond)]; nanosecond: 0); - check!(".4🤠", [fixed(Nanosecond), Literal("🤠")]; nanosecond: 400_000_000); - check!(".4x", [fixed(Nanosecond)]; TOO_LONG); - check!(". 4", [fixed(Nanosecond)]; INVALID); - check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming + check("", &[fixed(Nanosecond)], parsed!()); // no field set, but not an error + check(".", &[fixed(Nanosecond)], Err(TOO_SHORT)); + check("4", &[fixed(Nanosecond)], Err(TOO_LONG)); // never consumes `4` + check("4", &[fixed(Nanosecond), num(Second)], parsed!(second: 4)); + check(".0", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4", &[fixed(Nanosecond)], parsed!(nanosecond: 400_000_000)); + check(".42", &[fixed(Nanosecond)], parsed!(nanosecond: 420_000_000)); + check(".421", &[fixed(Nanosecond)], parsed!(nanosecond: 421_000_000)); + check(".42195", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_000)); + check(".421951", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_000)); + check(".4219512", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_200)); + check(".42195123", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_230)); + check(".421950803", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".4219508035", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".42195080354", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".421950803547", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".000000003", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000031", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000035", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".000000003547", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000009", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".000000000547", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".0000000009999999999999999999999999", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4🤠", &[fixed(Nanosecond), Literal("🤠")], parsed!(nanosecond: 400_000_000)); + check(".4x", &[fixed(Nanosecond)], Err(TOO_LONG)); + check(". 4", &[fixed(Nanosecond)], Err(INVALID)); + check(" .4", &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("4210", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); - check!("421🤠", [internal_fixed(Nanosecond3NoDot), Literal("🤠")]; nanosecond: 421_000_000); - check!("🤠421", [Literal("🤠"), internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("123456789", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("1234", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("12345", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); - check!("1234567", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); - check!("123456789", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); - check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("12345678", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("1234567890", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check("", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("4", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("42", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("421", &[internal_fixed(Nanosecond3NoDot)], parsed!(nanosecond: 421_000_000)); + check("4210", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check( + "42143", + &[internal_fixed(Nanosecond3NoDot), num(Second)], + parsed!(nanosecond: 421_000_000, second: 43), + ); + check( + "421🤠", + &[internal_fixed(Nanosecond3NoDot), Literal("🤠")], + parsed!(nanosecond: 421_000_000), + ); + check( + "🤠421", + &[Literal("🤠"), internal_fixed(Nanosecond3NoDot)], + parsed!(nanosecond: 421_000_000), + ); + check("42195", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + check(".421", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("1234", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("12345", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("421950", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 421_950_000)); + check("000003", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 3000)); + check("000000", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 0)); + check("1234567", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + check(".42100", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("42195", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("12345678", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("421950803", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 421_950_803)); + check("000000003", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 3)); + check( + "42195080354", + &[internal_fixed(Nanosecond9NoDot), num(Second)], + parsed!(nanosecond: 421_950_803, second: 54), + ); // don't skip digits that come after the 9 + check("1234567890", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_LONG)); + check("000000000", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 0)); + check("00000000x", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(" 4", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(".42100000", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); } #[test] @@ -955,460 +979,586 @@ mod tests { use crate::format::Item::Literal; // TimezoneOffset - check!("1", [fixed(TimezoneOffset)]; INVALID); - check!("12", [fixed(TimezoneOffset)]; INVALID); - check!("123", [fixed(TimezoneOffset)]; INVALID); - check!("1234", [fixed(TimezoneOffset)]; INVALID); - check!("12345", [fixed(TimezoneOffset)]; INVALID); - check!("123456", [fixed(TimezoneOffset)]; INVALID); - check!("1234567", [fixed(TimezoneOffset)]; INVALID); - check!("+1", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("−12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fixed(TimezoneOffset)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffset)]; INVALID); - check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fixed(TimezoneOffset)]; offset: 0); - check!("-00:00", [fixed(TimezoneOffset)]; offset: 0); - check!("−00:00", [fixed(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fixed(TimezoneOffset)]; offset: 60); - check!("-00:01", [fixed(TimezoneOffset)]; offset: -60); - check!("+00:30", [fixed(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fixed(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fixed(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fixed(TimezoneOffset)]; offset: -86_400); - check!("−24:00", [fixed(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fixed(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fixed(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fixed(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fixed(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fixed(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fixed(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!(" −12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); - check!("", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffset), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(TimezoneOffset)]; INVALID); - check!("X12:34", [fixed(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("X+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("X−12:34", [fixed(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🤠+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("+12:34🤠", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:🤠34", [fixed(TimezoneOffset)]; INVALID); - check!("+1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: 45_240); - check!("-1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); - check!("−1234🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: 45_240); - check!("-12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); - check!("−12:34🤠", [fixed(TimezoneOffset), Literal("🤠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🤠+12:34", [Literal("🤠"), fixed(TimezoneOffset)]; offset: 45_240); - check!("Z", [fixed(TimezoneOffset)]; INVALID); - check!("A", [fixed(TimezoneOffset)]; INVALID); - check!("PST", [fixed(TimezoneOffset)]; INVALID); - check!("#Z", [fixed(TimezoneOffset)]; INVALID); - check!(":Z", [fixed(TimezoneOffset)]; INVALID); - check!("+Z", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffset)]; INVALID); - check!("+Z:", [fixed(TimezoneOffset)]; INVALID); - check!("z", [fixed(TimezoneOffset)]; INVALID); - check!(" :Z", [fixed(TimezoneOffset)]; INVALID); - check!(" Z", [fixed(TimezoneOffset)]; INVALID); - check!(" z", [fixed(TimezoneOffset)]; INVALID); + check("1", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12345", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("-00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("−00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); // MINUS SIGN (U+2212) + check("+00:01", &[fixed(TimezoneOffset)], parsed!(offset: 60)); + check("-00:01", &[fixed(TimezoneOffset)], parsed!(offset: -60)); + check("+00:30", &[fixed(TimezoneOffset)], parsed!(offset: 1_800)); + check("-00:30", &[fixed(TimezoneOffset)], parsed!(offset: -1_800)); + check("+24:00", &[fixed(TimezoneOffset)], parsed!(offset: 86_400)); + check("-24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); + check("−24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); // MINUS SIGN (U+2212) + check("+99:59", &[fixed(TimezoneOffset)], parsed!(offset: 359_940)); + check("-99:59", &[fixed(TimezoneOffset)], parsed!(offset: -359_940)); + check("+00:60", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("+00:99", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("#12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check(" −12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("\t -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("12:34 ", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check( + "+12345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffset), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("Z+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X−12:34", &[fixed(TimezoneOffset)], Err(INVALID)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34🤠", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:🤠34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[Literal("🤠"), fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("A", &[fixed(TimezoneOffset)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffset)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffset)], Err(INVALID)); + check("z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffset)], Err(INVALID)); // TimezoneOffsetColon - check!("1", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12", [fixed(TimezoneOffsetColon)]; INVALID); - check!("123", [fixed(TimezoneOffsetColon)]; INVALID); - check!("1234", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12345", [fixed(TimezoneOffsetColon)]; INVALID); - check!("123456", [fixed(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+1", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("−1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("−12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("−12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("−12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fixed(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffsetColon), Literal(":")]; offset: 45_240); - check!("Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("A", [fixed(TimezoneOffsetColon)]; INVALID); - check!("PST", [fixed(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" z", [fixed(TimezoneOffsetColon)]; INVALID); + check("1", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("−12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("#1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check(":", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check( + "+12345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetColon), Literal(":")], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("A", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12", [fixed(TimezoneOffsetZ)]; INVALID); - check!("123", [fixed(TimezoneOffsetZ)]; INVALID); - check!("1234", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12345", [fixed(TimezoneOffsetZ)]; INVALID); - check!("123456", [fixed(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+1", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); - check!("−1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); - check!("−12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffsetZ), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!("z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fixed(TimezoneOffsetZ)]; INVALID); - check!("PST", [fixed(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(":z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+🙃", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Y", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("+1234ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); - check!("+12:34ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check("1", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12::34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12:3456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check( + "+12345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetZ), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("X12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("\u{0363}Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("A", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("-Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+A", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+🙃", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" +Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check(" -Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Y", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("+1234ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + check("+12:34ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. // TimezoneOffsetPermissive - check!("1", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!("−12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" −12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); - check!("🤠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🤠34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🤠", [internal_fixed(TimezoneOffsetPermissive), Literal("🤠")]; offset: 45_240); - check!("🤠+12:34", [Literal("🤠"), internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+🙃", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check("1", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+123", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:7", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:78", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" 12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check(" +12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check(" -12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check(" −12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check( + "+12345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:34:", + &[internal_fixed(TimezoneOffsetPermissive), Literal(":")], + parsed!(offset: 45_240), + ); + check("🤠+12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34🤠", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:🤠34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check( + "+12:34🤠", + &[internal_fixed(TimezoneOffsetPermissive), Literal("🤠")], + parsed!(offset: 45_240), + ); + check( + "🤠+12:34", + &[Literal("🤠"), internal_fixed(TimezoneOffsetPermissive)], + parsed!(offset: 45_240), + ); + check("Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("A", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("Z ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("#Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("-Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+A", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+🙃", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" :Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" +Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check(" -Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+:Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("Y", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); // TimezoneName - check!("CEST", [fixed(TimezoneName)]; ); - check!("cest", [fixed(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fixed(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fixed(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fixed(TimezoneName), Literal(" "), num(Numeric::Day)]; day: 5); - check!("CEST ", [fixed(TimezoneName)]; TOO_LONG); - check!(" CEST", [fixed(TimezoneName)]; TOO_LONG); - check!("CE ST", [fixed(TimezoneName)]; TOO_LONG); + check("CEST", &[fixed(TimezoneName)], parsed!()); + check("cest", &[fixed(TimezoneName)], parsed!()); // lowercase + check("XXXXXXXX", &[fixed(TimezoneName)], parsed!()); // not a real timezone name + check("!!!!", &[fixed(TimezoneName)], parsed!()); // not a real timezone name! + check("CEST 5", &[fixed(TimezoneName), Literal(" "), num(Numeric::Day)], parsed!(day: 5)); + check("CEST ", &[fixed(TimezoneName)], Err(TOO_LONG)); + check(" CEST", &[fixed(TimezoneName)], Err(TOO_LONG)); + check("CE ST", &[fixed(TimezoneName)], Err(TOO_LONG)); } #[test] + #[rustfmt::skip] fn test_parse_practical_examples() { use crate::format::InternalInternal::*; use crate::format::Item::{Literal, Space}; use crate::format::Numeric::*; // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("2015-02-04T14:37:05-09:00", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("20150204143705567", - [num(Year), num(Month), num(Day), - num(Hour), num(Minute), num(Second), internal_fixed(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), - fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), Literal(":"), - num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", - [Literal("🤠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), - fixed(Fixed::ShortMonthName), Literal("🤠"), num(Year), Space(" "), num(Hour), Literal(":"), - num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT"), Literal("🤠")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), - num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), - num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), num(Year)]; + check( + "2015-02-04T14:37:05+09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: 32400 + ), + ); + check( + "2015-02-04T14:37:05-09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "20150204143705567", + &[ + num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second), + internal_fixed(Nanosecond3NoDot) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, nanosecond: 567000000 + ), + ); + check( + "Mon, 10 Jun 2013 09:32:37 GMT", + &[ + fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), + Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", + &[ + Literal("🤠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), + Space(" "), fixed(Fixed::ShortMonthName), Literal("🤠"), num(Year), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), + Literal("GMT"), Literal("🤠") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "Sun Aug 02 13:39:15 CEST 2020", + &[ + fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), + Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), + Literal(":"), num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), + num(Year) + ], + parsed!( year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num(Timestamp), Literal("."), num(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num(Timestamp), fixed(Fixed::Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15 + ), + ); + check( + "20060102150405", + &[num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)], + parsed!( + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5 + ), + ); + check( + "3:14PM", + &[num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)], + parsed!(hour_div_12: 1, hour_mod_12: 3, minute: 14), + ); + check( + "12345678901234.56789", + &[num(Timestamp), Literal("."), num(Nanosecond)], + parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234), + ); + check( + "12345678901234.56789", + &[num(Timestamp), fixed(Fixed::Nanosecond)], + parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234), + ); // docstring examples from `impl str::FromStr` - check!("2000-01-02T03:04:05Z", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), - internal_fixed(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); - check!("2000-01-02 03:04:05Z", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), - internal_fixed(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); + check( + "2000-01-02T03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + check( + "2000-01-02 03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + } + + #[track_caller] + fn parses(s: &str, items: &[Item]) { + let mut parsed = Parsed::new(); + assert!(parse(&mut parsed, s, items.iter()).is_ok()); + } + + #[track_caller] + fn check(s: &str, items: &[Item], expected: ParseResult) { + let mut parsed = Parsed::new(); + let result = parse(&mut parsed, s, items.iter()); + let parsed = result.map(|_| parsed); + assert_eq!(parsed, expected); } #[test] From 677dbbeb3ee229ab25428e813cd519ec99bc51a0 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:24:25 -0700 Subject: [PATCH 411/999] Revert changes to `TimeZoneName::new` parser --- src/offset/local/tz_info/timezone.rs | 115 ++++++--------------------- 1 file changed, 26 insertions(+), 89 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index ac7c3f2616..d2de060237 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,49 +486,29 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name - /// - /// Note: Converts `−` MINUS SIGN (U+2212) to `-` HYPHEN-MINUS (U+2D). - /// Multi-byte MINUS SIGN is allowed in [ISO 8601 / RFC 3339]. But - /// working with single-byte HYPHEN-MINUS is easier and more common. - /// - /// [ISO 8601 / RFC 3339]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn new(input: &[u8]) -> Result { - let s = match str::from_utf8(input) { - Ok(s) => s, - Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), - }; + let len = input.len(); - if !(3..=7).contains(&s.chars().count()) { + if !(3..=7).contains(&len) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); } let mut bytes = [0; 8]; - let mut copied = 0; - for (i, c) in s.chars().enumerate() { - match c { - '0'..='9' | 'A'..='Z' | 'a'..='z' - // ISO 8601 / RFC 3339 proscribes use of `+` PLUS SIGN (U+2B) - // in timezone - | '+' - // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) - // in timezone - | '-' => { - bytes[i + 1] = c as u8; - } - // ISO 8601 / RFC 3339 recommends the use of - // `−` MINUS SIGN (U+2212) in timezone. - // But replace with single-byte `-` HYPHEN-MINUS (U+2D) for - // easier byte <-> char conversions later on. - | '−' => { - bytes[i + 1] = b'-'; - } + bytes[0] = input.len() as u8; + + let mut i = 0; + while i < len { + let b = input[i]; + match b { + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } - copied += 1; + + bytes[i + 1] = b; + i += 1; } - bytes[0] = copied as u8; Ok(Self { bytes }) } @@ -761,63 +741,20 @@ mod tests { } #[test] - fn test_timezonename_new() -> Result<(), Error> { - // expect Error::LocalTimeType() - const INPUT_ERR: &[&str] = &[ - "", - "1", - "+", - "-", - "−", // MINUS SIGN (U+2212) - "12", - "--", - "−−", // MINUS SIGN (U+2212) - "AB", - "ab", - "12345678", - "ABCDEFGH", - "123456789", - "1234567890", - "--------", - "123\0\0\0", - "\0\0\0", - "\x00123", - "123\0", - ]; - for input_ in INPUT_ERR.iter() { - eprintln!("TimeZoneName::new({:?}) (expect Error::LocalTimeType)", input_); - let input_ = input_.as_bytes(); - let err = TimeZoneName::new(input_); - eprintln!("err = {:?}", err); - assert!(matches!(err, Err(Error::LocalTimeType(_)))); - } - // expect Ok - const INPUT_OK_EXPECT: &[(&str, &str)] = &[ - ("123", "123"), - ("abc", "abc"), - ("ABC", "ABC"), - ("1234", "1234"), - ("12345", "12345"), - ("123456", "123456"), - ("1234567", "1234567"), - ("+1234", "+1234"), - ("+1234", "+1234"), - ("-1234", "-1234"), - ("−1234", "-1234"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - // Ok nonsense - ("+++", "+++"), - ("-----", "-----"), - ("−−−", "---"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - ("−−−−−−−", "-------"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - ]; - for (input_, expect) in INPUT_OK_EXPECT.iter() { - eprintln!("TimeZoneName::new({:?})", input_); - let output = TimeZoneName::new(input_.as_bytes()); - match output { - Ok(output) => assert_eq!(output.as_bytes(), expect.as_bytes()), - Err(error) => panic!("Failed: input {:?}, error {}", input_, error), - } - } + fn test_tz_ascii_str() -> Result<(), Error> { + assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); + assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); + assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); + assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); + assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); + assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); + + assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); Ok(()) } From 8599d7c5ce4f3758bd47eacc279f32fc48a671ce Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 19:07:33 +0200 Subject: [PATCH 412/999] Add quote from man page --- src/offset/local/tz_info/timezone.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index d2de060237..43d7f42da6 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,6 +486,11 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name + /// + /// man tzfile(5): + /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII + /// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with + /// POSIX requirements for time zone abbreviations. fn new(input: &[u8]) -> Result { let len = input.len(); From 56c696766b6704ada56cc4ab763746b44a2d0bfa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 4 Jul 2023 12:25:45 +0200 Subject: [PATCH 413/999] Test some realistic values in `test_tz_ascii_str` --- src/offset/local/tz_info/timezone.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 43d7f42da6..36b2808105 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -748,18 +748,17 @@ mod tests { #[test] fn test_tz_ascii_str() -> Result<(), Error> { assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); - assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); - assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); - assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); - assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); - assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); + assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET"); + assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT"); + assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg"); + assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02"); + assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); + assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) + assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); - - assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_)))); Ok(()) } From e5f322e26ba74aaa10ab711a222b23322c3d3bd6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 15 Jul 2023 07:59:08 +0200 Subject: [PATCH 414/999] Remove `#![deny(dead_code)]` --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2eb1a010dc..6d02e6302d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -387,7 +387,6 @@ #![deny(missing_docs)] #![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)] // can remove this if/when rustc-serialize support is removed From 4c18df43aa6e182bed67c4967651738c30bef374 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 15 Jul 2023 07:42:42 +0200 Subject: [PATCH 415/999] Remove needless `any` in feature gate --- src/round.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/round.rs b/src/round.rs index b6e771e0f0..69ecef9e8b 100644 --- a/src/round.rs +++ b/src/round.rs @@ -100,11 +100,11 @@ const fn span_for_digits(digits: u16) -> u32 { /// will also fail if the `Duration` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std"))] + #[cfg(feature = "std")] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std")))] + #[cfg(not(feature = "std"))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by Duration. @@ -299,7 +299,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std"))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] From c50e5857dac93493d9606300d2a7c5a353c7b3be Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Wed, 14 Dec 2022 18:20:30 +0100 Subject: [PATCH 416/999] Adding automatic dependency management --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5cd6ac397c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" From f8029b5f358e4f46e9e96c48d3641e4dfeaa7997 Mon Sep 17 00:00:00 2001 From: "Oleg V. Kozlyuk" Date: Thu, 15 Dec 2022 22:57:58 +0300 Subject: [PATCH 417/999] Update per review notes --- .github/dependabot.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5cd6ac397c..55047562e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,12 @@ updates: - package-ecosystem: cargo directory: "/" schedule: - interval: "daily" + interval: "weekly" + - package-ecosystem: cargo + directory: "/fuzz/" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" From f4b3ebad63467246c4f74834e1c1f036249d3254 Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:33:11 +0100 Subject: [PATCH 418/999] Wrap all Dependabot ecosystem names in quotes No functional change but makes this file self-consistent and consistent with the Dependabot docs. --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 55047562e7..0f67083a2f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,10 @@ version: 2 updates: - - package-ecosystem: cargo + - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" - - package-ecosystem: cargo + - package-ecosystem: "cargo" directory: "/fuzz/" schedule: interval: "weekly" From 13e7924767b41fca3c00be49d931bc469d58f102 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 29 Aug 2022 10:54:27 +0200 Subject: [PATCH 419/999] Add branch policy explanation to PR template --- .github/pull_request_template.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ba92e741d0..950451f3e0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,6 @@ ### Thanks for contributing to chrono! +If your feature is semver-compatible, please target the 0.4.x branch; +the main branch will be used for 0.5.0 development going forward. + Please consider adding a test to ensure your bug fix/feature will not break in the future. From c6a75156ba0666b18f02dd6719bee3d499328b5a Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:25:18 -0700 Subject: [PATCH 420/999] Add various tests for current parsing Add more varying testing for most parsing functions. Tests emphasize whitespace, literals, timezones, and timezone delimiters (colons and whitespace). Add tests for multiple-byte characters and combining characters in and around data and parsing formats. These tests are added to aid humans verifying the next commit that changes parsing behavior. Issue #660 --- src/datetime/mod.rs | 5 + src/datetime/tests.rs | 898 +++++++++++++++++++++++++++++++++++- src/format/strftime.rs | 107 +++++ src/naive/date.rs | 33 +- src/naive/datetime/tests.rs | 45 +- src/naive/time/tests.rs | 70 ++- 6 files changed, 1128 insertions(+), 30 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8686340e16..24f67298cf 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -700,6 +700,9 @@ impl DateTime { /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP /// and email headers. /// + /// The RFC 2822 standard allows arbitrary intermixed whitespace. + /// See [RFC 2822 Appendix A.5] + /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( @@ -707,6 +710,8 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` + /// + /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2d273f605d..b8338267f2 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -357,8 +357,10 @@ fn test_datetime_with_timezone() { #[test] #[cfg(any(feature = "alloc", feature = "std"))] -fn test_datetime_rfc2822_and_rfc3339() { +fn test_datetime_rfc2822() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 assert_eq!( Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" @@ -367,6 +369,7 @@ fn test_datetime_rfc2822_and_rfc3339() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -389,6 +392,7 @@ fn test_datetime_rfc2822_and_rfc3339() { .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + // seconds 60 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -421,7 +425,131 @@ fn test_datetime_rfc2822_and_rfc3339() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + ), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap() + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap()) + ); + + // bad year + assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() + ); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") + .is_err()); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") + .is_err()); + // wrong day of week separator '.' + assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() + ); +} + +#[test] +#[cfg(any(feature = "alloc", feature = "std"))] +fn test_datetime_rfc3339() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) + ); + + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( @@ -447,6 +575,44 @@ fn test_datetime_rfc2822_and_rfc3339() { ) .unwrap()) ); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() + ); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() + ); } #[test] @@ -590,7 +756,94 @@ fn test_datetime_from_str() { } #[test] -fn test_datetime_parse_from_str() { +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {:?}", s); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12t12:12:12Z", // wrong divider 't' + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {:?}", s); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_utc_datetime_from_str() { let ymdhms = |y, m, d, h, n, s, off| { FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() }; @@ -605,6 +858,645 @@ fn test_datetime_parse_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!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_utc_datetime_from_str_with_spaces() { + let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!( + Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), + Ok(dt), + ); + assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + // with varying spaces - should fail + // leading space in data + assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = FixedOffset::east_opt(-9 * 60 * 60) + .unwrap() + .ymd_opt(2013, 8, 9) + .unwrap() + .and_hms_opt(23, 54, 35) + .unwrap(); + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data? + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 PST", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 XXXXX", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + + // + // %z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 --0900", + "%b %d %Y %H:%M:%S -%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 +-0900", + "%b %d %Y %H:%M:%S +%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing newline after timezone + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing colon + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + // trailing colon with space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00: ", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // trailing colon, mismatch space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900::", + "%b %d %Y %H:%M:%S %z::" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %z:00" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %z:00 " + ), + Ok(dt), + ); + + // + // %:z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00:", + "%b %d %Y %H:%M:%S %:z:" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z::" + ), + Ok(dt), + ); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // mismatching colon expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %::z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900\t\n", + "%b %d %Y %H:%M:%S %::z\t\n" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900:", + "%b %d %Y %H:%M:%S %::z:" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900:0", + "%b %d %Y %H:%M:%S :%::z:0" + ), + Ok(dt), + ); + // mismatching colons and spaces + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900: ", + "%b %d %Y %H:%M:%S :%::z::" + ) + .is_err()); + // mismatching colons expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900: 23:54:35", + "%b %d %Y %::z: %H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 :-0900:0 23:54:35", + "%b %d %Y :%::z:0 %H:%M:%S" + ), + Ok(dt), + ); + // mismatching colons expectations mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 :-0900: 23:54:35", + "%b %d %Y :%::z %H:%M:%S" + ) + .is_err()); + // mismatching colons expectations, before end + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %::z " + ) + .is_err()); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %:::z " + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:::z" + ) + .is_err()); + + // + // %::::z + // + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + + // + // %#z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09: ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35+-09", + "%b %d %Y %H:%M:%S+%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35--09", + "%b %d %Y %H:%M:%S-%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -090023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:0023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + // timezone with partial minutes adjacent hours + assert_ne!( + DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), + Ok(dt), + ); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090 ", + "%b %d %Y %H:%M:%S %#z " + ) + .is_err()); + // bad timezone data (partial minutes) mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -090 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:00 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // timezone data ambiguous with hours + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); assert_eq!( DateTime::parse_from_str("0", "%s").unwrap(), NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 5ca7507043..50d4ac7cc3 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -508,12 +508,32 @@ mod tests { 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(" "), [Space(" ")]); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect("a"), [Literal("a")]); + assert_eq!(parse_and_collect("ab"), [Literal("ab")]); + assert_eq!(parse_and_collect("😽"), [Literal("😽")]); + assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]); + assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]); + assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); assert_eq!( @@ -530,12 +550,88 @@ mod tests { parse_and_collect("%Y-%m-%d"), [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] ); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]); + assert_eq!( + parse_and_collect("😽😽a b😽c"), + [Literal("😽😽a"), Space(" "), Literal("b😽c")] + ); + assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽"), + [Space(" "), Literal("😽"), Space(" "), Literal("😽")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽はい😽 ハンバーガー"), + [ + Space(" "), + Literal("😽"), + Space(" "), + Literal("😽はい😽"), + Space(" "), + Literal("ハンバーガー") + ] + ); + assert_eq!( + parse_and_collect("%%😽%%😽"), + [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")] + ); + assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]); assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); assert_eq!(parse_and_collect("%%"), [Literal("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("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("%%%%"), [Literal("%"), Literal("%")]); + assert_eq!( + parse_and_collect("%%%%ハンバーガー"), + [Literal("%"), Literal("%"), Literal("ハンバーガー")] + ); 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]); @@ -555,6 +651,10 @@ mod tests { assert_eq!(parse_and_collect("%0e"), [num0(Day)]); assert_eq!(parse_and_collect("%_e"), [nums(Day)]); assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]); assert_eq!( parse_and_collect("%#z"), [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] @@ -664,6 +764,13 @@ mod tests { assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); + + // 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" + ); } #[test] diff --git a/src/naive/date.rs b/src/naive/date.rs index 3acbc1fe03..1f7838847b 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2967,16 +2967,20 @@ mod tests { "360-02-29", "0360-02-29", "2015-2 -18", + "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; + eprintln!("d {:?} (NaiveDate)", d); let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, @@ -2984,6 +2988,7 @@ mod tests { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; + eprintln!("d_ {:?} (NaiveDate)", d_); assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ @@ -2996,13 +3001,27 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 4656147d64..b16f68b9ec 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -199,11 +199,16 @@ fn test_datetime_timestamp() { fn test_datetime_from_str() { // valid cases let valid = [ - "2015-2-18T23:16:9.15", + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { + eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -228,16 +233,34 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 72491fbd2c..3eb83ba167 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -235,9 +235,37 @@ fn test_date_from_str() { " 4 : 3 : 2.1 ", " 09:08:07 ", " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", "23:59:60.373929310237", ]; for &s in &valid { + eprintln!("test_time_parse_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -262,15 +290,30 @@ fn test_date_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8", // missing data + "15:8:x", // missing data, invalid data + "15:8:9x", // missing data, invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] @@ -281,6 +324,15 @@ fn test_time_parse_from_str() { Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } From 90511c8840370f1976be9167c0cae44cc9ec3bba Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 17:37:05 +0200 Subject: [PATCH 421/999] Backport various changes to datetime/tests.rs --- src/datetime/tests.rs | 928 +++++++++++++----------------------------- 1 file changed, 273 insertions(+), 655 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b8338267f2..24101039d1 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -238,6 +238,100 @@ fn test_datetime_sub_months() { ); } +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(any(feature = "alloc", feature = "std"))] +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::microseconds(micro)) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(any(feature = "alloc", feature = "std"))] +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::nanoseconds(nano)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -365,10 +459,6 @@ fn test_datetime_rfc2822() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - assert_eq!( - Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); // timezone +05 assert_eq!( edt.from_local_datetime( @@ -381,17 +471,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); // seconds 60 assert_eq!( edt.from_local_datetime( @@ -404,17 +483,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), @@ -425,136 +493,130 @@ fn test_datetime_rfc2822() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" + ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), + "Thu, 19 Feb 2015 00:20:32 +0500" ); assert_eq!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) ); // many varying whitespace intermixed assert_eq!( - DateTime::::parse_from_rfc2822( - "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + DateTime::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" ), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) ); // example from RFC 2822 Appendix A.5. assert_eq!( - DateTime::::parse_from_rfc2822( + DateTime::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap() + Ok( + ymdhms( + &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), + 1969, 2, 13, 23, 32, 0, + ) ) ); // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" assert_eq!( - DateTime::::parse_from_rfc2822( + DateTime::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap()) + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) ); // bad year - assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); // wrong format - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() - ); + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err()); // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") - .is_err()); + assert!(DateTime::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000").is_err()); // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") - .is_err()); + assert!(DateTime::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000").is_err()); // wrong day of week separator '.' - assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); // *trailing* space causes failure - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() - ); + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err()); } #[test] #[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_rfc3339() { - let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -566,7 +628,7 @@ fn test_datetime_rfc3339() { assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -575,44 +637,21 @@ fn test_datetime_rfc3339() { ) .unwrap()) ); - assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() - ); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() - ); + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err()); + assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err()); } #[test] @@ -844,12 +883,12 @@ fn test_parse_datetime_utc() { #[test] fn test_utc_datetime_from_str() { - let ymdhms = |y, m, d, h, n, s, off| { - FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() - }; + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); assert_eq!( DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") @@ -858,56 +897,45 @@ 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::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west_opt(10 * 3600) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(13, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -916,7 +944,7 @@ fn test_utc_datetime_from_str() { #[test] fn test_utc_datetime_from_str_with_spaces() { - let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); // with varying spaces - should succeed assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); @@ -952,12 +980,8 @@ fn test_utc_datetime_from_str_with_spaces() { #[test] fn test_datetime_parse_from_str() { - let dt = FixedOffset::east_opt(-9 * 60 * 60) - .unwrap() - .ymd_opt(2013, 8, 9) - .unwrap() - .and_hms_opt(23, 54, 35) - .unwrap(); + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); + let parse = DateTime::parse_from_str; // timezone variations @@ -965,542 +989,136 @@ fn test_datetime_parse_from_str() { // %Z // // wrong timezone format - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data? - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 PST", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 XXXXX", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); // // %z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 --0900", - "%b %d %Y %H:%M:%S -%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 +-0900", - "%b %d %Y %H:%M:%S +%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing newline after timezone - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing colon - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); // trailing colon with space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00: ", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); // trailing colon, mismatch space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900::", - "%b %d %Y %H:%M:%S %z::" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %z:00" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %z:00 " - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); // // %:z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00:", - "%b %d %Y %H:%M:%S %:z:" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:"), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z::" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); // - // %:::z + // %::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); // mismatching colon expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %::z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900\t\n", - "%b %d %Y %H:%M:%S %::z\t\n" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900:", - "%b %d %Y %H:%M:%S %::z:" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900:0", - "%b %d %Y %H:%M:%S :%::z:0" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); // mismatching colons and spaces - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900: ", - "%b %d %Y %H:%M:%S :%::z::" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); // mismatching colons expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900: 23:54:35", - "%b %d %Y %::z: %H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 :-0900:0 23:54:35", - "%b %d %Y :%::z:0 %H:%M:%S" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); // mismatching colons expectations mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 :-0900: 23:54:35", - "%b %d %Y :%::z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); // mismatching colons expectations, before end - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %::z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); // // %:::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %:::z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); // // %::::z // // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // // %#z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09: ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35+-09", - "%b %d %Y %H:%M:%S+%#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35--09", - "%b %d %Y %H:%M:%S-%#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:00 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -090023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:0023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // timezone with partial minutes adjacent hours - assert_ne!( - DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), - Ok(dt), - ); + assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) with trailing space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090 ", - "%b %d %Y %H:%M:%S %#z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); // bad timezone data (partial minutes) mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -090 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:00 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // timezone data ambiguous with hours - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:23:54:35", - "%b %d %Y %#z%H:%M:%S" - ) - .is_err()); - assert_eq!( - DateTime::parse_from_str("0", "%s").unwrap(), - NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() - ); + assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); } #[test] From 3993c0273466ece5e70cb9c62fba5128d481016c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 17:08:37 +0200 Subject: [PATCH 422/999] Make imports similar to main branch --- src/datetime/mod.rs | 3 +-- src/format/parsed.rs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 24f67298cf..5ff141d3c2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -31,8 +31,7 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; #[allow(deprecated)] use crate::Date; -use crate::Months; -use crate::{Datelike, Timelike, Weekday}; +use crate::{Datelike, Months, Timelike, Weekday}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 809a5ae782..431833ec7d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -8,9 +8,7 @@ use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::oldtime::Duration as OldDuration; -use crate::DateTime; -use crate::Weekday; -use crate::{Datelike, Timelike}; +use crate::{DateTime, Datelike, Timelike, Weekday}; /// Parsed parts of date and time. There are two classes of methods: /// From 34003e3be980473af50b90f117d680ce19fc7785 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Jul 2023 07:40:51 +0200 Subject: [PATCH 423/999] [CI] Test more `no_std` combinations --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e8fe52aa1..c9c3e572a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,6 +94,9 @@ jobs: # line-continuation marks shell: bash - run: cargo test --no-default-features + - run: cargo test --no-default-features --features=alloc + - run: cargo test --no-default-features --features=unstable-locales + - run: cargo test --no-default-features --features=alloc,unstable-locales no_std: strategy: From 0c39758474d1d78001dcda4a96f75a6915082284 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 19:46:43 +0200 Subject: [PATCH 424/999] Use feature gates in src/oldtime.rs --- src/lib.rs | 4 +++- src/oldtime.rs | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6d02e6302d..beb42f86dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -400,7 +400,9 @@ extern crate time as oldtime; #[cfg(not(feature = "oldtime"))] mod oldtime; // this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::{Duration, OutOfRangeError}; +pub use oldtime::Duration; +#[cfg(feature = "std")] +pub use oldtime::OutOfRangeError; use core::fmt; diff --git a/src/oldtime.rs b/src/oldtime.rs index e27be7db63..53293aa0e1 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -11,9 +11,10 @@ //! Temporal quantification use core::ops::{Add, Div, Mul, Neg, Sub}; +#[cfg(feature = "std")] use core::time::Duration as StdDuration; use core::{fmt, i64}; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "rkyv")] @@ -291,6 +292,7 @@ impl Duration { /// /// This function errors when original duration is larger than the maximum /// value supported for this type. + #[cfg(feature = "std")] pub fn from_std(duration: StdDuration) -> Result { // We need to check secs as u64 before coercing to i64 if duration.as_secs() > MAX.secs as u64 { @@ -307,6 +309,7 @@ impl Duration { /// /// This function errors when duration is less than zero. As standard /// library implementation is limited to non-negative values. + #[cfg(feature = "std")] pub fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); @@ -388,17 +391,13 @@ impl Div for Duration { } } -#[cfg(any(feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl<'a> std::iter::Sum<&'a Duration> for Duration { +impl<'a> core::iter::Sum<&'a Duration> for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + *x) } } -#[cfg(any(feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::iter::Sum for Duration { +impl core::iter::Sum for Duration { fn sum>(iter: I) -> Duration { iter.fold(Duration::zero(), |acc, x| acc + x) } @@ -444,16 +443,18 @@ impl fmt::Display for Duration { /// The `std::time::Duration` supports a range from zero to `u64::MAX` /// *seconds*, while this module supports signed range of up to /// `i64::MAX` of *milliseconds*. +#[cfg(feature = "std")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OutOfRangeError(()); +#[cfg(feature = "std")] impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Source duration value is out of range for the target type") } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for OutOfRangeError { #[allow(deprecated)] @@ -487,7 +488,10 @@ impl arbitrary::Arbitrary<'_> for Duration { #[cfg(test)] mod tests { - use super::{Duration, OutOfRangeError, MAX, MIN}; + #[cfg(feature = "std")] + use super::OutOfRangeError; + use super::{Duration, MAX, MIN}; + #[cfg(feature = "std")] use std::time::Duration as StdDuration; use std::{i32, i64}; @@ -713,6 +717,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn test_to_std() { assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); @@ -725,6 +730,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn test_from_std() { assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0))); From 02df954a6c8384a6d406b9d1a5565634bdd76dba Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Jul 2023 12:25:35 +0200 Subject: [PATCH 425/999] Don't import integer types --- src/naive/date.rs | 1 - src/naive/datetime/tests.rs | 1 - src/naive/internals.rs | 4 +--- src/naive/time/tests.rs | 1 - src/oldtime.rs | 1 - 5 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 1f7838847b..88b871b849 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2346,7 +2346,6 @@ mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; use crate::oldtime::Duration; 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. diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index b16f68b9ec..be1bdf52f4 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -2,7 +2,6 @@ use super::NaiveDateTime; use crate::oldtime::Duration; use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; -use std::i64; #[test] fn test_datetime_from_timestamp_millis() { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 65b47ae73b..910419f387 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -16,7 +16,7 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] use crate::Weekday; -use core::{fmt, i32}; +use core::fmt; /// The internal date representation: `year << 13 | Of` pub(super) type DateImpl = i32; @@ -504,8 +504,6 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday { #[cfg(test)] mod tests { - use std::u32; - use super::weekday_from_u32_mod7; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 3eb83ba167..28ffcb4ef6 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,7 +1,6 @@ use super::NaiveTime; use crate::oldtime::Duration; use crate::Timelike; -use std::u32; #[test] fn test_time_from_hms_milli() { diff --git a/src/oldtime.rs b/src/oldtime.rs index 53293aa0e1..b6579dcf65 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -493,7 +493,6 @@ mod tests { use super::{Duration, MAX, MIN}; #[cfg(feature = "std")] use std::time::Duration as StdDuration; - use std::{i32, i64}; #[test] fn test_duration() { From e91c5a63920691359b76bd06cda4a091b7018c85 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 8 Jul 2023 08:00:39 +0200 Subject: [PATCH 426/999] Replace `Vec` with array --- src/naive/date.rs | 2 +- src/oldtime.rs | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 88b871b849..cc1101839a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3122,7 +3122,7 @@ mod tests { #[test] fn test_naiveweek() { let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); - let asserts = vec![ + let asserts = [ (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), diff --git a/src/oldtime.rs b/src/oldtime.rs index b6579dcf65..f343756ed4 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -685,13 +685,9 @@ mod tests { let sum_2: Duration = duration_list_2.iter().sum(); assert_eq!(sum_2, Duration::seconds(17)); - let duration_vec = vec![ - Duration::zero(), - Duration::seconds(1), - Duration::seconds(6), - Duration::seconds(10), - ]; - let sum_3: Duration = duration_vec.into_iter().sum(); + let duration_arr = + [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; + let sum_3: Duration = duration_arr.into_iter().sum(); assert_eq!(sum_3, Duration::seconds(17)); } From 29923331b26c4063d19392e01effe05d9dd094fe Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 8 Jul 2023 08:01:52 +0200 Subject: [PATCH 427/999] Adjust formatting --- src/datetime/tests.rs | 3 +-- src/format/parse.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 24101039d1..1874d968b2 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -837,8 +837,7 @@ fn test_parse_datetime_utc() { }; assert!( d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", + "`{}` is parsed into `{:?}`, but reparsed result `{:?}` does not match", s, d, d_ diff --git a/src/format/parse.rs b/src/format/parse.rs index 746772af6c..5a39cef7ce 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1644,8 +1644,8 @@ mod tests { // Test against test data above for &(date, checkdate) in testdates.iter() { - eprintln!("Test input: {:?}", date); - eprintln!(" Expect: {:?}", checkdate); + #[cfg(feature = "std")] + eprintln!("Test input: {:?}\n Expect: {:?}", date, checkdate); let dt = rfc2822_to_datetime(date); // parse a date if dt != checkdate { // check for expected result From 7e484fc46f202bb6653dcbcedd3aaf027f7d7467 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 18 Jul 2023 07:54:09 +0200 Subject: [PATCH 428/999] Import `alloc` crate globally --- src/datetime/mod.rs | 3 --- src/format/formatting.rs | 3 --- src/format/mod.rs | 3 --- src/lib.rs | 3 +++ 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5ff141d3c2..3e9e8eb096 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -3,9 +3,6 @@ //! ISO 8601 date and time with time zone. -#[cfg(feature = "alloc")] -extern crate alloc; - #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::string::{String, ToString}; use core::borrow::Borrow; diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 61cf4dc5e6..f850e4e39c 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -3,9 +3,6 @@ //! Date and time formatting routines. -#[cfg(feature = "alloc")] -extern crate alloc; - #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std"))] diff --git a/src/format/mod.rs b/src/format/mod.rs index e31c8a8f4f..3ef39e1a6d 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -30,9 +30,6 @@ //! # Ok::<(), chrono::ParseError>(()) //! ``` -#[cfg(feature = "alloc")] -extern crate alloc; - #[cfg(feature = "alloc")] use alloc::boxed::Box; use core::fmt; diff --git a/src/lib.rs b/src/lib.rs index beb42f86dd..8ef20fc58c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -394,6 +394,9 @@ #![cfg_attr(feature = "rustc-serialize", allow(deprecated))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "alloc")] +extern crate alloc; + #[cfg(feature = "oldtime")] #[cfg_attr(docsrs, doc(cfg(feature = "oldtime")))] extern crate time as oldtime; From 5255fc9d240e236a6ce2b2aaaf08915ede593328 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 8 Jul 2023 08:25:42 +0200 Subject: [PATCH 429/999] Verify bounds at compile time in `test_datetime_is_send_and_copy` --- src/datetime/tests.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 1874d968b2..9c73232885 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1148,26 +1148,10 @@ fn test_datetime_format_with_local() { } #[test] -#[cfg(feature = "clock")] -fn test_datetime_is_copy() { - // UTC is known to be `Copy`. - let a = Utc::now(); - let b = a; - assert_eq!(a, b); -} - -#[test] -#[cfg(feature = "clock")] -fn test_datetime_is_send() { - use std::thread; - - // UTC is known to be `Send`. - let a = Utc::now(); - thread::spawn(move || { - let _ = a; - }) - .join() - .unwrap(); +fn test_datetime_is_send_and_copy() { + fn _assert_send_copy() {} + // UTC is known to be `Send + Copy`. + _assert_send_copy::>(); } #[test] From 3a6ce44b1343eb56836eb6e1fcfae56c529c57ed Mon Sep 17 00:00:00 2001 From: Michael Kefeder Date: Thu, 20 Jul 2023 12:54:12 +0200 Subject: [PATCH 430/999] impl Error trait for ParseMonthError --- src/month.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/month.rs b/src/month.rs index 161c43c85b..146a98ebe5 100644 --- a/src/month.rs +++ b/src/month.rs @@ -235,6 +235,16 @@ pub struct ParseMonthError { pub(crate) _dummy: (), } +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for ParseMonthError {} + +impl fmt::Display for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + impl fmt::Debug for ParseMonthError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ParseMonthError {{ .. }}") From 89cafd4cac2d26594070e2548960fe5dcce0bba0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 07:11:12 +0200 Subject: [PATCH 431/999] Add `DateTime::from_naive_utc_and_offset` --- src/datetime/mod.rs | 29 ++++++++++++++++++++++------- src/offset/mod.rs | 4 ++-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3e9e8eb096..3911aca6b4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -94,19 +94,34 @@ pub const MIN_DATETIME: DateTime = DateTime::::MIN_UTC; pub const MAX_DATETIME: DateTime = DateTime::::MAX_UTC; impl DateTime { - /// Makes a new `DateTime` with given *UTC* datetime and offset. - /// The local datetime should be constructed via the `TimeZone` trait. + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. /// - /// # Example + /// This is a low-level method, intended for use cases such as deserializing a `DateTime` or + /// passing it through FFI. /// - /// ``` - /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; + /// For regular use you will probably want to use a method such as + /// [`TimeZone::from_local_datetime`] or [`NaiveDateTime::and_local_timezone`] instead. + /// + /// # Example /// - /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc); - /// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); + #[cfg_attr(not(feature = "clock"), doc = "```ignore")] + #[cfg_attr(feature = "clock", doc = "```rust")] + /// use chrono::{Local, DateTime}; + /// + /// let dt = Local::now(); + /// // Get components + /// let naive_utc = dt.naive_utc(); + /// let offset = dt.offset().clone(); + /// // Serialize, pass through FFI... and recreate the `DateTime`: + /// let dt_new = DateTime::::from_naive_utc_and_offset(naive_utc, offset); + /// assert_eq!(dt, dt_new); /// ``` #[inline] #[must_use] + pub fn from_naive_utc_and_offset(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + DateTime { datetime, offset } + } + pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { DateTime { datetime, offset } } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index ee1fe7e233..ea53915309 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -474,7 +474,7 @@ pub trait TimeZone: Sized + Clone { #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { self.offset_from_local_datetime(local) - .map(|offset| DateTime::from_utc(*local - offset.fix(), offset)) + .map(|offset| DateTime::from_naive_utc_and_offset(*local - offset.fix(), offset)) } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. @@ -496,7 +496,7 @@ pub trait TimeZone: Sized + Clone { /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). #[allow(clippy::wrong_self_convention)] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc)) + DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc)) } } From 87bb2c8aeb58d0fe9fdda9aca1c7454182922755 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 08:53:48 +0200 Subject: [PATCH 432/999] Deprecate `DateTime::{from_local, from_utc}` --- src/datetime/mod.rs | 36 ++++++++++++------------------------ src/datetime/tests.rs | 7 ++++--- src/offset/utc.rs | 6 +++--- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3911aca6b4..8cf3814a82 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -122,12 +122,18 @@ impl DateTime { DateTime { datetime, offset } } + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. + #[inline] + #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_utc_datetime() or DateTime::from_naive_utc_and_offset instead" + )] pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { DateTime { datetime, offset } } - /// Makes a new `DateTime` with given **local** datetime and offset that - /// presents local timezone. + /// Makes a new `DateTime` from a `NaiveDateTime` in *local* time and an `Offset`. /// /// # Panics /// @@ -135,30 +141,12 @@ impl DateTime { /// /// 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 - /// - /// ``` - /// use chrono::DateTime; - /// use chrono::naive::NaiveDate; - /// use chrono::offset::{Utc, FixedOffset}; - /// - /// let naivedatetime_utc = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); - /// let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - /// - /// let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - /// let naivedatetime_east = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); - /// let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); - /// - /// let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); - /// let naivedatetime_west = NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); - /// let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); - - /// assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); - /// assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); - /// ``` #[inline] #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_local_datetime() or NaiveDateTime::and_local_timezone instead" + )] pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { let datetime_utc = datetime - offset.fix(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 9c73232885..b16f6ff22a 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -674,7 +674,7 @@ fn test_rfc3339_opts() { assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); - let ut = DateTime::::from_utc(dt.naive_utc(), Utc); + let ut = dt.naive_utc().and_utc(); assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); @@ -1276,6 +1276,7 @@ fn test_datetime_format_alignment() { } #[test] +#[allow(deprecated)] fn test_datetime_from_local() { // 2000-01-12T02:00:00Z let naivedatetime_utc = @@ -1321,7 +1322,7 @@ fn test_years_elapsed() { #[test] fn test_datetime_add_assign() { let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); - let datetime = DateTime::::from_utc(naivedatetime, Utc); + let datetime = naivedatetime.and_utc(); let mut datetime_add = datetime; datetime_add += Duration::seconds(60); @@ -1358,7 +1359,7 @@ fn test_datetime_add_assign_local() { #[test] fn test_datetime_sub_assign() { let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(); - let datetime = DateTime::::from_utc(naivedatetime, Utc); + let datetime = naivedatetime.and_utc(); let mut datetime_sub = datetime; datetime_sub -= Duration::minutes(90); diff --git a/src/offset/utc.rs b/src/offset/utc.rs index dbcb8eecbb..f531bcae67 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -33,9 +33,9 @@ use crate::{Date, DateTime}; /// # Example /// /// ``` -/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; +/// use chrono::{TimeZone, NaiveDateTime, Utc}; /// -/// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc); +/// let dt = Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_opt(61, 0).unwrap()); /// /// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); @@ -71,7 +71,7 @@ impl Utc { SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); let naive = NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap(); - DateTime::from_utc(naive, Utc) + Utc.from_utc_datetime(&naive) } /// Returns a `DateTime` which corresponds to the current date and time. From 080f7a0c66ced77afbf1a92caa027d223ef1f336 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 13:05:38 +0200 Subject: [PATCH 433/999] Add test for deprecated methods --- src/datetime/tests.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b16f6ff22a..e82484635e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1481,3 +1481,16 @@ fn test_auto_conversion() { let utc_dt2: DateTime = cdt_dt.into(); assert_eq!(utc_dt, utc_dt2); } + +#[test] +#[cfg(feature = "clock")] +#[allow(deprecated)] +fn test_test_deprecated_from_offset() { + let now = Local::now(); + let naive = now.naive_local(); + let utc = now.naive_utc(); + let offset: FixedOffset = *now.offset(); + + assert_eq!(DateTime::::from_local(naive, offset), now); + assert_eq!(DateTime::::from_utc(utc, offset), now); +} From bdb10684e031c1beb7e31b9e1df773d0e42c9df2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 12:38:56 +0200 Subject: [PATCH 434/999] Import `NaiveDateTime` --- src/format/formatting.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f850e4e39c..5bec76921d 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -11,7 +11,7 @@ use core::fmt; use core::fmt::Write; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::naive::{NaiveDate, NaiveTime}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std"))] @@ -478,7 +478,7 @@ fn format_inner( // same as `%Y-%m-%dT%H:%M:%S%.f%:z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) + Some(write_rfc3339(result, NaiveDateTime::new(*d, *t), off)) } else { None } @@ -580,7 +580,7 @@ impl OffsetFormat { #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) fn write_rfc3339( result: &mut String, - dt: crate::NaiveDateTime, + dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { // reuse `Debug` impls which already print ISO 8601 format. @@ -599,7 +599,7 @@ pub(crate) fn write_rfc3339( /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( result: &mut String, - dt: crate::NaiveDateTime, + dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) From 17d50a1b24f77557545e1f0f41b42020d0b1bd90 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 12:35:53 +0200 Subject: [PATCH 435/999] Let `OffsetFormat::format` take `&mut impl Write` --- src/format/formatting.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 5bec76921d..557f13091a 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -498,11 +498,11 @@ fn format_inner( #[cfg(any(feature = "alloc", feature = "std"))] impl OffsetFormat { - /// Writes an offset from UTC to `result` with the format defined by `self`. - fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result { + /// Writes an offset from UTC with the format defined by `self`. + fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { let off = off.local_minus_utc(); if self.allow_zulu && off == 0 { - result.push('Z'); + w.write_char('Z')?; return Ok(()); } let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; @@ -549,28 +549,28 @@ impl OffsetFormat { if hours < 10 { if self.padding == Pad::Space { - result.push(' '); + w.write_char(' ')?; } - result.push(sign); + w.write_char(sign)?; if self.padding == Pad::Zero { - result.push('0'); + w.write_char('0')?; } - result.push((b'0' + hours) as char); + w.write_char((b'0' + hours) as char)?; } else { - result.push(sign); - write_hundreds(result, hours)?; + w.write_char(sign)?; + write_hundreds(w, hours)?; } if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision { if colons { - result.push(':'); + w.write_char(':')?; } - write_hundreds(result, mins)?; + write_hundreds(w, mins)?; } if let OffsetPrecision::Seconds = precision { if colons { - result.push(':'); + w.write_char(':')?; } - write_hundreds(result, secs)?; + write_hundreds(w, secs)?; } Ok(()) } From 7f8b7ae03171969f8ead27e56eae862046358a48 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 12:19:46 +0200 Subject: [PATCH 436/999] Let `write_rfc3339` take `&mut impl Write` --- src/format/formatting.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 557f13091a..8b01779c56 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -579,20 +579,20 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) fn write_rfc3339( - result: &mut String, + w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. - write!(result, "{:?}", dt)?; + write!(w, "{:?}", dt)?; OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, allow_zulu: false, padding: Pad::Zero, } - .format(result, off) + .format(w, off) } #[cfg(any(feature = "alloc", feature = "std"))] From 506ca47a856bca9f45d0d7dc1792f463e1e4a660 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 12:35:46 +0200 Subject: [PATCH 437/999] Let `write_rfc2822` take `&mut impl Write` --- src/format/formatting.rs | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 8b01779c56..acca000437 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -469,7 +469,7 @@ fn format_inner( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, d, t, off, locale)) + Some(write_rfc2822_inner(result, *d, *t, off, locale)) } else { None } @@ -598,19 +598,19 @@ pub(crate) fn write_rfc3339( #[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( - result: &mut String, + w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { - write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) + write_rfc2822_inner(w, dt.date(), dt.time(), off, Locales::new(None)) } #[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( - result: &mut String, - d: &NaiveDate, - t: &NaiveTime, + w: &mut impl Write, + d: NaiveDate, + t: NaiveTime, off: FixedOffset, locale: Locales, ) -> fmt::Result { @@ -620,29 +620,29 @@ fn write_rfc2822_inner( return Err(fmt::Error); } - result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); - result.push_str(", "); - write_hundreds(result, d.day() as u8)?; - result.push(' '); - result.push_str(locale.short_months[d.month0() as usize]); - result.push(' '); - write_hundreds(result, (year / 100) as u8)?; - write_hundreds(result, (year % 100) as u8)?; - result.push(' '); - write_hundreds(result, t.hour() as u8)?; - result.push(':'); - write_hundreds(result, t.minute() as u8)?; - result.push(':'); + w.write_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize])?; + w.write_str(", ")?; + write_hundreds(w, d.day() as u8)?; + w.write_char(' ')?; + w.write_str(locale.short_months[d.month0() as usize])?; + w.write_char(' ')?; + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + w.write_char(' ')?; + write_hundreds(w, t.hour() as u8)?; + w.write_char(':')?; + write_hundreds(w, t.minute() as u8)?; + w.write_char(':')?; let sec = t.second() + t.nanosecond() / 1_000_000_000; - write_hundreds(result, sec as u8)?; - result.push(' '); + write_hundreds(w, sec as u8)?; + w.write_char(' ')?; OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::None, allow_zulu: false, padding: Pad::Zero, } - .format(result, off) + .format(w, off) } /// Equivalent to `{:02}` formatting for n < 100. From d759e689cf62869d78a964fbf5e32f379057f885 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 12:44:23 +0200 Subject: [PATCH 438/999] Let `format_inner` take `&mut impl Write` --- src/format/formatting.rs | 80 +++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index acca000437..e88d2773d5 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -270,7 +270,7 @@ pub fn format_item_localized( #[cfg(any(feature = "alloc", feature = "std"))] fn format_inner( - result: &mut String, + w: &mut impl Write, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, @@ -280,9 +280,9 @@ fn format_inner( let locale = Locales::new(locale); match *item { - Item::Literal(s) | Item::Space(s) => result.push_str(s), + Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(any(feature = "alloc", feature = "std"))] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, ref pad) => { use self::Numeric::*; @@ -329,19 +329,19 @@ fn format_inner( if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { // non-four-digit years require an explicit sign as per ISO 8601 match *pad { - Pad::None => write!(result, "{:+}", v), - Pad::Zero => write!(result, "{:+01$}", v, width + 1), - Pad::Space => write!(result, "{:+1$}", v, width + 1), + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), } } else { match *pad { - Pad::None => write!(result, "{}", v), - Pad::Zero => write!(result, "{:01$}", v, width), - Pad::Space => write!(result, "{:1$}", v, width), + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), } - }? + } } else { - return Err(fmt::Error); // insufficient arguments for given format + Err(fmt::Error) // insufficient arguments for given format } } @@ -350,34 +350,34 @@ fn format_inner( let ret = match *spec { ShortMonthName => date.map(|d| { - result.push_str(locale.short_months[d.month0() as usize]); + w.write_str(locale.short_months[d.month0() as usize])?; Ok(()) }), LongMonthName => date.map(|d| { - result.push_str(locale.long_months[d.month0() as usize]); + w.write_str(locale.long_months[d.month0() as usize])?; Ok(()) }), ShortWeekdayName => date.map(|d| { - result.push_str( + w.write_str( locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - ); + )?; Ok(()) }), LongWeekdayName => date.map(|d| { - result.push_str( + w.write_str( locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], - ); + )?; Ok(()) }), LowerAmPm => time.map(|t| { let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; - for char in ampm.chars() { - result.extend(char.to_lowercase()) + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? } Ok(()) }), UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }); + w.write_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] })?; Ok(()) }), Nanosecond => time.map(|t| { @@ -385,45 +385,45 @@ fn format_inner( if nano == 0 { Ok(()) } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) + write!(w, ".{:03}", nano / 1_000_000) } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) + write!(w, ".{:06}", nano / 1_000) } else { - write!(result, ".{:09}", nano) + write!(w, ".{:09}", nano) } }), Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:03}", nano / 1_000_000) + write!(w, ".{:03}", nano / 1_000_000) }), Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:06}", nano / 1_000) + write!(w, ".{:06}", nano / 1_000) }), Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:09}", nano) + write!(w, ".{:09}", nano) }), Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) + write!(w, "{:03}", nano / 1_000_000) }) } Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) + write!(w, "{:06}", nano / 1_000) }) } Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) + write!(w, "{:09}", nano) }) } TimezoneName => off.map(|(name, _)| { - result.push_str(name); + w.write_str(name)?; Ok(()) }), TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { @@ -433,7 +433,7 @@ fn format_inner( allow_zulu: *spec == TimezoneOffsetZ, padding: Pad::Zero, } - .format(result, off) + .format(w, off) }), TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { OffsetFormat { @@ -442,7 +442,7 @@ fn format_inner( allow_zulu: *spec == TimezoneOffsetColonZ, padding: Pad::Zero, } - .format(result, off) + .format(w, off) }), TimezoneOffsetDoubleColon => off.map(|&(_, off)| { OffsetFormat { @@ -451,7 +451,7 @@ fn format_inner( allow_zulu: false, padding: Pad::Zero, } - .format(result, off) + .format(w, off) }), TimezoneOffsetTripleColon => off.map(|&(_, off)| { OffsetFormat { @@ -460,7 +460,7 @@ fn format_inner( allow_zulu: false, padding: Pad::Zero, } - .format(result, off) + .format(w, off) }), Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { return Err(fmt::Error); @@ -469,7 +469,7 @@ fn format_inner( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, *d, *t, off, locale)) + Some(write_rfc2822_inner(w, *d, *t, off, locale)) } else { None } @@ -478,22 +478,18 @@ fn format_inner( // same as `%Y-%m-%dT%H:%M:%S%.f%:z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, NaiveDateTime::new(*d, *t), off)) + Some(write_rfc3339(w, NaiveDateTime::new(*d, *t), off)) } else { None } } }; - match ret { - Some(ret) => ret?, - None => return Err(fmt::Error), // insufficient arguments for given format - } + ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format } - Item::Error => return Err(fmt::Error), + Item::Error => Err(fmt::Error), } - Ok(()) } #[cfg(any(feature = "alloc", feature = "std"))] From 1a3cd63ef4fd4175bf7efa73c1c5a1135174257e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 13:54:42 +0200 Subject: [PATCH 439/999] Delay getting locale strings until use --- src/format/formatting.rs | 114 +++++++++++++++++---------------------- src/format/locales.rs | 4 ++ src/format/mod.rs | 5 +- 3 files changed, 56 insertions(+), 67 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index e88d2773d5..0952a8ffc5 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -24,61 +24,47 @@ use super::{ Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, OffsetPrecision, Pad, }; +use locales::*; -#[cfg(any(feature = "alloc", feature = "std"))] -struct Locales { - short_months: &'static [&'static str], - long_months: &'static [&'static str], - short_weekdays: &'static [&'static str], - long_weekdays: &'static [&'static str], - am_pm: &'static [&'static str], -} +#[cfg(not(feature = "unstable-locales"))] +mod locales { + use crate::format::Locale; -#[cfg(any(feature = "alloc", feature = "std"))] -impl Locales { - fn new(_locale: Option) -> Self { - #[cfg(feature = "unstable-locales")] - { - let locale = _locale.unwrap_or(Locale::POSIX); - Self { - short_months: locales::short_months(locale), - long_months: locales::long_months(locale), - short_weekdays: locales::short_weekdays(locale), - long_weekdays: locales::long_weekdays(locale), - am_pm: locales::am_pm(locale), - } - } - #[cfg(not(feature = "unstable-locales"))] - Self { - short_months: &[ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - ], - long_months: &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - long_weekdays: &[ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - am_pm: &["AM", "PM"], - } + pub(crate) const fn default_locale() -> Locale { + Locale + } + + pub(crate) fn short_months(_locale: Locale) -> &'static [&'static str] { + &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + } + + pub(crate) fn long_months(_locale: Locale) -> &'static [&'static str] { + &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + } + + pub(crate) fn short_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + } + + pub(crate) fn long_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + } + + pub(crate) fn am_pm(_locale: Locale) -> &'static [&'static str] { + &["AM", "PM"] } } @@ -277,7 +263,7 @@ fn format_inner( item: &Item<'_>, locale: Option, ) -> fmt::Result { - let locale = Locales::new(locale); + let locale = locale.unwrap_or(default_locale()); match *item { Item::Literal(s) | Item::Space(s) => w.write_str(s), @@ -350,34 +336,34 @@ fn format_inner( let ret = match *spec { ShortMonthName => date.map(|d| { - w.write_str(locale.short_months[d.month0() as usize])?; + w.write_str(short_months(locale)[d.month0() as usize])?; Ok(()) }), LongMonthName => date.map(|d| { - w.write_str(locale.long_months[d.month0() as usize])?; + w.write_str(long_months(locale)[d.month0() as usize])?; Ok(()) }), ShortWeekdayName => date.map(|d| { w.write_str( - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], )?; Ok(()) }), LongWeekdayName => date.map(|d| { w.write_str( - locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], )?; Ok(()) }), LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; for c in ampm.chars().flat_map(|c| c.to_lowercase()) { w.write_char(c)? } Ok(()) }), UpperAmPm => time.map(|t| { - w.write_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] })?; + w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; Ok(()) }), Nanosecond => time.map(|t| { @@ -598,7 +584,7 @@ pub(crate) fn write_rfc2822( dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { - write_rfc2822_inner(w, dt.date(), dt.time(), off, Locales::new(None)) + write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) } #[cfg(any(feature = "alloc", feature = "std"))] @@ -608,7 +594,7 @@ fn write_rfc2822_inner( d: NaiveDate, t: NaiveTime, off: FixedOffset, - locale: Locales, + locale: Locale, ) -> fmt::Result { let year = d.year(); // RFC2822 is only defined on years 0 through 9999 @@ -616,11 +602,11 @@ fn write_rfc2822_inner( return Err(fmt::Error); } - w.write_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize])?; + w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; w.write_str(", ")?; write_hundreds(w, d.day() as u8)?; w.write_char(' ')?; - w.write_str(locale.short_months[d.month0() as usize])?; + w.write_str(short_months(locale)[d.month0() as usize])?; w.write_char(' ')?; write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; diff --git a/src/format/locales.rs b/src/format/locales.rs index 7311d661b1..4ae742aaa1 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,5 +1,9 @@ use pure_rust_locales::{locale_match, Locale}; +pub(crate) const fn default_locale() -> Locale { + Locale::POSIX +} + pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { locale_match!(locale => LC_TIME::ABMON) } diff --git a/src/format/mod.rs b/src/format/mod.rs index 3ef39e1a6d..5e9f16c2ce 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -66,9 +66,8 @@ pub use pure_rust_locales::Locale; pub use strftime::StrftimeItems; #[cfg(not(feature = "unstable-locales"))] -#[allow(dead_code)] -#[derive(Debug)] -struct Locale; +#[derive(Copy, Clone, Debug)] +pub(crate) struct Locale; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. #[derive(Clone, PartialEq, Eq, Hash)] From b65255ea43557c2dc30ecfa4d6c34a754d0a1918 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:33:42 +0200 Subject: [PATCH 440/999] Merge `locales` modules --- src/format/formatting.rs | 43 --------------- src/format/locales.rs | 112 +++++++++++++++++++++++++++++---------- src/format/mod.rs | 12 ++--- 3 files changed, 87 insertions(+), 80 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 0952a8ffc5..263f31707b 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -17,7 +17,6 @@ use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::{Datelike, Timelike, Weekday}; -#[cfg(feature = "unstable-locales")] use super::locales; #[cfg(any(feature = "alloc", feature = "std"))] use super::{ @@ -26,48 +25,6 @@ use super::{ }; use locales::*; -#[cfg(not(feature = "unstable-locales"))] -mod locales { - use crate::format::Locale; - - pub(crate) const fn default_locale() -> Locale { - Locale - } - - pub(crate) fn short_months(_locale: Locale) -> &'static [&'static str] { - &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - } - - pub(crate) fn long_months(_locale: Locale) -> &'static [&'static str] { - &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ] - } - - pub(crate) fn short_weekdays(_locale: Locale) -> &'static [&'static str] { - &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] - } - - pub(crate) fn long_weekdays(_locale: Locale) -> &'static [&'static str] { - &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - } - - pub(crate) fn am_pm(_locale: Locale) -> &'static [&'static str] { - &["AM", "PM"] - } -} - /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. #[cfg(any(feature = "alloc", feature = "std"))] diff --git a/src/format/locales.rs b/src/format/locales.rs index 4ae742aaa1..e768c6ea04 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,41 +1,95 @@ -use pure_rust_locales::{locale_match, Locale}; +#[cfg(feature = "unstable-locales")] +mod localized { + use pure_rust_locales::{locale_match, Locale}; -pub(crate) const fn default_locale() -> Locale { - Locale::POSIX -} + pub(crate) const fn default_locale() -> Locale { + Locale::POSIX + } -pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABMON) -} + pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABMON) + } -pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::MON) -} + pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::MON) + } -pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABDAY) -} + pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABDAY) + } -pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::DAY) -} + pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::DAY) + } -pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::AM_PM) -} + pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::AM_PM) + } -pub(crate) const fn d_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_FMT) -} + pub(crate) const fn d_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_FMT) + } -pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_T_FMT) -} + pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_T_FMT) + } + + pub(crate) const fn t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT) + } -pub(crate) const fn t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::T_FMT) + pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT_AMPM) + } } -pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::T_FMT_AMPM) +#[cfg(feature = "unstable-locales")] +pub(crate) use localized::*; +#[cfg(feature = "unstable-locales")] +pub use pure_rust_locales::Locale; + +#[cfg(not(feature = "unstable-locales"))] +mod unlocalized { + #[derive(Copy, Clone, Debug)] + pub(crate) struct Locale; + + pub(crate) const fn default_locale() -> Locale { + Locale + } + + pub(crate) const fn short_months(_locale: Locale) -> &'static [&'static str] { + &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + } + + pub(crate) const fn long_months(_locale: Locale) -> &'static [&'static str] { + &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + } + + pub(crate) const fn short_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + } + + pub(crate) const fn long_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + } + + pub(crate) const fn am_pm(_locale: Locale) -> &'static [&'static str] { + &["AM", "PM"] + } } + +#[cfg(not(feature = "unstable-locales"))] +pub(crate) use unlocalized::*; diff --git a/src/format/mod.rs b/src/format/mod.rs index 5e9f16c2ce..65598187e6 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -48,7 +48,6 @@ pub(crate) mod scan; pub mod strftime; -#[cfg(feature = "unstable-locales")] pub(crate) mod locales; pub(crate) use formatting::write_hundreds; @@ -58,17 +57,14 @@ pub use formatting::{format, format_item, DelayedFormat}; pub use formatting::{format_item_localized, format_localized}; #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) use formatting::{write_rfc2822, write_rfc3339}; +#[cfg(feature = "unstable-locales")] +pub use locales::Locale; +#[cfg(not(feature = "unstable-locales"))] +pub(crate) use locales::Locale; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; -/// L10n locales. -#[cfg(feature = "unstable-locales")] -pub use pure_rust_locales::Locale; pub use strftime::StrftimeItems; -#[cfg(not(feature = "unstable-locales"))] -#[derive(Copy, Clone, Debug)] -pub(crate) struct Locale; - /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. #[derive(Clone, PartialEq, Eq, Hash)] enum Void {} From 86391acae7da828e6d13cacd106f57220b3d33be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 16:58:35 +0200 Subject: [PATCH 441/999] Fix potential panic due to overflow --- src/format/formatting.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 263f31707b..fa30f3e110 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -258,7 +258,7 @@ fn format_inner( match (date, time, off) { (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), (Some(d), Some(t), Some(&(_, off))) => { - Some((d.and_time(*t) - off).timestamp()) + Some(d.and_time(*t).timestamp() - i64::from(off.local_minus_utc())) } (_, _, _) => None, }, From 7a1c26e9ed3c3578252b8588f1b62b1236398923 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 12 Jul 2023 16:27:11 +0200 Subject: [PATCH 442/999] Make formatting benchmarks comparable with `to_rfc3339` --- benches/chrono.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/benches/chrono.rs b/benches/chrono.rs index d0babf3e19..1cc5eb0659 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -148,14 +148,16 @@ fn bench_parse_strftime_localized(c: &mut Criterion) { fn bench_format(c: &mut Criterion) { let dt = Local::now(); - c.bench_function("bench_format", |b| b.iter(|| format!("{}", dt.format("%Y-%m-%d %H-%M-%S")))); + c.bench_function("bench_format", |b| { + b.iter(|| format!("{}", black_box(dt).format("%Y-%m-%dT%H:%M:%S%.f%:z"))) + }); } fn bench_format_with_items(c: &mut Criterion) { let dt = Local::now(); - let items: Vec<_> = StrftimeItems::new("%Y-%m-%d %H-%M-%S").collect(); + let items: Vec<_> = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%.f%:z").collect(); c.bench_function("bench_format_with_items", |b| { - b.iter(|| format!("{}", dt.format_with_items(items.iter()))) + b.iter(|| format!("{}", black_box(dt).format_with_items(items.iter()))) }); } @@ -163,19 +165,22 @@ fn bench_format_manual(c: &mut Criterion) { let dt = Local::now(); c.bench_function("bench_format_manual", |b| { b.iter(|| { + black_box(dt); format!( - "{}-{:02}-{:02} {:02}:{:02}:{:02}", + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}{:+02}:{:02}", dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), - dt.second() + dt.second(), + dt.nanosecond(), + dt.offset().fix().local_minus_utc() / 3600, + dt.offset().fix().local_minus_utc() / 60, ) }) }); } - criterion_group!( benches, bench_datetime_parse_from_rfc2822, From 62fd317b04743d0acb64569061ce345ce9624312 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 11 Jul 2023 18:11:37 +0200 Subject: [PATCH 443/999] Test more padding/alignment/truncation parameters --- src/datetime/tests.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index e82484635e..9a3adccf48 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,6 +4,8 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; use crate::oldtime::Duration; +#[cfg(any(feature = "alloc", feature = "std"))] +use crate::Timelike; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; #[derive(Clone)] @@ -1247,19 +1249,20 @@ fn test_from_system_time() { #[test] #[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_format_alignment() { - let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap(); + let datetime = + Utc.with_ymd_and_hms(2007, 1, 2, 12, 34, 56).unwrap().with_nanosecond(123456789).unwrap(); - // Item::Literal + // Item::Literal, odd number of padding bytes. let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>3}", percent)); - assert_eq!("% ", format!("{:<3}", percent)); - assert_eq!(" % ", format!("{:^3}", percent)); + assert_eq!(" %", format!("{:>4}", percent)); + assert_eq!("% ", format!("{:<4}", percent)); + assert_eq!(" % ", format!("{:^4}", percent)); - // Item::Numeric + // Item::Numeric, custom non-ASCII padding character let year = datetime.format("%Y"); - assert_eq!(" 2007", format!("{:>6}", year)); - assert_eq!("2007 ", format!("{:<6}", year)); - assert_eq!(" 2007 ", format!("{:^6}", year)); + assert_eq!("——2007", format!("{:—>6}", year)); + assert_eq!("2007——", format!("{:—<6}", year)); + assert_eq!("—2007—", format!("{:—^6}", year)); // Item::Fixed let tz = datetime.format("%Z"); @@ -1269,10 +1272,13 @@ fn test_datetime_format_alignment() { // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] let ymd = datetime.format("%Y %B %d"); - let ymd_formatted = "2007 January 02"; - assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); - assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); - assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); + assert_eq!(" 2007 January 02", format!("{:>17}", ymd)); + assert_eq!("2007 January 02 ", format!("{:<17}", ymd)); + assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd)); + + // Truncated + let time = datetime.format("%T%.6f"); + assert_eq!("12:34:56.1234", format!("{:.13}", time)); } #[test] From 783d6a13cce07dc040017ad9977bd5a2308f83e6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 19 Jul 2023 13:49:56 +0200 Subject: [PATCH 444/999] Collect formatting tests in `Formatting` module --- src/datetime/tests.rs | 37 ---------- src/format/formatting.rs | 139 ++++++++++++++++++++++++++++++++++++ src/naive/date.rs | 45 ------------ src/naive/datetime/tests.rs | 15 ---- src/naive/time/tests.rs | 33 --------- 5 files changed, 139 insertions(+), 130 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 9a3adccf48..ffe4474c8c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,8 +4,6 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; use crate::oldtime::Duration; -#[cfg(any(feature = "alloc", feature = "std"))] -use crate::Timelike; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; #[derive(Clone)] @@ -1246,41 +1244,6 @@ fn test_from_system_time() { ); } -#[test] -#[cfg(any(feature = "alloc", feature = "std"))] -fn test_datetime_format_alignment() { - let datetime = - Utc.with_ymd_and_hms(2007, 1, 2, 12, 34, 56).unwrap().with_nanosecond(123456789).unwrap(); - - // Item::Literal, odd number of padding bytes. - let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>4}", percent)); - assert_eq!("% ", format!("{:<4}", percent)); - assert_eq!(" % ", format!("{:^4}", percent)); - - // Item::Numeric, custom non-ASCII padding character - let year = datetime.format("%Y"); - assert_eq!("——2007", format!("{:—>6}", year)); - assert_eq!("2007——", format!("{:—<6}", year)); - assert_eq!("—2007—", format!("{:—^6}", year)); - - // Item::Fixed - let tz = datetime.format("%Z"); - assert_eq!(" UTC", format!("{:>5}", tz)); - assert_eq!("UTC ", format!("{:<5}", tz)); - assert_eq!(" UTC ", format!("{:^5}", tz)); - - // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] - let ymd = datetime.format("%Y %B %d"); - assert_eq!(" 2007 January 02", format!("{:>17}", ymd)); - assert_eq!("2007 January 02 ", format!("{:<17}", ymd)); - assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd)); - - // Truncated - let time = datetime.format("%T%.6f"); - assert_eq!("12:34:56.1234", format!("{:.13}", time)); -} - #[test] #[allow(deprecated)] fn test_datetime_from_local() { diff --git a/src/format/formatting.rs b/src/format/formatting.rs index fa30f3e110..140fd7b1d1 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -601,6 +601,145 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { mod tests { use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; use crate::FixedOffset; + #[cfg(any(feature = "alloc", feature = "std"))] + use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_date_format() { + let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); + assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); + assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); + assert_eq!(d.format("%d,%e").to_string(), "04, 4"); + assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); + assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); + assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year + assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); + assert_eq!(d.format("%F").to_string(), "2012-03-04"); + assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); + assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // non-four-digit years + assert_eq!( + NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), + "+12345" + ); + assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); + assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); + assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); + assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); + assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); + assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); + assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); + assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); + assert_eq!( + NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), + "-12345" + ); + + // corner cases + assert_eq!( + NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2008,08,52,53,01" + ); + assert_eq!( + NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2009,09,01,00,53" + ); + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_time_format() { + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); + assert_eq!(t.format("%M").to_string(), "05"); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); + assert_eq!(t.format("%R").to_string(), "03:05"); + assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); + assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); + assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); + + let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); + + let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); + + // corner cases + assert_eq!( + NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), + "01:57:09 PM" + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), + "23:59:60" + ); + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_datetime_format() { + let dt = + NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); + assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); + assert_eq!(dt.format("%s").to_string(), "1283929614"); + assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // a horror of leap second: coming near to you. + let dt = NaiveDate::from_ymd_opt(2012, 6, 30) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap(); + assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); + assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_datetime_format_alignment() { + let datetime = Utc + .with_ymd_and_hms(2007, 1, 2, 12, 34, 56) + .unwrap() + .with_nanosecond(123456789) + .unwrap(); + + // Item::Literal, odd number of padding bytes. + let percent = datetime.format("%%"); + assert_eq!(" %", format!("{:>4}", percent)); + assert_eq!("% ", format!("{:<4}", percent)); + assert_eq!(" % ", format!("{:^4}", percent)); + + // Item::Numeric, custom non-ASCII padding character + let year = datetime.format("%Y"); + assert_eq!("——2007", format!("{:—>6}", year)); + assert_eq!("2007——", format!("{:—<6}", year)); + assert_eq!("—2007—", format!("{:—^6}", year)); + + // Item::Fixed + let tz = datetime.format("%Z"); + assert_eq!(" UTC", format!("{:>5}", tz)); + assert_eq!("UTC ", format!("{:<5}", tz)); + assert_eq!(" UTC ", format!("{:^5}", tz)); + + // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] + let ymd = datetime.format("%Y %B %d"); + assert_eq!(" 2007 January 02", format!("{:>17}", ymd)); + assert_eq!("2007 January 02 ", format!("{:<17}", ymd)); + assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd)); + + // Truncated + let time = datetime.format("%T%.6f"); + assert_eq!("12:34:56.1234", format!("{:.13}", time)); + } #[test] fn test_offset_formatting() { diff --git a/src/naive/date.rs b/src/naive/date.rs index cc1101839a..4afe9006b3 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3053,51 +3053,6 @@ mod tests { ); } - #[test] - #[cfg(any(feature = "alloc", feature = "std"))] - fn test_date_format() { - let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); - assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); - assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); - assert_eq!(d.format("%d,%e").to_string(), "04, 4"); - assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); - assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); - assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year - assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); - assert_eq!(d.format("%F").to_string(), "2012-03-04"); - assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); - assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // non-four-digit years - assert_eq!( - NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), - "+12345" - ); - assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); - assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); - assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); - assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); - assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); - assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); - assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); - assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); - assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); - assert_eq!( - NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), - "-12345" - ); - - // corner cases - assert_eq!( - NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), - "2008,08,52,53,01" - ); - assert_eq!( - NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), - "2009,09,01,00,53" - ); - } - #[test] fn test_day_iterator_limit() { assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index be1bdf52f4..2df0bf1b21 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -310,21 +310,6 @@ fn test_datetime_parse_from_str() { ); } -#[test] -#[cfg(any(feature = "alloc", feature = "std"))] -fn test_datetime_format() { - let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); - assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); - assert_eq!(dt.format("%s").to_string(), "1283929614"); - assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // a horror of leap second: coming near to you. - let dt = - NaiveDate::from_ymd_opt(2012, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); - assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); - assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. -} - #[test] fn test_datetime_add_sub_invariant() { // issue #37 diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 28ffcb4ef6..944d86e187 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -334,36 +334,3 @@ fn test_time_parse_from_str() { assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } - -#[test] -#[cfg(any(feature = "alloc", feature = "std"))] -fn test_time_format() { - let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); - assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); - assert_eq!(t.format("%M").to_string(), "05"); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); - assert_eq!(t.format("%R").to_string(), "03:05"); - assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); - assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); - assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); - - let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); - - let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); - - // corner cases - assert_eq!(NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), "01:57:09 PM"); - assert_eq!( - NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), - "23:59:60" - ); -} From 1674e74dbdce97b254c926dba053e777df2f6047 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 20:57:48 +0200 Subject: [PATCH 445/999] Localize decimal point --- src/datetime/tests.rs | 19 +++++++++++++++++++ src/format/formatting.rs | 22 ++++++++++++++-------- src/format/locales.rs | 8 ++++++++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index ffe4474c8c..7686105275 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1463,3 +1463,22 @@ fn test_test_deprecated_from_offset() { assert_eq!(DateTime::::from_local(naive, offset), now); assert_eq!(DateTime::::from_utc(utc, offset), now); } + +#[test] +#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] +fn locale_decimal_point() { + use crate::Locale::{ar_SY, nl_NL}; + use crate::Timelike; + let dt = + Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap(); + + assert_eq!(dt.format_localized("%T%.f", nl_NL).to_string(), "18:58:00,123456780"); + assert_eq!(dt.format_localized("%T%.3f", nl_NL).to_string(), "18:58:00,123"); + assert_eq!(dt.format_localized("%T%.6f", nl_NL).to_string(), "18:58:00,123456"); + assert_eq!(dt.format_localized("%T%.9f", nl_NL).to_string(), "18:58:00,123456780"); + + assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00.123456780"); + assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00.123"); + assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); + assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); +} diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 140fd7b1d1..1df5939ab8 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -327,25 +327,31 @@ fn format_inner( let nano = t.nanosecond() % 1_000_000_000; if nano == 0 { Ok(()) - } else if nano % 1_000_000 == 0 { - write!(w, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(w, ".{:06}", nano / 1_000) } else { - write!(w, ".{:09}", nano) + w.write_str(decimal_point(locale))?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{:09}", nano) + } } }), Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:03}", nano / 1_000_000) + w.write_str(decimal_point(locale))?; + write!(w, "{:03}", nano / 1_000_000) }), Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:06}", nano / 1_000) + w.write_str(decimal_point(locale))?; + write!(w, "{:06}", nano / 1_000) }), Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:09}", nano) + w.write_str(decimal_point(locale))?; + write!(w, "{:09}", nano) }), Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { time.map(|t| { diff --git a/src/format/locales.rs b/src/format/locales.rs index e768c6ea04..4cf4d4149a 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -26,6 +26,10 @@ mod localized { locale_match!(locale => LC_TIME::AM_PM) } + pub(crate) const fn decimal_point(locale: Locale) -> &'static str { + locale_match!(locale => LC_NUMERIC::DECIMAL_POINT) + } + pub(crate) const fn d_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::D_FMT) } @@ -89,6 +93,10 @@ mod unlocalized { pub(crate) const fn am_pm(_locale: Locale) -> &'static [&'static str] { &["AM", "PM"] } + + pub(crate) const fn decimal_point(_locale: Locale) -> &'static str { + "." + } } #[cfg(not(feature = "unstable-locales"))] From 40c1ea1c465c291886e277a09e035814b222e6a2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 25 Jul 2023 12:47:08 +0200 Subject: [PATCH 446/999] Remove needless `Option` --- src/offset/local/tz_info/timezone.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 36b2808105..2e9578176d 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -225,7 +225,7 @@ impl<'a> TimeZoneRef<'a> { // if we have at least one transition, // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions if !self.transitions.is_empty() { - let mut prev = Some(self.local_time_types[0]); + let mut prev = self.local_time_types[0]; for transition in self.transitions { let after_ltt = self.local_time_types[transition.local_time_type_index]; @@ -233,34 +233,33 @@ impl<'a> TimeZoneRef<'a> { // the end and start here refers to where the time starts prior to the transition // and where it ends up after. not the temporal relationship. let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset); - let transition_start = - transition.unix_leap_time + i64::from(prev.unwrap().ut_offset); + let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset); match transition_start.cmp(&transition_end) { Ordering::Greater => { // bakwards transition, eg from DST to regular // this means a given local time could have one of two possible offsets if local_leap_time < transition_end { - return Ok(crate::LocalResult::Single(prev.unwrap())); + return Ok(crate::LocalResult::Single(prev)); } else if local_leap_time >= transition_end && local_leap_time <= transition_start { - if prev.unwrap().ut_offset < after_ltt.ut_offset { - return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt)); + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::LocalResult::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap())); + return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); } } } Ordering::Equal => { // should this ever happen? presumably we have to handle it anyway. if local_leap_time < transition_start { - return Ok(crate::LocalResult::Single(prev.unwrap())); + return Ok(crate::LocalResult::Single(prev)); } else if local_leap_time == transition_end { - if prev.unwrap().ut_offset < after_ltt.ut_offset { - return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt)); + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::LocalResult::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap())); + return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); } } } @@ -268,7 +267,7 @@ impl<'a> TimeZoneRef<'a> { // forwards transition, eg from regular to DST // this means that times that are skipped are invalid local times if local_leap_time <= transition_start { - return Ok(crate::LocalResult::Single(prev.unwrap())); + return Ok(crate::LocalResult::Single(prev)); } else if local_leap_time < transition_end { return Ok(crate::LocalResult::None); } else if local_leap_time == transition_end { @@ -278,7 +277,7 @@ impl<'a> TimeZoneRef<'a> { } // try the next transition, we are fully after this one - prev = Some(after_ltt); + prev = after_ltt; } }; From 1b913f0ab4ec2533732a376132bd9537ed375aeb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 25 Jul 2023 12:47:47 +0200 Subject: [PATCH 447/999] Use offset specified by last transition if there is no TZ string --- src/offset/local/tz_info/timezone.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 2e9578176d..dd12a4566b 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -177,9 +177,16 @@ impl<'a> TimeZoneRef<'a> { match self.extra_rule { Some(extra_rule) => extra_rule, None => { - return Err(Error::FindLocalTimeType( - "no local time type is available for the specified timestamp", - )) + // RFC 8536 3.2: + // "Local time for timestamps on or after the last transition is + // specified by the TZ string in the footer (Section 3.3) if present + // and nonempty; otherwise, it is unspecified." + // + // Older versions of macOS (1.12 and before?) have TZif file with a + // missing TZ string, and use the offset given by the last transition. + return Ok( + &self.local_time_types[last_transition.local_time_type_index] + ); } } } else { @@ -224,7 +231,7 @@ impl<'a> TimeZoneRef<'a> { // if we have at least one transition, // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions - if !self.transitions.is_empty() { + let offset_after_last = if !self.transitions.is_empty() { let mut prev = self.local_time_types[0]; for transition in self.transitions { @@ -279,6 +286,10 @@ impl<'a> TimeZoneRef<'a> { // try the next transition, we are fully after this one prev = after_ltt; } + + prev + } else { + self.local_time_types[0] }; if let Some(extra_rule) = self.extra_rule { @@ -288,7 +299,7 @@ impl<'a> TimeZoneRef<'a> { err => err, } } else { - Ok(crate::LocalResult::Single(self.local_time_types[0])) + Ok(crate::LocalResult::Single(offset_after_last)) } } @@ -786,7 +797,7 @@ mod tests { assert_eq!(*time_zone_2.find_local_time_type(0)?, cet); assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc); - assert!(matches!(time_zone_3.find_local_time_type(0), Err(Error::FindLocalTimeType(_)))); + assert_eq!(*time_zone_3.find_local_time_type(0)?, utc); assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc); assert_eq!(*time_zone_4.find_local_time_type(0)?, cet); From c1811514a537278fcec068b0e9d54ddd611a3b49 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 25 Jul 2023 13:08:59 +0200 Subject: [PATCH 448/999] Include Guayaquil from macOS 10.11 as test --- src/offset/local/tz_info/timezone.rs | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index dd12a4566b..4cf6afe3d2 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -755,6 +755,38 @@ mod tests { Ok(()) } + #[test] + fn test_no_tz_string() -> Result<(), Error> { + // Guayaquil from macOS 10.11 + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x01\0\0\0\x02\0\0\0\x08\xb6\xa4B\x18\x01\xff\xff\xb6h\0\0\xff\xff\xb9\xb0\0\x04QMT\0ECT\0\0\0\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + dbg!(&time_zone); + + let time_zone_result = TimeZone::new( + vec![Transition::new(-1230749160, 1)], + vec![ + LocalTimeType::new(-18840, false, Some(b"QMT"))?, + LocalTimeType::new(-18000, false, Some(b"ECT"))?, + ], + Vec::new(), + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1500000000)?, + LocalTimeType::new(-18840, false, Some(b"QMT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(0)?, + LocalTimeType::new(-18000, false, Some(b"ECT"))? + ); + + Ok(()) + } + #[test] fn test_tz_ascii_str() -> Result<(), Error> { assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); From 54c15ad9788ed5d3dbaa682a96570e3ad5f9e0d4 Mon Sep 17 00:00:00 2001 From: xiang Date: Mon, 24 Jul 2023 07:58:12 -0400 Subject: [PATCH 449/999] Add unit test for uncovered regions (#1149) Co-authored-by: cxworks --- src/format/scan.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 8f11e195d9..32ca2659f2 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -366,8 +366,12 @@ enum CommentState { #[cfg(test)] mod tests { - use super::comment_2822; + use super::{ + comment_2822, nanosecond, nanosecond_fixed, short_or_long_month0, short_or_long_weekday, + timezone_offset_2822, + }; use crate::format::{INVALID, TOO_SHORT}; + use crate::Weekday; #[test] fn test_rfc2822_comments() { @@ -402,4 +406,42 @@ mod tests { ); } } + + #[test] + fn test_timezone_offset_2822() { + assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", Some(-21600))); + assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", Some(-28800))); + assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", Some(-25200))); + assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", Some(-57060))); + assert_eq!(timezone_offset_2822("Gp").unwrap(), ("", None)); + } + + #[test] + fn test_short_or_long_month0() { + assert_eq!(short_or_long_month0("JUn").unwrap(), ("", 5)); + assert_eq!(short_or_long_month0("mAy").unwrap(), ("", 4)); + assert_eq!(short_or_long_month0("AuG").unwrap(), ("", 7)); + assert_eq!(short_or_long_month0("Aprâ").unwrap(), ("â", 3)); + assert_eq!(short_or_long_month0("JUl").unwrap(), ("", 6)); + assert_eq!(short_or_long_month0("mAr").unwrap(), ("", 2)); + assert_eq!(short_or_long_month0("Jan").unwrap(), ("", 0)); + } + + #[test] + fn test_short_or_long_weekday() { + assert_eq!(short_or_long_weekday("sAtu").unwrap(), ("u", Weekday::Sat)); + assert_eq!(short_or_long_weekday("thu").unwrap(), ("", Weekday::Thu)); + } + + #[test] + fn test_nanosecond_fixed() { + assert_eq!(nanosecond_fixed("", 0usize).unwrap(), ("", 0)); + assert!(nanosecond_fixed("", 1usize).is_err()); + } + + #[test] + fn test_nanosecond() { + assert_eq!(nanosecond("2Ù").unwrap(), ("Ù", 200000000)); + assert_eq!(nanosecond("8").unwrap(), ("", 800000000)); + } } From 20e73591fcd269b5535e0398f9e9a524e0667f41 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 10 May 2023 14:23:37 +0200 Subject: [PATCH 450/999] Raise MSRV to 1.57 --- .github/workflows/test.yml | 6 +++--- Cargo.toml | 2 +- README.md | 2 +- src/naive/date.rs | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9c3e572a4..32c894b4d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,8 +31,8 @@ jobs: - run: cargo test --all-features --color=always -- --color=always # later this may be able to be included with the below - # kept separate for now as the following don't compile on 1.56.1 - # * arbitrary + # kept separate for now as the following don't compile on 1.57 + # * arbitrary (requires 1.63 as of v1.3.0) rust_msrv: strategy: matrix: @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.56.1 + toolchain: 1.57 - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests # which are run elsewhere diff --git a/Cargo.toml b/Cargo.toml index acc61634df..2c27250ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2021" -rust-version = "1.56.0" +rust-version = "1.57.0" [lib] name = "chrono" diff --git a/README.md b/README.md index 639a09144d..113dfb38e5 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Optional features: ## Rust version requirements -The Minimum Supported Rust Version (MSRV) is currently **Rust 1.56.0**. +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done lightly. diff --git a/src/naive/date.rs b/src/naive/date.rs index 4afe9006b3..5a0551b475 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -227,8 +227,7 @@ impl NaiveDate { if year < MIN_YEAR || year > MAX_YEAR { return None; // Out-of-range } - // Enable debug check once the MSRV >= 1.57 (panicking in const feature) - // debug_assert!(YearFlags::from_year(year).0 == flags.0); + debug_assert!(YearFlags::from_year(year).0 == flags.0); match Of::new(ordinal, flags) { Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. From 307aa6d64da4d08394cc4078f6b5d4673a28b9c3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 10 May 2023 14:23:51 +0200 Subject: [PATCH 451/999] Add `try_opt` and `expect` macros --- src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8ef20fc58c..7da889b9d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,3 +531,25 @@ impl fmt::Debug for OutOfRange { #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} + +/// Workaround because `?` is not (yet) available in const context. +#[macro_export] +macro_rules! try_opt { + ($e:expr) => { + match $e { + Some(v) => v, + None => return None, + } + }; +} + +/// Workaround because `.expect()` is not (yet) available in const context. +#[macro_export] +macro_rules! expect { + ($e:expr, $m:literal) => { + match $e { + Some(v) => v, + None => panic!($m), + } + }; +} From a9cc8e9643a561a5fbdd1aa39255229a58d6ebb1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 10 May 2023 14:24:08 +0200 Subject: [PATCH 452/999] Make some methods on `NaiveTime` const --- src/naive/time/mod.rs | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index b0f730feb6..1e921267b9 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -19,6 +19,7 @@ use crate::format::{ }; use crate::oldtime::Duration as OldDuration; use crate::Timelike; +use crate::{expect, try_opt}; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; @@ -219,8 +220,8 @@ impl NaiveTime { #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] #[inline] #[must_use] - pub fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { - NaiveTime::from_hms_opt(hour, min, sec).expect("invalid time") + pub const fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { + expect!(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveTime` from hour, minute and second. @@ -262,8 +263,8 @@ impl NaiveTime { #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] #[inline] #[must_use] - pub fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).expect("invalid time") + pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { + expect!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and millisecond. @@ -292,10 +293,14 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn from_hms_milli_opt(hour: u32, min: u32, sec: u32, milli: u32) -> Option { - milli - .checked_mul(1_000_000) - .and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) + pub const fn from_hms_milli_opt( + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Option { + let nano = try_opt!(milli.checked_mul(1_000_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. @@ -309,8 +314,8 @@ impl NaiveTime { #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] #[inline] #[must_use] - pub fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).expect("invalid time") + pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { + expect!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. @@ -339,8 +344,14 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn from_hms_micro_opt(hour: u32, min: u32, sec: u32, micro: u32) -> Option { - micro.checked_mul(1_000).and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) + pub const fn from_hms_micro_opt( + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Option { + let nano = try_opt!(micro.checked_mul(1_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. @@ -354,8 +365,8 @@ impl NaiveTime { #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] #[inline] #[must_use] - pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).expect("invalid time") + pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { + expect!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. @@ -403,8 +414,8 @@ impl NaiveTime { #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] #[inline] #[must_use] - pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { - NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") + pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { + expect!(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. From 83c1d09e608c25a147e0224d4c1321531363e6e3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 07:56:53 +0200 Subject: [PATCH 453/999] Document error causes for `Datelike` trait --- src/traits.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 22174da209..bc1fee4f40 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -55,37 +55,101 @@ pub trait Datelike: Sized { /// 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. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for [`NaiveDate`]. + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// + /// [`NaiveDate`]: crate::NaiveDate + /// [`DateTime`]: crate::DateTime fn with_year(&self, year: i32) -> Option; /// Makes a new value with the month number (starting from 1) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_month(&self, month: u32) -> Option; /// Makes a new value with the month number (starting from 0) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month0` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_month0(&self, month0: u32) -> Option; /// Makes a new value with the day of month (starting from 1) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day(31)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_day(&self, day: u32) -> Option; /// Makes a new value with the day of month (starting from 0) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day0(30)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day0` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_day0(&self, day0: u32) -> Option; /// Makes a new value with the day of year (starting from 1) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_ordinal(&self, ordinal: u32) -> Option; /// Makes a new value with the day of year (starting from 0) changed. /// - /// Returns `None` when the resulting value would be invalid. + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal0` is out of range. + /// + /// [`DateTime`]: crate::DateTime fn with_ordinal0(&self, ordinal0: u32) -> Option; /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. From d4a4bcd93e6210dbf76c0fc6e018c0fb10de22f1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 08:25:11 +0200 Subject: [PATCH 454/999] Add warning against combining multiple `Datelike::with_*` --- src/traits.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index bc1fee4f40..079db53d83 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,6 +1,33 @@ use crate::{IsoWeek, Weekday}; /// The common set of methods for date component. +/// +/// Methods such as [`year`], [`month`], [`day`] and [`weekday`] can be used to get basic +/// information about the date. +/// +/// The `with_*` methods can change the date. +/// +/// # Warning +/// +/// The `with_*` methods can be convenient to change a single component of a date, but they must be +/// used with some care. Examples to watch out for: +/// +/// - [`with_year`] changes the year component of a year-month-day value. Don't use this method if +/// you want the ordinal to stay the same after changing the year, of if you want the week and +/// weekday values to stay the same. +/// - Don't combine two `with_*` methods to change two components of the date. For example to +/// change both the year and month components of a date. This could fail because an intermediate +/// value does not exist, while the final date would be valid. +/// +/// For more complex changes to a date, it is best to use the methods on [`NaiveDate`] to create a +/// new value instead of altering an existing date. +/// +/// [`year`]: Datelike::year +/// [`month`]: Datelike::month +/// [`day`]: Datelike::day +/// [`weekday`]: Datelike::weekday +/// [`with_year`]: Datelike::with_year +/// [`NaiveDate`]: crate::NaiveDate pub trait Datelike: Sized { /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). fn year(&self) -> i32; @@ -55,6 +82,10 @@ pub trait Datelike: Sized { /// Makes a new value with the year number changed, while keeping the same month and day. /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// /// # Errors /// /// Returns `None` when: @@ -66,6 +97,25 @@ pub trait Datelike: Sized { /// /// [`NaiveDate`]: crate::NaiveDate /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 5, 13).unwrap() + /// ); + /// // Resulting date 2023-02-29 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().with_year(2023).is_none()); + /// + /// // Don't use `with_year` if you want the ordinal date to stay the same: + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` fn with_year(&self, year: i32) -> Option; /// Makes a new value with the month number (starting from 1) changed. @@ -80,6 +130,37 @@ pub trait Datelike: Sized { /// - The value for `month` is out of range. /// /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 9, 12).unwrap() + /// ); + /// // Resulting date 2023-09-31 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2023, 5, 31).unwrap().with_month(9).is_none()); + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); + /// ``` fn with_month(&self, month: u32) -> Option; /// Makes a new value with the month number (starting from 0) changed. From 869293ea2a455c4d0dd4d13c0d0e8fd095340004 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:15:46 +0200 Subject: [PATCH 455/999] Rename `diff_days()` to `add_days()` --- src/naive/date.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 5a0551b475..0152a39618 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -67,7 +67,7 @@ impl NaiveWeek { // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - self.date.diff_days(days as i64).unwrap() + self.date.add_days(days as i64).unwrap() } /// Returns a date representing the last day of the week. @@ -95,7 +95,7 @@ impl NaiveWeek { // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - self.date.diff_days(days as i64).unwrap() + self.date.add_days(days as i64).unwrap() } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -740,7 +740,7 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.diff_days(d)) + i64::try_from(days.0).ok().and_then(|d| self.add_days(d)) } /// Subtract a duration in [`Days`] from the date @@ -768,10 +768,10 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.diff_days(-d)) + i64::try_from(days.0).ok().and_then(|d| self.add_days(-d)) } - fn diff_days(self, days: i64) -> Option { + fn add_days(self, days: i64) -> Option { let secs = days.checked_mul(86400)?; // 86400 seconds in one day if secs >= core::i64::MAX / 1000 || secs <= core::i64::MIN / 1000 { return None; // See the `time` 0.1 crate. Outside these bounds, `Duration::seconds` will panic From 5a192a6ab6026d63516e4583777d593fba96afa4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:17:23 +0200 Subject: [PATCH 456/999] Change argument to add_days to i32 --- src/naive/date.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0152a39618..304d1dd937 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -67,7 +67,7 @@ impl NaiveWeek { // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - self.date.add_days(days as i64).unwrap() + self.date.add_days(days).unwrap() } /// Returns a date representing the last day of the week. @@ -95,7 +95,7 @@ impl NaiveWeek { // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - self.date.add_days(days as i64).unwrap() + self.date.add_days(days).unwrap() } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -740,7 +740,7 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.add_days(d)) + i32::try_from(days.0).ok().and_then(|d| self.add_days(d)) } /// Subtract a duration in [`Days`] from the date @@ -768,11 +768,11 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.add_days(-d)) + i32::try_from(days.0).ok().and_then(|d| self.add_days(-d)) } - fn add_days(self, days: i64) -> Option { - let secs = days.checked_mul(86400)?; // 86400 seconds in one day + fn add_days(self, days: i32) -> Option { + let secs = (days as i64).checked_mul(86400)?; // 86400 seconds in one day if secs >= core::i64::MAX / 1000 || secs <= core::i64::MIN / 1000 { return None; // See the `time` 0.1 crate. Outside these bounds, `Duration::seconds` will panic } From f8080619dbd8a93b92fd38ae01d8deafae409b0b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:20:21 +0200 Subject: [PATCH 457/999] Invert dependency between `checked_add_signed()` and `diff_days()` --- src/naive/date.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 304d1dd937..02582ea02e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -25,7 +25,7 @@ use crate::format::{ use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Duration, Weekday}; +use crate::{Datelike, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -771,12 +771,18 @@ impl NaiveDate { i32::try_from(days.0).ok().and_then(|d| self.add_days(-d)) } - fn add_days(self, days: i32) -> Option { - let secs = (days as i64).checked_mul(86400)?; // 86400 seconds in one day - if secs >= core::i64::MAX / 1000 || secs <= core::i64::MIN / 1000 { - return None; // See the `time` 0.1 crate. Outside these bounds, `Duration::seconds` will panic - } - self.checked_add_signed(Duration::seconds(secs)) + /// Add a duration of `i32` days to the date. + pub(crate) fn add_days(self, days: i32) -> Option { + let year = self.year(); + let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); + let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); + let cycle = (cycle as i32).checked_add(days)?; + let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); + year_div_400 += cycle_div_400y; + + let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. @@ -1124,16 +1130,8 @@ impl NaiveDate { /// ``` #[must_use] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_add(i32::try_from(rhs.num_days()).ok()?)?; - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + let days = i32::try_from(rhs.num_days()).ok()?; + self.add_days(days) } /// Subtracts the number of whole days in the given `Duration` from the current date. From 6cb69fbdedb5e47394ddf7aaf1cf911325ac8df3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:20:40 +0200 Subject: [PATCH 458/999] Use `add_days()` for `checked_sub_signed()` --- src/naive/date.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 02582ea02e..4667c2b700 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1156,16 +1156,8 @@ impl NaiveDate { /// ``` #[must_use] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_sub(i32::try_from(rhs.num_days()).ok()?)?; - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + let days = i32::try_from(-rhs.num_days()).ok()?; + self.add_days(days) } /// Subtracts another `NaiveDate` from the current date. From 9b658e5a736cbec9532fec6ce707682900174aa2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:20:21 +0200 Subject: [PATCH 459/999] Move check for 0 into `add_days()` --- src/naive/date.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4667c2b700..542559ebe1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -736,10 +736,6 @@ impl NaiveDate { /// ``` #[must_use] pub fn checked_add_days(self, days: Days) -> Option { - if days.0 == 0 { - return Some(self); - } - i32::try_from(days.0).ok().and_then(|d| self.add_days(d)) } @@ -764,15 +760,14 @@ impl NaiveDate { /// ``` #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { - if days.0 == 0 { - return Some(self); - } - i32::try_from(days.0).ok().and_then(|d| self.add_days(-d)) } /// Add a duration of `i32` days to the date. pub(crate) fn add_days(self, days: i32) -> Option { + if days == 0 { + return Some(self); + } let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); From 4ceac07a34d3817529414cb5e11b163df031b97c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 29 Apr 2023 08:45:55 +0200 Subject: [PATCH 460/999] Make methods of `NaiveDate` const --- src/naive/date.rs | 177 ++++++++++++++++++++++++-------------- src/naive/datetime/mod.rs | 2 +- 2 files changed, 113 insertions(+), 66 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 542559ebe1..a56e3628da 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -25,6 +25,7 @@ use crate::format::{ use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; +use crate::{expect, try_opt}; use crate::{Datelike, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; @@ -255,8 +256,8 @@ impl NaiveDate { /// 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 { - NaiveDate::from_ymd_opt(year, month, day).expect("invalid or out-of-range date") + pub const fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { + expect!(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) @@ -303,8 +304,8 @@ impl NaiveDate { /// `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 { - NaiveDate::from_yo_opt(year, ordinal).expect("invalid or out-of-range date") + pub const fn from_yo(year: i32, ordinal: u32) -> NaiveDate { + expect!(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) @@ -333,7 +334,7 @@ impl NaiveDate { /// assert!(from_yo_opt(-400000, 1).is_none()); /// ``` #[must_use] - pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { + pub const fn from_yo_opt(year: i32, ordinal: u32) -> Option { let flags = YearFlags::from_year(year); NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } @@ -348,8 +349,8 @@ impl NaiveDate { /// 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 { - NaiveDate::from_isoywd_opt(year, week, weekday).expect("invalid or out-of-range date") + pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { + expect!(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) @@ -402,7 +403,7 @@ impl NaiveDate { /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); /// ``` #[must_use] - pub fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { + pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { let flags = YearFlags::from_year(year); let nweeks = flags.nisoweeks(); if 1 <= week && week <= nweeks { @@ -443,8 +444,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] #[inline] #[must_use] - pub fn from_num_days_from_ce(days: i32) -> NaiveDate { - NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") + pub const fn from_num_days_from_ce(days: i32) -> NaiveDate { + expect!(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") } /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with @@ -470,9 +471,10 @@ impl NaiveDate { /// assert_eq!(from_ndays_opt(-100_000_000), None); /// ``` #[must_use] - pub fn from_num_days_from_ce_opt(days: i32) -> Option { - let days = days.checked_add(365)?; // make December 31, 1 BCE equal to day 0 - let (year_div_400, cycle) = div_mod_floor(days, 146_097); + pub const fn from_num_days_from_ce_opt(days: i32) -> Option { + let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 + let year_div_400 = days.div_euclid(146_097); + let cycle = days.rem_euclid(146_097); let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) @@ -490,8 +492,13 @@ impl NaiveDate { /// `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 { - NaiveDate::from_weekday_of_month_opt(year, month, weekday, n).expect("out-of-range date") + pub const fn from_weekday_of_month( + year: i32, + month: u32, + weekday: Weekday, + n: u8, + ) -> NaiveDate { + expect!(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week @@ -515,7 +522,7 @@ impl NaiveDate { /// NaiveDate::from_ymd_opt(2017, 3, 10)) /// ``` #[must_use] - pub fn from_weekday_of_month_opt( + pub const fn from_weekday_of_month_opt( year: i32, month: u32, weekday: Weekday, @@ -524,9 +531,9 @@ impl NaiveDate { if n == 0 { return None; } - let first = NaiveDate::from_ymd_opt(year, month, 1)?.weekday(); + let first = try_opt!(NaiveDate::from_ymd_opt(year, month, 1)).weekday(); let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; - let day = (u32::from(n) - 1) * 7 + first_to_dow + 1; + let day = (n - 1) as u32 * 7 + first_to_dow + 1; NaiveDate::from_ymd_opt(year, month, day) } @@ -622,7 +629,7 @@ impl NaiveDate { /// ); /// ``` #[must_use] - pub fn checked_add_months(self, months: Months) -> Option { + pub const fn checked_add_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); } @@ -657,7 +664,7 @@ impl NaiveDate { /// ); /// ``` #[must_use] - pub fn checked_sub_months(self, months: Months) -> Option { + pub const fn checked_sub_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); } @@ -669,7 +676,7 @@ impl NaiveDate { } } - fn diff_months(self, months: i32) -> Option { + const fn diff_months(self, months: i32) -> Option { let (years, left) = ((months / 12), (months % 12)); // Determine new year (without taking months into account for now @@ -706,9 +713,13 @@ impl NaiveDate { let flags = YearFlags::from_year(year); let feb_days = if flags.ndays() == 366 { 29 } else { 28 }; let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - let day = Ord::min(self.day(), days[(month - 1) as usize]); + let day_max = days[(month - 1) as usize]; + let mut day = self.day(); + if day > day_max { + day = day_max; + }; - NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags)?) + NaiveDate::from_mdf(year, try_opt!(Mdf::new(month as u32, day, flags))) } /// Add a duration in [`Days`] to the date @@ -735,8 +746,11 @@ impl NaiveDate { /// ); /// ``` #[must_use] - pub fn checked_add_days(self, days: Days) -> Option { - i32::try_from(days.0).ok().and_then(|d| self.add_days(d)) + pub const fn checked_add_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(days.0 as i32), + false => None, + } } /// Subtract a duration in [`Days`] from the date @@ -759,19 +773,22 @@ impl NaiveDate { /// ); /// ``` #[must_use] - pub fn checked_sub_days(self, days: Days) -> Option { - i32::try_from(days.0).ok().and_then(|d| self.add_days(-d)) + pub const fn checked_sub_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(-(days.0 as i32)), + false => None, + } } /// Add a duration of `i32` days to the date. - pub(crate) fn add_days(self, days: i32) -> Option { + pub(crate) const fn add_days(self, days: i32) -> Option { if days == 0 { return Some(self); } let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_add(days)?; + let cycle = try_opt!((cycle as i32).checked_add(days)); let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; @@ -811,8 +828,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] #[must_use] - pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { - self.and_hms_opt(hour, min, sec).expect("invalid time") + pub const fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { + expect!(self.and_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. @@ -837,8 +854,9 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { - NaiveTime::from_hms_opt(hour, min, sec).map(|time| self.and_time(time)) + pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { + let time = try_opt!(NaiveTime::from_hms_opt(hour, min, sec)); + Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. @@ -852,8 +870,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] #[must_use] - pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { - self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") + pub const fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { + expect!(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. @@ -880,14 +898,15 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn and_hms_milli_opt( + pub const fn and_hms_milli_opt( &self, hour: u32, min: u32, sec: u32, milli: u32, ) -> Option { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).map(|time| self.and_time(time)) + let time = try_opt!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli)); + Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. @@ -915,8 +934,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] #[inline] #[must_use] - pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { - self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") + pub const fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { + expect!(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. @@ -943,14 +962,15 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn and_hms_micro_opt( + pub const fn and_hms_micro_opt( &self, hour: u32, min: u32, sec: u32, micro: u32, ) -> Option { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).map(|time| self.and_time(time)) + let time = try_opt!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro)); + Some(self.and_time(time)) } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. @@ -964,8 +984,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] #[inline] #[must_use] - pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { - self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") + pub const fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { + expect!(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. @@ -992,19 +1012,20 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn and_hms_nano_opt( + pub const fn and_hms_nano_opt( &self, hour: u32, min: u32, sec: u32, nano: u32, ) -> Option { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).map(|time| self.and_time(time)) + let time = try_opt!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano)); + Some(self.and_time(time)) } /// Returns the packed month-day-flags. #[inline] - fn mdf(&self) -> Mdf { + const fn mdf(&self) -> Mdf { self.of().to_mdf() } @@ -1018,8 +1039,8 @@ impl NaiveDate { /// /// Returns `None` when the resulting `NaiveDate` would be invalid. #[inline] - fn with_mdf(&self, mdf: Mdf) -> Option { - Some(self.with_of(mdf.to_of()?)) + const fn with_mdf(&self, mdf: Mdf) -> Option { + Some(self.with_of(try_opt!(mdf.to_of()))) } /// Makes a new `NaiveDate` with the packed ordinal-flags changed. @@ -1039,8 +1060,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] #[must_use] - pub fn succ(&self) -> NaiveDate { - self.succ_opt().expect("out of bound") + pub const fn succ(&self) -> NaiveDate { + expect!(self.succ_opt(), "out of bound") } /// Makes a new `NaiveDate` for the next calendar date. @@ -1060,7 +1081,7 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn succ_opt(&self) -> Option { + pub const fn succ_opt(&self) -> Option { match self.of().succ() { Some(of) => Some(self.with_of(of)), None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1), @@ -1075,8 +1096,8 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] #[must_use] - pub fn pred(&self) -> NaiveDate { - self.pred_opt().expect("out of bound") + pub const fn pred(&self) -> NaiveDate { + expect!(self.pred_opt(), "out of bound") } /// Makes a new `NaiveDate` for the previous calendar date. @@ -1096,7 +1117,7 @@ impl NaiveDate { /// ``` #[inline] #[must_use] - pub fn pred_opt(&self) -> Option { + pub const fn pred_opt(&self) -> Option { match self.of().pred() { Some(of) => Some(self.with_of(of)), None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), @@ -1183,10 +1204,10 @@ impl NaiveDate { let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = i64::from(internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal())); - let cycle2 = i64::from(internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal())); + let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal()) as i64; + let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal()) as i64; OldDuration::days( - (i64::from(year1_div_400) - i64::from(year2_div_400)) * 146_097 + (cycle1 - cycle2), + (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2), ) } @@ -1196,9 +1217,11 @@ impl NaiveDate { /// /// Returns `None` if `base < self`. #[must_use] - pub fn years_since(&self, base: Self) -> Option { + pub const fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); - if (self.month(), self.day()) < (base.month(), base.day()) { + // Comparing tuples is not (yet) possible in const context. Instead we combine month and + // day into one `u32` for easy comparison. + if (self.month() << 5 | self.day()) < (base.month() << 5 | base.day()) { years -= 1; } @@ -1389,6 +1412,30 @@ impl NaiveDate { NaiveWeek { date: *self, start } } + // This duplicates `Datelike::year()`, because trait methods can't be const yet. + #[inline] + const fn year(&self) -> i32 { + self.ymdf >> 13 + } + + // This duplicates `Datelike::month()`, because trait methods can't be const yet. + #[inline] + const fn month(&self) -> u32 { + self.mdf().month() + } + + // This duplicates `Datelike::day()`, because trait methods can't be const yet. + #[inline] + const fn day(&self) -> u32 { + self.mdf().day() + } + + // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. + #[inline] + const fn weekday(&self) -> Weekday { + self.of().weekday() + } + /// The minimum possible `NaiveDate` (January 1, 262145 BCE). pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; /// The maximum possible `NaiveDate` (December 31, 262143 CE). @@ -1408,7 +1455,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn year(&self) -> i32 { - self.ymdf >> 13 + self.year() } /// Returns the month number starting from 1. @@ -1425,7 +1472,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn month(&self) -> u32 { - self.mdf().month() + self.month() } /// Returns the month number starting from 0. @@ -1442,7 +1489,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn month0(&self) -> u32 { - self.mdf().month() - 1 + self.month() - 1 } /// Returns the day of month starting from 1. @@ -1482,7 +1529,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn day(&self) -> u32 { - self.mdf().day() + self.day() } /// Returns the day of month starting from 0. @@ -1570,7 +1617,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn weekday(&self) -> Weekday { - self.of().weekday() + self.weekday() } #[inline] @@ -2138,7 +2185,7 @@ impl Default for NaiveDate { } } -fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { +const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { (val.div_euclid(div), val.rem_euclid(div)) } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 6fa9bb31fb..78d6d0a57f 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -955,7 +955,7 @@ impl NaiveDateTime { impl Datelike for NaiveDateTime { /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). /// - /// See also the [`NaiveDate::year`] method. + /// See also the [`NaiveDate::year`](./struct.NaiveDate.html#method.year) method. /// /// # Example /// From 6d5d303c4b8b55fcb4dc4c07150a51ec30d41d38 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 12 Jul 2023 13:55:07 +0200 Subject: [PATCH 461/999] Add benchmark for `to_rfc3339_opts` --- benches/chrono.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/benches/chrono.rs b/benches/chrono.rs index 1cc5eb0659..3f74923af2 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -63,6 +63,21 @@ fn bench_datetime_to_rfc3339(c: &mut Criterion) { c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); } +fn bench_datetime_to_rfc3339_opts(c: &mut Criterion) { + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + c.bench_function("bench_datetime_to_rfc3339_opts", |b| { + b.iter(|| black_box(dt).to_rfc3339_opts(SecondsFormat::Nanos, true)) + }); +} + fn bench_year_flags_from_year(c: &mut Criterion) { c.bench_function("bench_year_flags_from_year", |b| { b.iter(|| { @@ -188,6 +203,7 @@ criterion_group!( bench_datetime_from_str, bench_datetime_to_rfc2822, bench_datetime_to_rfc3339, + bench_datetime_to_rfc3339_opts, bench_year_flags_from_year, bench_num_days_from_ce, bench_get_local_time, From dc9a05161aacdbbd01b6ad8d2e9240b42eba0b8a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 12 Jul 2023 14:02:43 +0200 Subject: [PATCH 462/999] use `NaiveTime::hms` in `write_rfc2822_inner` --- src/format/formatting.rs | 9 ++++++--- src/naive/time/mod.rs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 1df5939ab8..0bd73c7f58 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -531,6 +531,7 @@ pub(crate) fn write_rfc3339( // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. write!(w, "{:?}", dt)?; + OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, @@ -574,11 +575,13 @@ fn write_rfc2822_inner( write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; w.write_char(' ')?; - write_hundreds(w, t.hour() as u8)?; + + let (hour, min, sec) = t.hms(); + write_hundreds(w, hour as u8)?; w.write_char(':')?; - write_hundreds(w, t.minute() as u8)?; + write_hundreds(w, min as u8)?; w.write_char(':')?; - let sec = t.second() + t.nanosecond() / 1_000_000_000; + let sec = sec + t.nanosecond() / 1_000_000_000; write_hundreds(w, sec as u8)?; w.write_char(' ')?; OffsetFormat { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 1e921267b9..148bebb16e 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -812,7 +812,7 @@ impl NaiveTime { } /// Returns a triple of the hour, minute and second numbers. - fn hms(&self) -> (u32, u32, u32) { + pub(crate) fn hms(&self) -> (u32, u32, u32) { let sec = self.secs % 60; let mins = self.secs / 60; let min = mins % 60; From af7865170546ee5faaa38fa5bd07690eeaebf299 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 12 Jul 2023 14:12:52 +0200 Subject: [PATCH 463/999] Optimize `write_rfc3339` --- src/format/formatting.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 0bd73c7f58..46e0d62487 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -528,9 +528,42 @@ pub(crate) fn write_rfc3339( dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(w, "{:?}", dt)?; + let year = dt.date().year(); + if (0..=9999).contains(&year) { + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(w, "{:+05}", year)?; + } + w.write_char('-')?; + write_hundreds(w, dt.date().month() as u8)?; + w.write_char('-')?; + write_hundreds(w, dt.date().day() as u8)?; + + w.write_char('T')?; + + let (hour, min, mut sec) = dt.time().hms(); + let mut nano = dt.nanosecond(); + if nano >= 1_000_000_000 { + sec += 1; + nano -= 1_000_000_000; + } + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec; + write_hundreds(w, sec as u8)?; + + if nano == 0 { + } else if nano % 1_000_000 == 0 { + write!(w, ".{:03}", nano / 1_000_000)?; + } else if nano % 1_000 == 0 { + write!(w, ".{:06}", nano / 1_000)?; + } else { + write!(w, ".{:09}", nano)?; + } OffsetFormat { precision: OffsetPrecision::Minutes, From bce54858fd66e1594135b2f5e72acb0e88fa05c3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 12 Jul 2023 12:50:08 +0200 Subject: [PATCH 464/999] Optimize `to_rfc3339_opts` --- src/datetime/mod.rs | 58 +++++++++------------------------------- src/format/formatting.rs | 40 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8cf3814a82..d2a0c9b611 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -4,22 +4,20 @@ //! ISO 8601 date and time with time zone. #[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::string::{String, ToString}; +use alloc::string::String; use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] -use std::string::ToString; -#[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(any(feature = "alloc", feature = "std"))] -use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std"))] +use crate::format::{write_rfc3339, DelayedFormat}; use crate::format::{Fixed, Item}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -48,6 +46,7 @@ mod tests; /// /// See the `TimeZone::to_rfc3339_opts` function for usage. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[allow(clippy::manual_non_exhaustive)] pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, @@ -508,8 +507,11 @@ impl DateTime { #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339(&self) -> String { + // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. let mut result = String::with_capacity(32); - crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) + let naive = self.naive_local(); + let offset = self.offset.fix(); + write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) .expect("writing rfc3339 datetime to string should never fail"); result } @@ -542,46 +544,10 @@ impl DateTime { #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use crate::format::Numeric::*; - use crate::format::Pad::Zero; - use crate::SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &[Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - let dt = self.fixed_offset(); - match ssitem { - None => dt.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => dt.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } + let mut result = String::with_capacity(38); + write_rfc3339(&mut result, self.naive_local(), self.offset.fix(), secform, use_z) + .expect("writing rfc3339 datetime to string should never fail"); + result } /// The minimum possible `DateTime`. diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 46e0d62487..c0a877cd09 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -15,7 +15,7 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::{Datelike, Timelike, Weekday}; +use crate::{Datelike, SecondsFormat, Timelike, Weekday}; use super::locales; #[cfg(any(feature = "alloc", feature = "std"))] @@ -427,7 +427,13 @@ fn format_inner( // same as `%Y-%m-%dT%H:%M:%S%.f%:z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(w, NaiveDateTime::new(*d, *t), off)) + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(*d, *t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) } else { None } @@ -523,10 +529,13 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[cfg(any(feature = "alloc", feature = "std"))] +#[inline] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, + secform: SecondsFormat, + use_z: bool, ) -> fmt::Result { let year = dt.date().year(); if (0..=9999).contains(&year) { @@ -556,19 +565,28 @@ pub(crate) fn write_rfc3339( let sec = sec; write_hundreds(w, sec as u8)?; - if nano == 0 { - } else if nano % 1_000_000 == 0 { - write!(w, ".{:03}", nano / 1_000_000)?; - } else if nano % 1_000 == 0 { - write!(w, ".{:06}", nano / 1_000)?; - } else { - write!(w, ".{:09}", nano)?; - } + match secform { + SecondsFormat::Secs => {} + SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?, + SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?, + SecondsFormat::Nanos => write!(w, ".{:09}", nano)?, + SecondsFormat::AutoSi => { + if nano == 0 { + } else if nano % 1_000_000 == 0 { + write!(w, ".{:03}", nano / 1_000_000)? + } else if nano % 1_000 == 0 { + write!(w, ".{:06}", nano / 1_000)? + } else { + write!(w, ".{:09}", nano)? + } + } + SecondsFormat::__NonExhaustive => unreachable!(), + }; OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, - allow_zulu: false, + allow_zulu: use_z, padding: Pad::Zero, } .format(w, off) From e39078c288fb6cc6b336160c16d0ca547e782ee4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 20 Jul 2023 13:12:40 +0200 Subject: [PATCH 465/999] Add some examples to `Utc::now` and `Local::now` --- src/offset/local/mod.rs | 28 +++++++++++++++++++++++++++- src/offset/utc.rs | 23 ++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 9e7d30335a..a710d081de 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -113,7 +113,33 @@ impl Local { Local::now().date() } - /// Returns a `DateTime` which corresponds to the current date and time. + /// Returns a `DateTime` which corresponds to the current date, time and offset from + /// UTC. + /// + /// See also the similar [`Utc::now()`] which returns `DateTime`, i.e. without the local + /// offset. + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{DateTime, FixedOffset, Local}; + /// // Current local time + /// let now = Local::now(); + /// + /// // Current local date + /// let today = now.date_naive(); + /// + /// // Current local time, converted to `DateTime` + /// let now_fixed_offset = Local::now().fixed_offset(); + /// // or + /// let now_fixed_offset: DateTime = Local::now().into(); + /// + /// // Current time in some timezone (let's use +05:00) + /// // Note that it is usually more efficient to use `Utc::now` for this use case. + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Local::now().with_timezone(&offset); + /// ``` pub fn now() -> DateTime { Utc::now().with_timezone(&Local) } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index f531bcae67..d27f7877e1 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -59,7 +59,28 @@ impl Utc { Utc::now().date() } - /// Returns a `DateTime` which corresponds to the current date and time. + /// Returns a `DateTime` which corresponds to the current date and time in UTC. + /// + /// See also the similar [`Local::now()`] which returns `DateTime`, i.e. the local date + /// and time including offset from UTC. + /// + /// [`Local::now()`]: crate::Local::now + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{FixedOffset, Utc}; + /// // Current time in UTC + /// let now_utc = Utc::now(); + /// + /// // Current date in UTC + /// let today_utc = now_utc.date_naive(); + /// + /// // Current time in some timezone (let's use +05:00) + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Utc::now().with_timezone(&offset); + /// ``` #[cfg(not(all( target_arch = "wasm32", feature = "wasmbind", From 2e2374f106197dd43915f9403a4a746b71570e31 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 22 Jul 2023 07:16:27 +0200 Subject: [PATCH 466/999] Don't test the same thing twice in `test_date_extreme_offset` --- src/offset/fixed.rs | 44 ++++++++++++-------------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index f7f0425dde..acfae81085 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -262,45 +262,25 @@ mod tests { fn test_date_extreme_offset() { // starting from 0.3 we don't have an offset exceeding one day. // this makes everything easier! + let offset = FixedOffset::east_opt(86399).unwrap(); assert_eq!( - format!( - "{:?}", - FixedOffset::east_opt(86399) - .unwrap() - .with_ymd_and_hms(2012, 2, 29, 5, 6, 7) - .unwrap() - ), - "2012-02-29T05:06:07+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07+23:59:59" ); + let offset = FixedOffset::east_opt(-86399).unwrap(); assert_eq!( - format!( - "{:?}", - FixedOffset::east_opt(86399) - .unwrap() - .with_ymd_and_hms(2012, 2, 29, 5, 6, 7) - .unwrap() - ), - "2012-02-29T05:06:07+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(86399).unwrap(); assert_eq!( - format!( - "{:?}", - FixedOffset::west_opt(86399) - .unwrap() - .with_ymd_and_hms(2012, 3, 4, 5, 6, 7) - .unwrap() - ), - "2012-03-04T05:06:07-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(-86399).unwrap(); assert_eq!( - format!( - "{:?}", - FixedOffset::west_opt(86399) - .unwrap() - .with_ymd_and_hms(2012, 3, 4, 5, 6, 7) - .unwrap() - ), - "2012-03-04T05:06:07-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07+23:59:59" ); } From b63601debb9825fb64ff8dd471bab7d9fc2f98bf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 20 Jul 2023 16:55:35 +0200 Subject: [PATCH 467/999] Add example to `Weekday::num_days_from_monday` --- src/weekday.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/weekday.rs b/src/weekday.rs index f51e2178a0..a7abd94153 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -114,6 +114,19 @@ impl Weekday { /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + /// + /// # Example + /// + #[cfg_attr(not(feature = "clock"), doc = "```ignore")] + #[cfg_attr(feature = "clock", doc = "```rust")] + /// # use chrono::{Local, Datelike}; + /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays. + /// // Use `num_days_from_monday` to index into the array. + /// const MTWRFSU: [char; 7] = ['M', 'T', 'W', 'R', 'F', 'S', 'U']; + /// + /// let today = Local::now().weekday(); + /// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]); + /// ``` #[inline] pub const fn num_days_from_monday(&self) -> u32 { self.num_days_from(Weekday::Mon) From 3fbf34bd5e3e228ad2b01c42f6520024162cebb8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 21 Jul 2023 21:09:30 +0200 Subject: [PATCH 468/999] Fix deserialization of negative timestamps --- src/datetime/serde.rs | 24 ++++++++++++++++++------ src/naive/datetime/serde.rs | 20 ++++++++++---------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index a96f31a901..a411d38fe6 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -221,7 +221,10 @@ pub mod ts_nanoseconds { E: de::Error, { serde_from( - Utc.timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32), + Utc.timestamp_opt( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, + ), &value, ) } @@ -474,7 +477,10 @@ pub mod ts_microseconds { E: de::Error, { serde_from( - Utc.timestamp_opt(value / 1_000_000, ((value % 1_000_000) * 1_000) as u32), + Utc.timestamp_opt( + value.div_euclid(1_000_000), + (value.rem_euclid(1_000_000) * 1_000) as u32, + ), &value, ) } @@ -725,7 +731,7 @@ pub mod ts_milliseconds { where E: de::Error, { - serde_from(Utc.timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32), &value) + serde_from(Utc.timestamp_millis_opt(value), &value) } /// Deserialize a timestamp in milliseconds since the epoch @@ -914,8 +920,7 @@ pub mod ts_seconds { use serde::{de, ser}; use super::{serde_from, SecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; + use crate::{DateTime, LocalResult, TimeZone, Utc}; /// Serialize a UTC datetime into an integer number of seconds since the epoch /// @@ -994,7 +999,14 @@ pub mod ts_seconds { where E: de::Error, { - serde_from(Utc.timestamp_opt(value as i64, 0), &value) + serde_from( + if value > i64::MAX as u64 { + LocalResult::None + } else { + Utc.timestamp_opt(value as i64, 0) + }, + &value, + ) } } } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 1a97fb23d4..d1a740a89c 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -157,8 +157,11 @@ pub mod ts_nanoseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_opt( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, + ) + .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result @@ -166,8 +169,8 @@ pub mod ts_nanoseconds { E: de::Error, { NaiveDateTime::from_timestamp_opt( - value as i64 / 1_000_000_000, - (value as i64 % 1_000_000_000) as u32, + (value / 1_000_000_000) as i64, + (value % 1_000_000_000) as u32, ) .ok_or_else(|| E::custom(ne_timestamp(value))) } @@ -405,11 +408,8 @@ pub mod ts_microseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt( - value / 1_000_000, - ((value % 1_000_000) * 1000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_micros(value) + .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result @@ -656,7 +656,7 @@ pub mod ts_milliseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) + NaiveDateTime::from_timestamp_millis(value) .ok_or_else(|| E::custom(ne_timestamp(value))) } From 995419e1fd5d0868075a46e325cb496180ab8704 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 21 Jul 2023 21:24:27 +0200 Subject: [PATCH 469/999] Doctests negative values when deserializing timestamps --- src/datetime/serde.rs | 9 +++++++++ src/naive/datetime/serde.rs | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index a411d38fe6..0e58d95ed6 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -199,6 +199,9 @@ pub mod ts_nanoseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_999).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -455,6 +458,9 @@ pub mod ts_microseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -710,6 +716,9 @@ pub mod ts_milliseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_000_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index d1a740a89c..49dfb8390d 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -135,6 +135,9 @@ pub mod ts_nanoseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -262,6 +265,9 @@ pub mod ts_nanoseconds_option { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -386,6 +392,9 @@ pub mod ts_microseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -510,6 +519,9 @@ pub mod ts_microseconds_option { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -634,6 +646,9 @@ pub mod ts_milliseconds { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -758,6 +773,9 @@ pub mod ts_milliseconds_option { /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> From 2cc1db1dae67ee28e70bfb89b165d03676c4c630 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 10 Jul 2023 09:43:03 +0200 Subject: [PATCH 470/999] Allow missing seconds in `NaiveTime::from_str` --- src/naive/time/mod.rs | 14 ++++++++++++-- src/naive/time/tests.rs | 9 +++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 148bebb16e..0eca2d5008 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1335,26 +1335,36 @@ impl fmt::Display for NaiveTime { /// let t = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_234_567_890).unwrap(); // leap second /// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); /// +/// // Seconds are optional +/// let t = NaiveTime::from_hms_opt(23, 56, 0).unwrap(); +/// assert_eq!("23:56".parse::(), Ok(t)); +/// /// assert!("foo".parse::().is_err()); /// ``` impl str::FromStr for NaiveTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult { - const ITEMS: &[Item<'static>] = &[ + const HOUR_AND_MINUTE: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), + ]; + const SECOND_AND_NANOS: &[Item<'static>] = &[ Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), Item::Space(""), ]; + const TRAILING_WHITESPACE: [Item<'static>; 1] = [Item::Space("")]; let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; + let s = parse_and_remainder(&mut parsed, s, HOUR_AND_MINUTE.iter())?; + // Seconds are optional, don't fail if parsing them doesn't succeed. + let s = parse_and_remainder(&mut parsed, s, SECOND_AND_NANOS.iter()).unwrap_or(s); + parse(&mut parsed, s, TRAILING_WHITESPACE.iter())?; parsed.to_naive_time() } } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 944d86e187..7789c4accb 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -225,7 +225,7 @@ fn test_time_fmt() { } #[test] -fn test_date_from_str() { +fn test_time_from_str() { // valid cases let valid = [ "0:0:0", @@ -233,6 +233,7 @@ fn test_date_from_str() { "0:0:0.0000003", " 4 : 3 : 2.1 ", " 09:08:07 ", + " 09:08 ", " 9:8:07 ", "01:02:03", "4:3:2.1", @@ -293,9 +294,9 @@ fn test_date_from_str() { "", // empty "x", // invalid "15", // missing data - "15:8", // missing data - "15:8:x", // missing data, invalid data - "15:8:9x", // missing data, invalid data + "15:8:", // trailing colon + "15:8:x", // invalid data + "15:8:9x", // invalid data "23:59:61", // invalid second (out of bounds) "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) From 5f80aafe0deb22b0873c14826f5af79f120bd814 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 25 Jul 2023 07:54:43 +0200 Subject: [PATCH 471/999] Make `write_rfc3339` available in `no_std` --- src/format/formatting.rs | 16 ++++++---------- src/format/mod.rs | 5 +++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index c0a877cd09..429b9658b0 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -10,19 +10,17 @@ use core::borrow::Borrow; use core::fmt; use core::fmt::Write; +use crate::datetime::SecondsFormat; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::Offset; +use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std"))] -use crate::{Datelike, SecondsFormat, Timelike, Weekday}; +use crate::{NaiveDate, NaiveTime, Weekday}; use super::locales; +use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] -use super::{ - Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, - OffsetPrecision, Pad, -}; +use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; use locales::*; /// A *temporary* object which can be used as an argument to `format!` or others. @@ -447,7 +445,6 @@ fn format_inner( } } -#[cfg(any(feature = "alloc", feature = "std"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -528,7 +525,6 @@ impl OffsetFormat { } /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std"))] #[inline] pub(crate) fn write_rfc3339( w: &mut impl Write, diff --git a/src/format/mod.rs b/src/format/mod.rs index 65598187e6..42ee3c2fb2 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -52,11 +52,12 @@ pub(crate) mod locales; pub(crate) use formatting::write_hundreds; #[cfg(any(feature = "alloc", feature = "std"))] +pub(crate) use formatting::write_rfc2822; +pub(crate) use formatting::write_rfc3339; +#[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; -#[cfg(any(feature = "alloc", feature = "std"))] -pub(crate) use formatting::{write_rfc2822, write_rfc3339}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; #[cfg(not(feature = "unstable-locales"))] From 15d5d4dad51a04ae0bd37f1e35b88302ba837acc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Apr 2023 11:24:14 +0200 Subject: [PATCH 472/999] Do not use `Offset`'s `Debug` impl when serializing `DateTime` --- src/datetime/mod.rs | 4 +- src/datetime/rustc_serialize.rs | 4 +- src/datetime/serde.rs | 73 ++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index d2a0c9b611..21a2b172ab 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1418,7 +1418,9 @@ where &FixedOffset::east_opt(3650).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ) .ok(), - Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) + // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. + // In this case `+01:00:50` becomes `+01:01` + Some(r#""2014-07-24T12:34:06+01:01""#.into()) ); } diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 8e75350d92..115ec9eb3f 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,6 +1,6 @@ #![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] -use super::DateTime; +use super::{DateTime, SecondsFormat}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; @@ -10,7 +10,7 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; impl Encodable for DateTime { fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) + self.to_rfc3339_opts(SecondsFormat::AutoSi, true).encode(s) } } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 0e58d95ed6..61f7f68840 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -3,11 +3,12 @@ use core::fmt; use serde::{de, ser}; -use super::DateTime; +use super::{DateTime, SecondsFormat}; +use crate::format::write_rfc3339; use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, TimeZone, Utc}; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; #[doc(hidden)] #[derive(Debug)] @@ -25,7 +26,7 @@ pub struct MicroSecondsTimestampVisitor; #[derive(Debug)] pub struct MilliSecondsTimestampVisitor; -/// Serialize into a rfc3339 time string +/// Serialize into an ISO 8601 formatted string. /// /// See [the `serde` module](./serde/index.html) for alternate /// serializations. @@ -34,18 +35,19 @@ impl ser::Serialize for DateTime { where S: ser::Serializer, { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, + struct FormatIso8601<'a, Tz: TimeZone> { + inner: &'a DateTime, } - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + impl<'a, Tz: TimeZone> fmt::Display for FormatIso8601<'a, Tz> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) + let naive = self.inner.naive_local(); + let offset = self.inner.offset.fix(); + write_rfc3339(f, naive, offset, SecondsFormat::AutoSi, true) } } - // Debug formatting is correct RFC3339, and it allows Zulu. - serializer.collect_str(&FormatWrapped { inner: &self }) + serializer.collect_str(&FormatIso8601 { inner: self }) } } @@ -1154,7 +1156,8 @@ mod tests { #[cfg(feature = "clock")] use crate::datetime::test_decodable_json; use crate::datetime::test_encodable_json; - use crate::{DateTime, TimeZone, Utc}; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use core::fmt; #[test] fn test_serde_serialize() { @@ -1183,4 +1186,54 @@ mod tests { assert_eq!(dt, decoded); assert_eq!(dt.offset(), decoded.offset()); } + + #[test] + fn test_serde_no_offset_debug() { + use crate::{LocalResult, NaiveDate, NaiveDateTime, Offset}; + use core::fmt::Debug; + + #[derive(Clone)] + struct TestTimeZone; + impl Debug for TestTimeZone { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TEST") + } + } + impl TimeZone for TestTimeZone { + type Offset = TestTimeZone; + fn from_offset(_state: &TestTimeZone) -> TestTimeZone { + TestTimeZone + } + fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { + LocalResult::Single(TestTimeZone) + } + fn offset_from_local_datetime( + &self, + _local: &NaiveDateTime, + ) -> LocalResult { + LocalResult::Single(TestTimeZone) + } + fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { + TestTimeZone + } + fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> TestTimeZone { + TestTimeZone + } + } + impl Offset for TestTimeZone { + fn fix(&self) -> FixedOffset { + FixedOffset::east_opt(15 * 60 * 60).unwrap() + } + } + + let tz = TestTimeZone; + assert_eq!(format!("{:?}", &tz), "TEST"); + + let dt = tz.with_ymd_and_hms(2023, 4, 24, 21, 10, 33).unwrap(); + let encoded = serde_json::to_string(&dt).unwrap(); + dbg!(&encoded); + let decoded: DateTime = serde_json::from_str(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset().fix(), *decoded.offset()); + } } From f946cf5a32902e370abd1d4755e4e458d541fd17 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:20:54 -0700 Subject: [PATCH 473/999] Add workflow grcov code coverage report using codecov.io Add github workflow to run grcov code coverage over `cargo test` and upload report to codecov.io --- .github/workflows/grcov.yml | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/grcov.yml diff --git a/.github/workflows/grcov.yml b/.github/workflows/grcov.yml new file mode 100644 index 0000000000..97422d0334 --- /dev/null +++ b/.github/workflows/grcov.yml @@ -0,0 +1,103 @@ +name: grcov +on: + push: + branches: [main, 0.4.x] + pull_request: +jobs: + # Run code coverage with rust "stable" then upload code coverage result to + # codecov.io + # Help from: + # - https://github.com/marketplace/actions/rust-grcov + # - https://eipi.xyz/blog/rust-code-coverage-with-github-workflows/ + # - https://github.com/mozilla/grcov/blob/v0.8.18/README.md#example-how-to-generate-gcda-files-for-a-rust-project + # - https://github.com/mozilla/grcov/blob/v0.8.18/README.md#lcov-output + job_grcov: + name: grcov + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v3 + - name: get toolchain stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo install + uses: actions-rs/cargo@v1 + with: + command: install + args: -- rustfilt grcov + - name: cargo build with special profiling + uses: actions-rs/cargo@v1 + env: + CARGO_INCREMENTAL: 0 + RUSTC_BOOTSTRAP: 1 + RUSTDOCFLAGS: -Cpanic=abort + RUSTFLAGS: -Zprofile + -Ccodegen-units=1 + -Copt-level=0 + -Clink-dead-code + -Coverflow-checks=off + -Zpanic_abort_tests + -Cpanic=abort + with: + command: build + - name: cargo test with special profiling + uses: actions-rs/cargo@v1 + env: + CARGO_INCREMENTAL: 0 + RUSTC_BOOTSTRAP: 1 + RUSTDOCFLAGS: -Cpanic=abort + RUSTFLAGS: -Zprofile + -Ccodegen-units=1 + -Copt-level=0 + -Clink-dead-code + -Coverflow-checks=off + -Zpanic_abort_tests + -Cpanic=abort + with: + command: test + args: --no-fail-fast --all-targets + - name: grcov + shell: bash + env: + CARGO_INCREMENTAL: 0 + RUSTC_BOOTSTRAP: 1 + RUSTDOCFLAGS: -Cpanic=abort + RUSTFLAGS: -Zprofile + -Ccodegen-units=1 + -Copt-level=0 + -Clink-dead-code + -Coverflow-checks=off + -Zpanic_abort_tests + -Cpanic=abort + run: | + set -eux + grcov --version + grcov \ + . \ + --source-dir ./src \ + --log-level DEBUG \ + --llvm \ + --binary-path ./target/debug/ \ + --branch \ + --ignore-not-existing \ + --output-type lcov \ + --output-path lcov.info + # print output for manual sanity check of lcov.info file + ls -l + head lcov.info + # action from https://github.com/codecov/codecov-action + - name: upload codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info + verbose: true + fail_ci_if_error: true + # public repository does not need secret section `with: token` + # see + # https://about.codecov.io/blog/javascript-code-coverage-using-github-actions-and-codecov/ + # + # list of acceptable formats: + # https://docs.codecov.com/docs/supported-report-formats From 69d359dbaf1e4e5fc9e3fd64091b383b6b67addd Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:31:09 -0700 Subject: [PATCH 474/999] README link to codecov report --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 113dfb38e5..288d17314b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Chrono on crates.io][cratesio-image]][cratesio] [![Chrono on docs.rs][docsrs-image]][docsrs] [![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] +[![codecov.io][codecov-img]][codecov-link] [gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg [gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest @@ -14,6 +15,8 @@ [docsrs]: https://docs.rs/chrono [gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg [gitter]: https://gitter.im/chrono-rs/chrono +[codecov-img]: https://img.shields.io/codecov/c/github/chronotope/chrono?logo=codecov +[codecov-link]: https://codecov.io/gh/chronotope/chrono Chrono aims to provide all functionality needed to do correct operations on dates and times in the [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): From 95b826e9dce969ae1150611958dfcf68e88f1867 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:09:31 -0700 Subject: [PATCH 475/999] change code coverage from grcov to cargo-llvm-cov --- .github/workflows/codecov.yml | 25 +++++++++ .github/workflows/grcov.yml | 103 ---------------------------------- 2 files changed, 25 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/codecov.yml delete mode 100644 .github/workflows/grcov.yml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000000..f080f254a5 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,25 @@ +name: codecov +on: + push: + branches: [main, 0.4.x] + pull_request: +jobs: + # Run code coverage using cargo-llvm-cov then upload to codecov.io + job_code_coverage: + name: llvm-cov + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v3 + - name: Install Rust + run: rustup update stable + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Generate code coverage + run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info + fail_ci_if_error: true diff --git a/.github/workflows/grcov.yml b/.github/workflows/grcov.yml deleted file mode 100644 index 97422d0334..0000000000 --- a/.github/workflows/grcov.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: grcov -on: - push: - branches: [main, 0.4.x] - pull_request: -jobs: - # Run code coverage with rust "stable" then upload code coverage result to - # codecov.io - # Help from: - # - https://github.com/marketplace/actions/rust-grcov - # - https://eipi.xyz/blog/rust-code-coverage-with-github-workflows/ - # - https://github.com/mozilla/grcov/blob/v0.8.18/README.md#example-how-to-generate-gcda-files-for-a-rust-project - # - https://github.com/mozilla/grcov/blob/v0.8.18/README.md#lcov-output - job_grcov: - name: grcov - runs-on: ubuntu-latest - steps: - - name: git checkout - uses: actions/checkout@v3 - - name: get toolchain stable - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: cargo install - uses: actions-rs/cargo@v1 - with: - command: install - args: -- rustfilt grcov - - name: cargo build with special profiling - uses: actions-rs/cargo@v1 - env: - CARGO_INCREMENTAL: 0 - RUSTC_BOOTSTRAP: 1 - RUSTDOCFLAGS: -Cpanic=abort - RUSTFLAGS: -Zprofile - -Ccodegen-units=1 - -Copt-level=0 - -Clink-dead-code - -Coverflow-checks=off - -Zpanic_abort_tests - -Cpanic=abort - with: - command: build - - name: cargo test with special profiling - uses: actions-rs/cargo@v1 - env: - CARGO_INCREMENTAL: 0 - RUSTC_BOOTSTRAP: 1 - RUSTDOCFLAGS: -Cpanic=abort - RUSTFLAGS: -Zprofile - -Ccodegen-units=1 - -Copt-level=0 - -Clink-dead-code - -Coverflow-checks=off - -Zpanic_abort_tests - -Cpanic=abort - with: - command: test - args: --no-fail-fast --all-targets - - name: grcov - shell: bash - env: - CARGO_INCREMENTAL: 0 - RUSTC_BOOTSTRAP: 1 - RUSTDOCFLAGS: -Cpanic=abort - RUSTFLAGS: -Zprofile - -Ccodegen-units=1 - -Copt-level=0 - -Clink-dead-code - -Coverflow-checks=off - -Zpanic_abort_tests - -Cpanic=abort - run: | - set -eux - grcov --version - grcov \ - . \ - --source-dir ./src \ - --log-level DEBUG \ - --llvm \ - --binary-path ./target/debug/ \ - --branch \ - --ignore-not-existing \ - --output-type lcov \ - --output-path lcov.info - # print output for manual sanity check of lcov.info file - ls -l - head lcov.info - # action from https://github.com/codecov/codecov-action - - name: upload codecov - uses: codecov/codecov-action@v3 - with: - files: lcov.info - verbose: true - fail_ci_if_error: true - # public repository does not need secret section `with: token` - # see - # https://about.codecov.io/blog/javascript-code-coverage-using-github-actions-and-codecov/ - # - # list of acceptable formats: - # https://docs.codecov.com/docs/supported-report-formats From a71eaaf6161abbfc5a4534bc79b4e4dfb09e8571 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 21:38:22 +0200 Subject: [PATCH 476/999] [CI] Skip libc feature in `features_check` --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32c894b4d0..816dba1084 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,8 +88,7 @@ jobs: - 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 + --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc # run using `bash` on all platforms for consistent # line-continuation marks shell: bash From 6c75fd6905e0c1e54d35c788450d3fc3a3a063c9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 21:41:22 +0200 Subject: [PATCH 477/999] Also check tests --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 816dba1084..b9081cbb52 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,7 +88,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps serde,rkyv \ - --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc + --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc \ + --all-targets # run using `bash` on all platforms for consistent # line-continuation marks shell: bash From 80238b5e1b5f5a2c59e9efbcf3504484a1f83736 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 21:41:35 +0200 Subject: [PATCH 478/999] Deny warnings --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9081cbb52..136443b9b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,6 +93,8 @@ jobs: # run using `bash` on all platforms for consistent # line-continuation marks shell: bash + env: + RUSTFLAGS: "-D warnings" - run: cargo test --no-default-features - run: cargo test --no-default-features --features=alloc - run: cargo test --no-default-features --features=unstable-locales From a117e7ed4ff5213ce6de49b63e9a77455454ece0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 22:01:19 +0200 Subject: [PATCH 479/999] Correct feature gates --- src/format/formatting.rs | 22 ++++++++++++++++++++++ src/format/mod.rs | 11 +++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 429b9658b0..aa2608820d 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -10,17 +10,37 @@ use core::borrow::Borrow; use core::fmt; use core::fmt::Write; +#[cfg(any( + feature = "alloc", + feature = "std", + feature = "serde", + feature = "rustc-serialize" +))] use crate::datetime::SecondsFormat; #[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::Offset; +#[cfg(any( + feature = "alloc", + feature = "std", + feature = "serde", + feature = "rustc-serialize" +))] use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::{NaiveDate, NaiveTime, Weekday}; +#[cfg(any(feature = "alloc", feature = "std"))] use super::locales; +#[cfg(any( + feature = "alloc", + feature = "std", + feature = "serde", + feature = "rustc-serialize" +))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; +#[cfg(any(feature = "alloc", feature = "std"))] use locales::*; /// A *temporary* object which can be used as an argument to `format!` or others. @@ -445,6 +465,7 @@ fn format_inner( } } +#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -526,6 +547,7 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] +#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, diff --git a/src/format/mod.rs b/src/format/mod.rs index 42ee3c2fb2..6932a66f5b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -48,19 +48,26 @@ pub(crate) mod scan; pub mod strftime; +#[cfg(any(feature = "alloc", feature = "std"))] pub(crate) mod locales; pub(crate) use formatting::write_hundreds; #[cfg(any(feature = "alloc", feature = "std"))] pub(crate) use formatting::write_rfc2822; +#[cfg(any( + feature = "alloc", + feature = "std", + feature = "serde", + feature = "rustc-serialize" +))] pub(crate) use formatting::write_rfc3339; #[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] pub use locales::Locale; -#[cfg(not(feature = "unstable-locales"))] +#[cfg(all(not(feature = "unstable-locales"), any(feature = "alloc", feature = "std")))] pub(crate) use locales::Locale; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; From 0be4e7c1d67614683f1e9345ed7d7df2643f6f0b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 20:48:02 +0200 Subject: [PATCH 480/999] Add `.github/codecov.yml` and set threshold to 1% --- .github/codecov.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..236a6f1da9 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + project: + default: + threshold: 1% From 972b4ded75e7a944f70a84ab52b736a345974769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= Date: Mon, 21 Aug 2023 15:03:32 +0200 Subject: [PATCH 481/999] Do not depend on `android-tzdata` if the `clock` feature is not enabled All usages of `android-tzdata` types are within the `offset::local` module, but such module is not built if the `clock` feature is not enabled. Therefore, `android-tzdata` is effectively an unused dependency when the `clock` feature is disabled. To avoid downloading unnecessary crates on build time, let's make the dependency on `android-tzdata` conditional on enabling the `clock` feature, as it was done with e.g. `iana-time-zone`. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c27250ca7..ac55f10aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] std = [] -clock = ["std", "winapi", "iana-time-zone"] +clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = ["time"] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] @@ -50,7 +50,7 @@ winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "tim iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } [target.'cfg(target_os = "android")'.dependencies] -android-tzdata = "0.1.1" +android-tzdata = { version = "0.1.1", optional = true } [dev-dependencies] serde_json = { version = "1" } From 89a387f02b6b486d9c110da457e1396308b8d7e7 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sat, 29 Jul 2023 22:46:09 +0200 Subject: [PATCH 482/999] Fix typo "accepted" --- src/datetime/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 21a2b172ab..110b61254f 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1229,7 +1229,7 @@ where } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time +/// A space or a 'T' are accepted as the separator between the date and time /// parts. /// /// All of these examples are equivalent: @@ -1250,7 +1250,7 @@ impl str::FromStr for DateTime { } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time +/// A space or a 'T' are accepted as the separator between the date and time /// parts. /// /// All of these examples are equivalent: From 7eb6db156c11af9690432a20eb626bc403b1859e Mon Sep 17 00:00:00 2001 From: Brennan Vincent Date: Mon, 21 Aug 2023 09:47:49 -0400 Subject: [PATCH 483/999] Fix incorrect doc comment (seconds -> milliseconds) --- src/oldtime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oldtime.rs b/src/oldtime.rs index f343756ed4..a659e8a7fe 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -112,8 +112,8 @@ impl Duration { } /// Makes a new `Duration` with given number of seconds. - /// Panics when the duration is more than `i64::MAX` seconds - /// or less than `i64::MIN` seconds. + /// Panics when the duration is more than `i64::MAX` milliseconds + /// or less than `i64::MIN` milliseconds. #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { From c6bf89e8f62a72b669abc4bd4a175993a022139d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Aug 2023 12:18:00 +0200 Subject: [PATCH 484/999] Conversion between chrono::Duration and core::time::Duration doesn't need std --- src/oldtime.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/oldtime.rs b/src/oldtime.rs index a659e8a7fe..64ade7be6a 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -11,7 +11,6 @@ //! Temporal quantification use core::ops::{Add, Div, Mul, Neg, Sub}; -#[cfg(feature = "std")] use core::time::Duration as StdDuration; use core::{fmt, i64}; #[cfg(feature = "std")] @@ -292,7 +291,6 @@ impl Duration { /// /// This function errors when original duration is larger than the maximum /// value supported for this type. - #[cfg(feature = "std")] pub fn from_std(duration: StdDuration) -> Result { // We need to check secs as u64 before coercing to i64 if duration.as_secs() > MAX.secs as u64 { @@ -309,7 +307,6 @@ impl Duration { /// /// This function errors when duration is less than zero. As standard /// library implementation is limited to non-negative values. - #[cfg(feature = "std")] pub fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); @@ -443,11 +440,9 @@ impl fmt::Display for Duration { /// The `std::time::Duration` supports a range from zero to `u64::MAX` /// *seconds*, while this module supports signed range of up to /// `i64::MAX` of *milliseconds*. -#[cfg(feature = "std")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OutOfRangeError(()); -#[cfg(feature = "std")] impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Source duration value is out of range for the target type") @@ -455,7 +450,6 @@ impl fmt::Display for OutOfRangeError { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for OutOfRangeError { #[allow(deprecated)] fn description(&self) -> &str { @@ -488,11 +482,9 @@ impl arbitrary::Arbitrary<'_> for Duration { #[cfg(test)] mod tests { - #[cfg(feature = "std")] use super::OutOfRangeError; use super::{Duration, MAX, MIN}; - #[cfg(feature = "std")] - use std::time::Duration as StdDuration; + use core::time::Duration as StdDuration; #[test] fn test_duration() { @@ -712,7 +704,6 @@ mod tests { } #[test] - #[cfg(feature = "std")] fn test_to_std() { assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); @@ -725,7 +716,6 @@ mod tests { } #[test] - #[cfg(feature = "std")] fn test_from_std() { assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0))); From e602b4c83bcdca3e44c79af3b4ca907cbd75ab7e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Aug 2023 11:31:57 +0200 Subject: [PATCH 485/999] Rename chrono::Duration to OldDuration in tests --- src/datetime/tests.rs | 48 +++++++++++++------------- src/naive/datetime/tests.rs | 57 +++++++++++++++++-------------- src/naive/time/tests.rs | 68 ++++++++++++++++++------------------- 3 files changed, 90 insertions(+), 83 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 7686105275..bed22f4b0d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -3,7 +3,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::oldtime::Duration; +use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; #[derive(Clone)] @@ -54,7 +54,7 @@ impl TimeZone for DstTester { DstTester::TO_WINTER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() - Duration::hours(1)); + .and_time(DstTester::transition_start_local() - OldDuration::hours(1)); let local_to_summer_transition_start = NaiveDate::from_ymd_opt( local.year(), @@ -70,7 +70,7 @@ impl TimeZone for DstTester { DstTester::TO_SUMMER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() + Duration::hours(1)); + .and_time(DstTester::transition_start_local() + OldDuration::hours(1)); if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { LocalResult::Single(DstTester::summer_offset()) @@ -267,7 +267,7 @@ fn ymdhms_milli( fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(Duration::milliseconds(milli)) + .checked_add_signed(OldDuration::milliseconds(milli)) .unwrap() } @@ -287,7 +287,7 @@ fn ymdhms_micro( fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(Duration::microseconds(micro)) + .checked_add_signed(OldDuration::microseconds(micro)) .unwrap() } @@ -307,7 +307,7 @@ fn ymdhms_nano( fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(Duration::nanoseconds(nano)) + .checked_add_signed(OldDuration::nanoseconds(nano)) .unwrap() } @@ -328,7 +328,7 @@ fn ymdhms_milli_utc( ) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(Duration::milliseconds(milli)) + .checked_add_signed(OldDuration::milliseconds(milli)) .unwrap() } @@ -392,12 +392,12 @@ fn test_datetime_offset() { let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( - dt + Duration::seconds(3600 + 60 + 1), + dt + OldDuration::seconds(3600 + 60 + 1), Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() ); assert_eq!( dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), - Duration::seconds(-7 * 3600 - 3 * 60 - 3) + OldDuration::seconds(-7 * 3600 - 3 * 60 - 3) ); assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); @@ -1275,16 +1275,16 @@ fn test_years_elapsed() { // This is always at least one year because 1 year = 52.1775 weeks. let one_year_ago = - Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + Utc::now().date_naive() - OldDuration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. let two_year_ago = - Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + Utc::now().date_naive() - OldDuration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::now().date_naive() + Duration::weeks(12); + let future = Utc::now().date_naive() + OldDuration::weeks(12); assert_eq!(Utc::now().date_naive().years_since(future), None); } @@ -1294,20 +1294,20 @@ fn test_datetime_add_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_add = datetime; - datetime_add += Duration::seconds(60); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + datetime_add += OldDuration::seconds(60); + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); } #[test] @@ -1320,8 +1320,8 @@ fn test_datetime_add_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_add += Duration::days(1); - assert_eq!(datetime_add, datetime + Duration::days(i)) + datetime_add += OldDuration::days(1); + assert_eq!(datetime_add, datetime + OldDuration::days(i)) } } @@ -1331,20 +1331,20 @@ fn test_datetime_sub_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_sub = datetime; - datetime_sub -= Duration::minutes(90); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + datetime_sub -= OldDuration::minutes(90); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); } #[test] @@ -1357,8 +1357,8 @@ fn test_datetime_sub_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_sub -= Duration::days(1); - assert_eq!(datetime_sub, datetime - Duration::days(i)) + datetime_sub -= OldDuration::days(1); + assert_eq!(datetime_sub, datetime - OldDuration::days(i)) } } diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 2df0bf1b21..0ec38202de 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::oldtime::Duration; +use crate::oldtime::Duration as OldDuration; use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; @@ -97,7 +97,7 @@ fn test_datetime_from_timestamp() { fn test_datetime_add() { fn check( (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: Duration, + rhs: OldDuration, result: Option<(i32, u32, u32, u32, u32, u32)>, ) { let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); @@ -108,12 +108,16 @@ fn test_datetime_add() { assert_eq!(lhs.checked_sub_signed(-rhs), sum); } - check((2014, 5, 6, 7, 8, 9), Duration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check( + (2014, 5, 6, 7, 8, 9), + OldDuration::seconds(-(3600 + 60 + 1)), + Some((2014, 5, 6, 6, 7, 8)), + ); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. @@ -123,17 +127,17 @@ fn test_datetime_add() { check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + Duration::seconds(86399), + max_days_from_year_0 + OldDuration::seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + OldDuration::seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), OldDuration::max_value(), None); let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - OldDuration::seconds(1), None); + check((0, 1, 1, 0, 0, 0), OldDuration::min_value(), None); } #[test] @@ -141,22 +145,25 @@ fn test_datetime_sub() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let since = NaiveDateTime::signed_duration_since; - assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), Duration::zero()); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), + OldDuration::zero() + ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::seconds(1) + OldDuration::seconds(1) ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(-1) + OldDuration::seconds(-1) ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(86399) + OldDuration::seconds(86399) ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - Duration::seconds(999_999_999) + OldDuration::seconds(999_999_999) ); } @@ -165,9 +172,9 @@ fn test_datetime_addassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += Duration::minutes(10_000_000); + date += OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += Duration::days(10); + date += OldDuration::days(10); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); } @@ -176,9 +183,9 @@ fn test_datetime_subassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= Duration::minutes(10_000_000); + date -= OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= Duration::days(10); + date -= OldDuration::days(10); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } @@ -315,7 +322,7 @@ fn test_datetime_add_sub_invariant() { // issue #37 let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let t = -946684799990000; - let time = base + Duration::microseconds(t); + let time = base + OldDuration::microseconds(t); assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); } @@ -344,7 +351,7 @@ fn test_nanosecond_range() { fn test_nanosecond_just_beyond_range() { let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + Duration::milliseconds(300); + let beyond_max = parsed + OldDuration::milliseconds(300); let _ = beyond_max.timestamp_nanos(); } @@ -353,7 +360,7 @@ fn test_nanosecond_just_beyond_range() { fn test_nanosecond_far_beyond_range() { let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + Duration::days(365); + let beyond_max = parsed + OldDuration::days(365); let _ = beyond_max.timestamp_nanos(); } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 7789c4accb..2ba2a17b9c 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,5 +1,5 @@ use super::NaiveTime; -use crate::oldtime::Duration; +use crate::oldtime::Duration as OldDuration; use crate::Timelike; #[test] @@ -94,23 +94,23 @@ fn test_time_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300)); + check!(hmsm(3, 5, 7, 900), OldDuration::zero(), hmsm(3, 5, 7, 900)); + check!(hmsm(3, 5, 7, 900), OldDuration::milliseconds(100), hmsm(3, 5, 8, 0)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 6, 500)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 7, 500)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 7, 1_400)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(800), hmsm(3, 5, 8, 100)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(1800), hmsm(3, 5, 9, 100)); + check!(hmsm(3, 5, 7, 900), OldDuration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap + check!(hmsm(3, 5, 7, 900), OldDuration::seconds(-86399), hmsm(3, 5, 8, 900)); + check!(hmsm(3, 5, 7, 900), OldDuration::days(12345), hmsm(3, 5, 7, 900)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::days(1), hmsm(3, 5, 7, 300)); + check!(hmsm(3, 5, 7, 1_300), OldDuration::days(-1), hmsm(3, 5, 8, 300)); // regression tests for #37 - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); + check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-990), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-9990), hmsm(23, 59, 50, 10)); } #[test] @@ -118,25 +118,25 @@ fn test_time_overflowing_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), + hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(11)), (hmsm(14, 4, 5, 678), 0) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)), + hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(23)), (hmsm(2, 4, 5, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)), + hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(-7)), (hmsm(20, 4, 5, 678), -86_400) ); // overflowing_add_signed with leap seconds may be counter-intuitive assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)), + hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(1)), (hmsm(3, 4, 5, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)), + hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(-1)), (hmsm(3, 4, 6, 678), -86_400) ); } @@ -145,9 +145,9 @@ fn test_time_overflowing_add() { fn test_time_addassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time += Duration::hours(10); + time += OldDuration::hours(10); assert_eq!(time, hms(22, 12, 12)); - time += Duration::hours(10); + time += OldDuration::hours(10); assert_eq!(time, hms(8, 12, 12)); } @@ -155,9 +155,9 @@ fn test_time_addassignment() { fn test_time_subassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time -= Duration::hours(10); + time -= OldDuration::hours(10); assert_eq!(time, hms(2, 12, 12)); - time -= Duration::hours(10); + time -= OldDuration::hours(10); assert_eq!(time, hms(16, 12, 12)); } @@ -173,25 +173,25 @@ fn test_time_sub() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1)); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), OldDuration::zero()); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), OldDuration::milliseconds(300)); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), OldDuration::seconds(3600 + 60 + 1)); check!( hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 300), - Duration::seconds(3600 + 60) + Duration::milliseconds(900) + OldDuration::seconds(3600 + 60) + OldDuration::milliseconds(900) ); // treats the leap second as if it coincides with the prior non-leap second, // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400)); + check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(400)); + check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); + check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); // additional equality: `time1 + duration = time2` is equivalent to // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); + assert_eq!(hmsm(3, 5, 6, 800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); + assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); } #[test] From a9fb2cc131a9989200ffae5855b851bc5870427e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 28 Aug 2023 10:31:55 +0200 Subject: [PATCH 486/999] Implement operations for core::time::Duration --- src/datetime/mod.rs | 43 +++++++++++++++++++++++++++++++++++-- src/datetime/tests.rs | 21 ++++++++++++++++++ src/naive/datetime/mod.rs | 37 +++++++++++++++++++++++++++++++ src/naive/datetime/tests.rs | 21 ++++++++++++++++++ src/naive/time/mod.rs | 41 +++++++++++++++++++++++++++++++++++ src/naive/time/tests.rs | 15 +++++++++++++ 6 files changed, 176 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 110b61254f..04913da2e6 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -9,6 +9,7 @@ use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; @@ -1131,6 +1132,17 @@ impl Add for DateTime { } } +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: Duration) -> DateTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") + } +} + impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1141,6 +1153,15 @@ impl AddAssign for DateTime { } } +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + *self += rhs; + } +} + impl Add for DateTime { type Output = DateTime; @@ -1158,6 +1179,17 @@ impl Sub for DateTime { } } +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: Duration) -> DateTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") + } +} + impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1168,6 +1200,15 @@ impl SubAssign for DateTime { } } +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + *self -= rhs; + } +} + impl Sub for DateTime { type Output = DateTime; @@ -1305,8 +1346,6 @@ impl From for DateTime { #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { - use std::time::Duration; - let sec = dt.timestamp(); let nsec = dt.timestamp_subsec_nanos(); if sec < 0 { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index bed22f4b0d..c7244f8b15 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1362,6 +1362,27 @@ fn test_datetime_sub_assign_local() { } } +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + let same = utc_dt + Duration::ZERO; + assert_eq!(utc_dt, same); + + utc_dt += Duration::new(3600, 0); + assert_eq!(utc_dt, Utc.with_ymd_and_hms(2023, 8, 29, 12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + #[test] #[cfg(all(target_os = "windows", feature = "clock"))] fn test_from_naive_date_time_windows() { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 78d6d0a57f..4943e764d4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -7,6 +7,7 @@ use core::borrow::Borrow; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, str}; #[cfg(feature = "rkyv")] @@ -1516,6 +1517,17 @@ impl Add for NaiveDateTime { } } +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveDateTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") + } +} + impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1523,6 +1535,13 @@ impl AddAssign for NaiveDateTime { } } +impl AddAssign for NaiveDateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1625,6 +1644,17 @@ impl Sub for NaiveDateTime { } } +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveDateTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") + } +} + impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1632,6 +1662,13 @@ impl SubAssign for NaiveDateTime { } } +impl SubAssign for NaiveDateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs); + } +} + /// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 0ec38202de..b1062e169c 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -189,6 +189,27 @@ fn test_datetime_subassignment() { assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + let same = dt + Duration::ZERO; + assert_eq!(dt, same); + + dt += Duration::new(3600, 0); + assert_eq!(dt, NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + #[test] fn test_datetime_timestamp() { let to_timestamp = |y, m, d, h, n, s| { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 0eca2d5008..4348e4ca79 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -6,6 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, str}; #[cfg(feature = "rkyv")] @@ -1112,6 +1113,26 @@ impl AddAssign for NaiveTime { } } +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.overflowing_add_signed(rhs).0 + } +} + +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + *self += rhs; + } +} + /// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. /// It is the same as the addition with a negated `Duration`. @@ -1174,6 +1195,26 @@ impl SubAssign for NaiveTime { } } +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveTime { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + self.overflowing_sub_signed(rhs).0 + } +} + +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + let rhs = OldDuration::from_std(rhs) + .expect("overflow converting from core::time::Duration to chrono::Duration"); + *self -= rhs; + } +} + /// Subtracts another `NaiveTime` from the current time. /// Returns a `Duration` within +/- 1 day. /// This does not overflow or underflow at all. diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 2ba2a17b9c..0a45381dea 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -194,6 +194,21 @@ fn test_time_sub() { assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); } +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut t = NaiveTime::from_hms_opt(11, 34, 23).unwrap(); + let same = t + Duration::ZERO; + assert_eq!(t, same); + + t += Duration::new(3600, 0); + assert_eq!(t, NaiveTime::from_hms_opt(12, 34, 23).unwrap()); + + t -= Duration::new(7200, 0); + assert_eq!(t, NaiveTime::from_hms_opt(10, 34, 23).unwrap()); +} + #[test] fn test_time_fmt() { assert_eq!( From 2d2f84743b7490e7cf5762c8bd6e2cddcb981e15 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Aug 2023 10:33:45 +0200 Subject: [PATCH 487/999] Use CODECOV_TOKEN when uploading coverage data --- .github/workflows/codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index f080f254a5..a9ccfae634 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -20,6 +20,8 @@ jobs: run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: lcov.info fail_ci_if_error: true From c2dd740543cbbab767c415461f0288e098d52a82 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 14:59:16 +0200 Subject: [PATCH 488/999] Fix rfc* tests with leap second The result of `checked_add_signed` will never be a leap second, so it is not suitable for creating test values with leap seconds. --- src/datetime/tests.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index c7244f8b15..6621ea0fcb 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,7 +4,7 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime}; +use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike}; #[derive(Clone)] struct DstTester; @@ -262,12 +262,12 @@ fn ymdhms_milli( hour: u32, min: u32, sec: u32, - milli: i64, + milli: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(OldDuration::milliseconds(milli)) + .with_nanosecond(milli * 1_000_000) .unwrap() } @@ -282,12 +282,12 @@ fn ymdhms_micro( hour: u32, min: u32, sec: u32, - micro: i64, + micro: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(OldDuration::microseconds(micro)) + .with_nanosecond(micro * 1000) .unwrap() } @@ -302,12 +302,12 @@ fn ymdhms_nano( hour: u32, min: u32, sec: u32, - nano: i64, + nano: u32, ) -> DateTime { fixedoffset .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(OldDuration::nanoseconds(nano)) + .with_nanosecond(nano) .unwrap() } @@ -324,11 +324,11 @@ fn ymdhms_milli_utc( hour: u32, min: u32, sec: u32, - milli: i64, + milli: u32, ) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap() - .checked_add_signed(OldDuration::milliseconds(milli)) + .with_nanosecond(milli * 1_000_000) .unwrap() } @@ -493,8 +493,8 @@ fn test_datetime_rfc2822() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), - "Thu, 19 Feb 2015 00:20:32 +0500" + ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), @@ -579,7 +579,7 @@ fn test_datetime_rfc3339() { ); assert_eq!( ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-19T00:00:00.234567+05:00" + "2015-02-18T23:59:60.234567+05:00" ); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), @@ -600,7 +600,7 @@ fn test_datetime_rfc3339() { assert_eq!( ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-19T00:00:00.234567+05:00" + "2015-02-18T23:59:60.234567+05:00" ); assert_eq!( ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), @@ -1489,7 +1489,6 @@ fn test_test_deprecated_from_offset() { #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] fn locale_decimal_point() { use crate::Locale::{ar_SY, nl_NL}; - use crate::Timelike; let dt = Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap(); From 39ad9b687470dc28bc43f454586f148e1cca946a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 15:10:24 +0200 Subject: [PATCH 489/999] Move checks for `parse_from_rfc2822` to the correct test --- src/datetime/tests.rs | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 6621ea0fcb..b005b24f6e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -471,6 +471,29 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap() + ) + .unwrap()) + ); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap()) + ); // seconds 60 assert_eq!( edt.from_local_datetime( @@ -614,29 +637,6 @@ fn test_datetime_rfc3339() { DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt5 - .from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 59, 59, 1_000) - .unwrap() - ) - .unwrap()) - ); - assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt5 - .from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - ) - .unwrap()) - ); assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); From 1c517cf43c9fb44e160e0ed785d2bb9487f9767a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Jun 2023 08:28:02 +0200 Subject: [PATCH 490/999] RFC 3339: accept space as separator between date and time From the standard: > NOTE: ISO 8601 defines date and time separated by "T". Applications using > this syntax may choose, for the sake of readability, to specify a full-date > and full-time separated by (say) a space character. The permissive parser already accepted a space, now the strict parser also accepts it. The vague wording suggest more characters than 'T' and ' ' might be allowed, but I have stopped at adding just a space. --- src/datetime/tests.rs | 5 ++++- src/format/parse.rs | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b005b24f6e..407f17e8af 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -637,6 +637,10 @@ fn test_datetime_rfc3339() { DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567)) + ); assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); @@ -646,7 +650,6 @@ fn test_datetime_rfc3339() { assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err()); - assert!(DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err()); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err()); assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err()); assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err()); diff --git a/src/format/parse.rs b/src/format/parse.rs index 5a39cef7ce..d726d9d1b9 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -190,6 +190,8 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. // note that this restriction is unique to RFC 3339 and not ISO 8601. // since this is not a typical Chrono behavior, we check it earlier. + // + // - For readability a full-date and a full-time may be separated by a space character. parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; s = scan::char(s, b'-')?; @@ -198,7 +200,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; s = match s.as_bytes().first() { - Some(&b't') | Some(&b'T') => &s[1..], + Some(&b't' | &b'T' | &b' ') => &s[1..], Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; @@ -1753,7 +1755,7 @@ mod tests { "2015-01-20T17:35:20.000000000452−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // too small with MINUS SIGN (U+2212) - ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015-01-20 17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T' ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year From 459b82f039b77936e14aaf7f291bc930eca5e221 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Jun 2023 08:39:13 +0200 Subject: [PATCH 491/999] Extract relaxed RFC 3339 parser from `FromStr` impl for `DateTime` This allows us to start using the permissive parser for the `RFC3339` item, which matches the documentation more closely. --- src/format/parse.rs | 80 ++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index d726d9d1b9..874f71b590 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -522,45 +522,57 @@ impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { - const DATE_ITEMS: &[Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - ]; - const TIME_ITEMS: &[Item<'static>] = &[ - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), - ]; - let mut parsed = Parsed::new(); - match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) { - Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { - if remainder.starts_with('T') || remainder.starts_with(' ') { - parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; - } else { - return Err(INVALID); - } - } - Err((_s, e)) => return Err(e), - Ok(_) => return Err(NOT_ENOUGH), - }; + parse_rfc3339_relaxed(&mut parsed, s)?; parsed.to_datetime() } } +/// Accepts a relaxed form of RFC3339. +/// +/// Differences with RFC3339: +/// - Values don't require padding to two digits. +/// - Years outside the range 0...=9999 are accepted, but they must include a sign. +/// - `UTC` is accepted as a valid timezone name/offset. +/// - There can be spaces between any of the components. +/// - The colon in the offset may be missing. +fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, s: &'a str) -> ParseResult<(&'a str, ())> { + const DATE_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + ]; + const TIME_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + Item::Fixed(Fixed::TimezoneOffsetZ), + Item::Space(""), + ]; + + match parse_internal(parsed, s, DATE_ITEMS.iter()) { + Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { + if remainder.starts_with('T') || remainder.starts_with(' ') { + parse(parsed, &remainder[1..], TIME_ITEMS.iter()).map(|_| (s, ())) + } else { + Err(INVALID) + } + } + Err((_s, e)) => Err(e), + Ok(_) => Err(NOT_ENOUGH), + } +} + #[cfg(test)] mod tests { use crate::format::*; From fb4371bcc113cf6891d50478d418e11f238f3b7c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 07:49:52 +0200 Subject: [PATCH 492/999] Don't consume trailing whitespace in `parse_rfc3339_relaxed` When used for the `RFC3339` formatting item it should not consume the trailing whitespace, only in the `from_str` method. --- src/format/parse.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 874f71b590..210a554c92 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -523,7 +523,10 @@ impl str::FromStr for DateTime { fn from_str(s: &str) -> ParseResult> { let mut parsed = Parsed::new(); - parse_rfc3339_relaxed(&mut parsed, s)?; + let (s, _) = parse_rfc3339_relaxed(&mut parsed, s)?; + if !s.trim_start().is_empty() { + return Err(TOO_LONG); + } parsed.to_datetime() } } @@ -536,7 +539,7 @@ impl str::FromStr for DateTime { /// - `UTC` is accepted as a valid timezone name/offset. /// - There can be spaces between any of the components. /// - The colon in the offset may be missing. -fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, s: &'a str) -> ParseResult<(&'a str, ())> { +fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), @@ -557,19 +560,20 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, s: &'a str) -> ParseResult<(&' Item::Fixed(Fixed::Nanosecond), Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), ]; - match parse_internal(parsed, s, DATE_ITEMS.iter()) { - Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { - if remainder.starts_with('T') || remainder.starts_with(' ') { - parse(parsed, &remainder[1..], TIME_ITEMS.iter()).map(|_| (s, ())) - } else { - Err(INVALID) - } - } + s = match parse_internal(parsed, s, DATE_ITEMS.iter()) { + Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => remainder, + Err((_s, e)) => return Err(e), + Ok(_) => return Err(NOT_ENOUGH), + }; + if !(s.starts_with('T') || s.starts_with(' ')) { + return Err(INVALID); + } + match parse_internal(parsed, &s[1..], TIME_ITEMS.iter()) { + Err((s, e)) if e.0 == ParseErrorKind::TooLong => Ok((s, ())), Err((_s, e)) => Err(e), - Ok(_) => Err(NOT_ENOUGH), + Ok(s) => Ok((s, ())), } } From ccd7f8545d4de6903e0ef266447ba4fdbe46b5cd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Jun 2023 08:46:54 +0200 Subject: [PATCH 493/999] Allow 't' as a seperator between date and time in `parse_rfc3339_relaxed` From the standard: > NOTE: Per [ABNF] and ISO8601, the "T" and "Z" characters in this syntax may > alternatively be lower case "t" or "z" respectively. The strict parser accepted 't', now the relaxed parser also does. --- src/datetime/tests.rs | 3 ++- src/format/parse.rs | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 407f17e8af..361b034d68 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -805,6 +805,8 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", "2001-02-03T04:05:06-01:00", + "2012-12-12 12:12:12Z", + "2012-12-12t12:12:12Z", "2012-12-12T12:12:12Z", "2012 -12-12T12:12:12Z", "2012 -12-12T12:12:12Z", @@ -871,7 +873,6 @@ fn test_parse_datetime_utc() { "2012-12-12T12:61:12Z", // invalid minute "2012-12-12T12:12:62Z", // invalid second "2012-12-12 T12:12:12Z", // space after date - "2012-12-12t12:12:12Z", // wrong divider 't' "2012-12-12T12:12:12ZZ", // trailing literal 'Z' "+802701-12-12T12:12:12Z", // invalid year (out of bounds) "+ 2012-12-12T12:12:12Z", // invalid space before year diff --git a/src/format/parse.rs b/src/format/parse.rs index 210a554c92..b3099672aa 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -567,10 +567,14 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult Err((_s, e)) => return Err(e), Ok(_) => return Err(NOT_ENOUGH), }; - if !(s.starts_with('T') || s.starts_with(' ')) { - return Err(INVALID); - } - match parse_internal(parsed, &s[1..], TIME_ITEMS.iter()) { + + s = match s.as_bytes().first() { + Some(&b't' | &b'T' | &b' ') => &s[1..], + Some(_) => return Err(INVALID), + None => return Err(TOO_SHORT), + }; + + match parse_internal(parsed, s, TIME_ITEMS.iter()) { Err((s, e)) if e.0 == ParseErrorKind::TooLong => Ok((s, ())), Err((_s, e)) => Err(e), Ok(s) => Ok((s, ())), From 3f8a5005e9244792715c71dc7aa0afd754d31e79 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Jun 2023 08:52:31 +0200 Subject: [PATCH 494/999] Use `parse_rfc3339` directly in `DateTime::parse_from_rfc3339` We want this to use the strict parser directly, so we can switch the `RFC3339` item to a relaxed parser. --- src/datetime/mod.rs | 12 ++++++++---- src/format/mod.rs | 3 ++- src/format/parse.rs | 10 ++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 04913da2e6..ec6ed4fe0e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -16,10 +16,12 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{ + parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed, + StrftimeItems, TOO_LONG, +}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{write_rfc3339, DelayedFormat}; -use crate::format::{Fixed, Item}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -699,9 +701,11 @@ impl DateTime { /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; + let (s, _) = parse_rfc3339(&mut parsed, s)?; + if !s.is_empty() { + return Err(TOO_LONG); + } parsed.to_datetime() } diff --git a/src/format/mod.rs b/src/format/mod.rs index 6932a66f5b..976eef1ac0 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -69,6 +69,7 @@ pub use formatting::{format_item_localized, format_localized}; pub use locales::Locale; #[cfg(all(not(feature = "unstable-locales"), any(feature = "alloc", feature = "std")))] pub(crate) use locales::Locale; +pub(crate) use parse::parse_rfc3339; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; pub use strftime::StrftimeItems; @@ -450,7 +451,7 @@ const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); -const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); +pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); // this implementation is here only because we need some private code from `scan` diff --git a/src/format/parse.rs b/src/format/parse.rs index b3099672aa..d288895084 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -155,7 +155,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st Ok((s, ())) } -fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { +pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ let (s_, v) = $e?; @@ -1817,15 +1817,9 @@ mod tests { ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; - fn rfc3339_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; - parsed.to_datetime() - } - // Test against test data above for &(date, checkdate) in testdates.iter() { - let dt = rfc3339_to_datetime(date); // parse a date + let dt = DateTime::::parse_from_rfc3339(date); if dt != checkdate { // check for expected result panic!( From e985f08868922d38b2b446977cebf19fe960fde3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Jun 2023 08:53:16 +0200 Subject: [PATCH 495/999] Switch `RFC3339` formatting item to relaxed parser to match documentation --- src/format/parse.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index d288895084..3f9fccd29b 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -488,7 +488,13 @@ where } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), - &RFC3339 => try_consume!(parse_rfc3339(parsed, s)), + &RFC3339 => { + // Used for the `%+` specifier, which has the description: + // "Same as `%Y-%m-%dT%H:%M:%S%.f%:z` (...) + // This format also supports having a `Z` or `UTC` in place of `%:z`." + // Use the relaxed parser to match this description. + try_consume!(parse_rfc3339_relaxed(parsed, s)) + } } } From 9ab02596a7a802b0df05f52859e5586b8772aa1b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 20 May 2023 20:57:49 +0200 Subject: [PATCH 496/999] Remove hack to accept "UTC" in `timezone_offset_zulu` --- src/datetime/tests.rs | 11 +++++++++++ src/format/parse.rs | 22 +++++++++++++++------- src/format/scan.rs | 17 ++--------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 361b034d68..4621260f85 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -743,6 +743,17 @@ fn test_datetime_from_str() { ) .unwrap()) ); + assert_eq!( + "2015-02-18T23:16:9.15Utc".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), diff --git a/src/format/parse.rs b/src/format/parse.rs index 3f9fccd29b..caac24c8bd 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -542,7 +542,8 @@ impl str::FromStr for DateTime { /// Differences with RFC3339: /// - Values don't require padding to two digits. /// - Years outside the range 0...=9999 are accepted, but they must include a sign. -/// - `UTC` is accepted as a valid timezone name/offset. +/// - `UTC` is accepted as a valid timezone name/offset (for compatibility with the debug format of +/// `DateTime`. /// - There can be spaces between any of the components. /// - The colon in the offset may be missing. fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { @@ -565,7 +566,6 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), Item::Space(""), - Item::Fixed(Fixed::TimezoneOffsetZ), ]; s = match parse_internal(parsed, s, DATE_ITEMS.iter()) { @@ -580,11 +580,19 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult None => return Err(TOO_SHORT), }; - match parse_internal(parsed, s, TIME_ITEMS.iter()) { - Err((s, e)) if e.0 == ParseErrorKind::TooLong => Ok((s, ())), - Err((_s, e)) => Err(e), - Ok(s) => Ok((s, ())), - } + s = match parse_internal(parsed, s, TIME_ITEMS.iter()) { + Err((s, e)) if e.0 == ParseErrorKind::TooLong => s, + Err((_s, e)) => return Err(e), + Ok(_) => return Err(NOT_ENOUGH), + }; + s = s.trim_start(); + let (s, offset) = if s.len() >= 3 && "UTC".eq_ignore_ascii_case(&s[..3]) { + (&s[3..], 0) + } else { + scan::timezone_offset(s, scan::colon_or_space, true, false, true)? + }; + parsed.set_offset(i64::from(offset))?; + Ok((s, ())) } #[cfg(test)] diff --git a/src/format/scan.rs b/src/format/scan.rs index 32ca2659f2..98f3673ff1 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -210,21 +210,8 @@ where F: FnMut(&str) -> ParseResult<&str>, { if allow_zulu { - let bytes = s.as_bytes(); - match bytes.first() { - Some(&b'z') | Some(&b'Z') => return Ok((&s[1..], 0)), - Some(&b'u') | Some(&b'U') => { - if bytes.len() >= 3 { - let (b, c) = (bytes[1], bytes[2]); - match (b | 32, c | 32) { - (b't', b'c') => return Ok((&s[3..], 0)), - _ => return Err(INVALID), - } - } else { - return Err(INVALID); - } - } - _ => {} + if let Some(&b'Z' | &b'z') = s.as_bytes().first() { + return Ok((&s[1..], 0)); } } From 30b6294ba111abce7de4cfb92567031b4c3b2f4b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Jul 2023 13:38:21 +0200 Subject: [PATCH 497/999] Switch to windows-bindgen --- Cargo.toml | 6 +++- src/offset/local/mod.rs | 4 +++ src/offset/local/win_bindings.rs | 51 +++++++++++++++++++++++++++++++ src/offset/local/win_bindings.txt | 6 ++++ src/offset/local/windows.rs | 5 ++- tests/win_bindings.rs | 22 +++++++++++++ 6 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/offset/local/win_bindings.rs create mode 100644 src/offset/local/win_bindings.txt create mode 100644 tests/win_bindings.rs diff --git a/Cargo.toml b/Cargo.toml index ac55f10aab..9689637bd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ name = "chrono" default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] +winapi = ["windows-targets"] std = [] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = ["time"] @@ -44,7 +45,10 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi", "sysinfoapi"], optional = true } +windows-targets = { version = "0.48", optional = true } + +[target.'cfg(windows)'.dev-dependencies] +windows-bindgen = { version = "0.51" } [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index a710d081de..d1f084a341 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -21,6 +21,10 @@ mod inner; #[path = "windows.rs"] mod inner; +#[cfg(all(windows, feature = "clock"))] +#[allow(unreachable_pub)] +mod win_bindings; + #[cfg(all( not(unix), not(windows), diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs new file mode 100644 index 0000000000..292c951414 --- /dev/null +++ b/src/offset/local/win_bindings.rs @@ -0,0 +1,51 @@ +// Bindings generated by `windows-bindgen` 0.51.1 + +#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] +::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); +::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); +::windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); +pub type BOOL = i32; +#[repr(C)] +pub struct FILETIME { + pub dwLowDateTime: u32, + pub dwHighDateTime: u32, +} +impl ::core::marker::Copy for FILETIME {} +impl ::core::clone::Clone for FILETIME { + fn clone(&self) -> Self { + *self + } +} +#[repr(C)] +pub struct SYSTEMTIME { + pub wYear: u16, + pub wMonth: u16, + pub wDayOfWeek: u16, + pub wDay: u16, + pub wHour: u16, + pub wMinute: u16, + pub wSecond: u16, + pub wMilliseconds: u16, +} +impl ::core::marker::Copy for SYSTEMTIME {} +impl ::core::clone::Clone for SYSTEMTIME { + fn clone(&self) -> Self { + *self + } +} +#[repr(C)] +pub struct TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, +} +impl ::core::marker::Copy for TIME_ZONE_INFORMATION {} +impl ::core::clone::Clone for TIME_ZONE_INFORMATION { + fn clone(&self) -> Self { + *self + } +} diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt new file mode 100644 index 0000000000..ce5d3acfe2 --- /dev/null +++ b/src/offset/local/win_bindings.txt @@ -0,0 +1,6 @@ +--out src/offset/local/win_bindings.rs +--config flatten sys +--filter + Windows.Win32.System.Time.SystemTimeToFileTime + Windows.Win32.System.Time.SystemTimeToTzSpecificLocalTime + Windows.Win32.System.Time.TzSpecificLocalTimeToSystemTime diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 84585170c2..539b6880fd 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -13,10 +13,9 @@ use std::io::Error; use std::ptr; use std::result::Result; -use winapi::shared::minwindef::FILETIME; -use winapi::um::minwinbase::SYSTEMTIME; -use winapi::um::timezoneapi::{ +use super::win_bindings::{ SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, + FILETIME, SYSTEMTIME, }; use super::FixedOffset; diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs new file mode 100644 index 0000000000..bfb110c38e --- /dev/null +++ b/tests/win_bindings.rs @@ -0,0 +1,22 @@ +#![cfg(all(windows, feature = "clock", feature = "std"))] + +use std::fs; +use windows_bindgen::bindgen; + +#[test] +fn gen_bindings() { + let input = "src/offset/local/win_bindings.txt"; + let output = "src/offset/local/win_bindings.rs"; + let existing = fs::read_to_string(output).unwrap(); + + let log = bindgen(["--etc", input]).unwrap(); + eprintln!("{}", log); + + // Check the output is the same as before. + // Depending on the git configuration the file may have been checked out with `\r\n` newlines or + // with `\n`. Compare line-by-line to ignore this difference. + let new = fs::read_to_string(output).unwrap(); + if !new.lines().eq(existing.lines()) { + panic!("generated file `{}` is changed.", output); + } +} From 00d389e522f3100572fd5334fa152d7e39b3934d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 28 Aug 2023 16:42:49 +0200 Subject: [PATCH 498/999] CI: Skip winapi feature in `cargo hack check` (already covered by the `clock` feature) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 136443b9b5..39b6b62cbb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,7 +88,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps serde,rkyv \ - --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc \ + --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc,winapi \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks From b5a9850aa14f4a21cd8f52210de731752006563f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Aug 2023 15:21:36 +0200 Subject: [PATCH 499/999] Bump version to 0.4.27 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 6ed9a60b1d..f3d6371af9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-05-31 -version: 0.4.26 +date-released: 2023-08-29 +version: 0.4.27 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 9689637bd0..1e12b4e7c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.26" +version = "0.4.27" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From b6a52442473672d7053b6f1f02700d34786856d9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Aug 2023 15:26:07 +0200 Subject: [PATCH 500/999] Add pitdicker as an author --- CITATION.cff | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CITATION.cff b/CITATION.cff index f3d6371af9..e5a1720ba9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -21,6 +21,9 @@ authors: - alias: esheppa family-names: Sheppard given-names: Eric + - alias: pitdicker + family-names: Dicker + given-names: Paul license: - Apache-2.0 - MIT From 80946ed4e71136b11e559624c8cf81ead86f994a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 30 Aug 2023 19:35:06 +0200 Subject: [PATCH 501/999] Fix `test_type_types` on 32-bit targets --- src/format/strftime.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 50d4ac7cc3..d7551457f7 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -906,11 +906,20 @@ mod tests { } #[test] - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))] fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 2); } + + #[test] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 12); + assert_eq!(size_of::(), 28); + assert_eq!(size_of::(), 2); + } } From 243d26d994641d621a102164a7a5716f24cb3747 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 30 Aug 2023 20:21:42 +0200 Subject: [PATCH 502/999] Bump version to 0.4.28 --- CITATION.cff | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index e5a1720ba9..b38c28af06 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,7 +4,7 @@ message: Please cite this crate using these information. # Version information. date-released: 2023-08-29 -version: 0.4.27 +version: 0.4.28 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 1e12b4e7c3..390b0e76ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.27" +version = "0.4.28" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 18f1c9057bc3937d8054b3680c424dc798ef8260 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 10:49:16 +0200 Subject: [PATCH 503/999] Revert "add test_issue_866" This reverts commit bf9f87638894dcfccb121a99653e29464be364bf. --- src/offset/local/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index d1f084a341..557f8db03e 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -257,12 +257,4 @@ mod tests { ); } } - - /// Test Issue #866 - #[test] - fn test_issue_866() { - #[allow(deprecated)] - let local_20221106 = Local.ymd(2022, 11, 6); - let _dt_20221106 = local_20221106.and_hms_milli_opt(1, 2, 59, 1000).unwrap(); - } } From f9d231b575b804171fe8cb59b68deb5b20b38813 Mon Sep 17 00:00:00 2001 From: Bill Fraser Date: Fri, 25 Aug 2023 14:09:13 -0700 Subject: [PATCH 504/999] Correct documentation for FromStr for Weekday and Month The parsing specifiers were wrong. --- src/format/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 976eef1ac0..2a16e1defb 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -456,7 +456,7 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); // this implementation is here only because we need some private code from `scan` -/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html). /// /// # Example /// @@ -492,7 +492,7 @@ impl FromStr for Weekday { } } -/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html). /// /// # Example /// From a1b9d0d9318ab4de1c831b93d6c86b55c8e15496 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 07:09:42 +0200 Subject: [PATCH 505/999] CI: merge jobs `timezones_linux` and `timezones_other` --- .github/workflows/test.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39b6b62cbb..8437bd7603 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,22 +6,10 @@ on: pull_request: jobs: - timezones_linux: + timezones: strategy: matrix: - os: [ubuntu-latest] - tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo test --all-features --color=always -- --color=always - - timezones_other: - strategy: - matrix: - os: [macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: From bbd5be55923b6ba0f03071f39fe9190137c298db Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 07:10:37 +0200 Subject: [PATCH 506/999] CI: test i686 Linux --- .github/workflows/test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8437bd7603..c9e6b581b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,6 +155,20 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} + cross-tests: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - run: cargo install cross + - uses: Swatinem/rust-cache@v2 + - run: cross test --lib --all-features --target i686-unknown-linux-gnu --color=always + - run: cross test --doc --all-features --target i686-unknown-linux-gnu --color=always + - run: cross test --lib --all-features --target i686-unknown-linux-musl --color=always + - run: cross test --doc --all-features --target i686-unknown-linux-musl --color=always + check-docs: runs-on: ubuntu-latest steps: From d92083d2ec4ae9c4113ddb352aff51c841c8c266 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 07:29:36 +0200 Subject: [PATCH 507/999] Disable `SystemTime` test on `wasm32-wasi` --- src/datetime/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 4621260f85..2077b99a6b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1185,8 +1185,11 @@ fn test_subsecond_part() { assert_eq!(1234567, datetime.timestamp_subsec_nanos()); } +// Some targets, such as `wasm32-wasi`, have a problematic definition of `SystemTime`, such as an +// `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`). +// See https://github.com/rust-lang/rust/issues/44394. #[test] -#[cfg(feature = "std")] +#[cfg(all(feature = "std", not(all(target_arch = "wasm32", target_os = "wasi"))))] fn test_from_system_time() { use std::time::{Duration, SystemTime, UNIX_EPOCH}; From 417fae8afcb197c8efff5ac71384bce22024b1f7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 07:31:03 +0200 Subject: [PATCH 508/999] CI: Remove `cargo-hack` from `test_wasm` --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9e6b581b1..1c7cc75910 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,7 +135,6 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v3 - uses: jetli/wasm-pack-action@v0.4.0 From 0fce0d85d34120cff6fb33bb7c34d71aec520a30 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 07:38:10 +0200 Subject: [PATCH 509/999] CI: run tests for `wasm32-wasi` target --- .github/workflows/test.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c7cc75910..00be669f85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,7 +109,6 @@ jobs: os: [ubuntu-latest] target: [ - wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, aarch64-linux-android, @@ -142,6 +141,27 @@ jobs: # with the host system. - run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind + test_wasi: + strategy: + matrix: + os: [ubuntu-latest] + target: + - wasm32-wasi + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasi + - uses: Swatinem/rust-cache@v2 + - run: cargo install cargo-wasi + - uses: mwilliamson/setup-wasmtime-action@v2 + with: + wasmtime-version: "12.0.1" + # We can't use `--all-features` because `rustc-serialize` doesn't support + # `wasm32-wasi`. + - run: cargo wasi test --features=serde,unstable-locales --color=always -- --color=always + cross-targets: strategy: matrix: From ecfad9e108c200f74ba1145a71e50ff3b1632ea3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 31 Aug 2023 20:51:40 +0200 Subject: [PATCH 510/999] Fix `test_time_zone_from_posix_tz` in environments without a tzdb --- src/offset/local/tz_info/timezone.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 4cf6afe3d2..02b6f341e2 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -859,8 +859,12 @@ mod tests { assert_eq!(time_zone_local, time_zone_local_1); } - let time_zone_utc = TimeZone::from_posix_tz("UTC")?; - assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); + // `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have + // a time zone database, like for example some docker containers. + // In that case skip the test. + if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") { + assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); + } } assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err()); From 1b25e4717c363c7a03fcd4982ea6307dbae842a8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 2 Sep 2023 19:29:12 +0200 Subject: [PATCH 511/999] Include doctests for code coverage --- .github/workflows/codecov.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index a9ccfae634..8b4f537ea8 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -12,12 +12,13 @@ jobs: CARGO_TERM_COLOR: always steps: - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update stable + # nightly is required for --doctests, see cargo-llvm-cov#2 + - name: Install Rust (nightly) + run: rustup update nightly - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info + run: cargo +nightly llvm-cov --all-features --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 env: From e39346e2877b3a5445953f2bc5b64263cf46fc72 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 1 Sep 2023 18:28:46 +0200 Subject: [PATCH 512/999] Move benchmarks to a separate crate --- .github/workflows/lint.yml | 4 ++++ .github/workflows/test.yml | 2 +- Cargo.toml | 13 +------------ bench/Cargo.toml | 26 ++++++++++++++++++++++++++ {benches => bench/benches}/chrono.rs | 1 - {benches => bench/benches}/serde.rs | 2 -- bench/src/lib.rs | 1 + 7 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 bench/Cargo.toml rename {benches => bench/benches}/chrono.rs (99%) rename {benches => bench/benches}/serde.rs (95%) create mode 100644 bench/src/lib.rs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6209a0ea65..1bbceb150c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,12 +17,16 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo fmt --check -- --color=always - run: cargo fmt --check --manifest-path fuzz/Cargo.toml + - run: cargo fmt --check --manifest-path bench/Cargo.toml - run: | cargo clippy --all-features --all-targets --color=always \ -- -D warnings - run: | cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ -- -D warnings + - run: | + cargo clippy --manifest-path bench/Cargo.toml --color=always \ + -- -D warnings env: RUSTFLAGS: "-Dwarnings" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 00be669f85..3c98174cb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v2 - - run: cargo check --benches + - run: cargo check --manifest-path bench/Cargo.toml --benches - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests # which are run elsewhere diff --git a/Cargo.toml b/Cargo.toml index 390b0e76ab..47a7159a95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = ["time"] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] -__internal_bench = ["criterion"] +__internal_bench = [] __doctest = [] [dependencies] @@ -35,7 +35,6 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.6", optional = true } -criterion = { version = "0.4.0", optional = true } rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } @@ -71,13 +70,3 @@ rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] features = ["serde"] - -[[bench]] -name = "chrono" -required-features = ["__internal_bench"] -harness = false - -[[bench]] -name = "serde" -required-features = ["__internal_bench", "serde"] -harness = false diff --git a/bench/Cargo.toml b/bench/Cargo.toml new file mode 100644 index 0000000000..4c14068877 --- /dev/null +++ b/bench/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "benches" +version = "0.1.0" +edition = "2021" + +# Even as a `dev-dependency` Criterion and its dependencies can affect the MSRV of chrono. +# But not when it lives in a separate crate :-). +# See https://github.com/chronotope/chrono/pull/1104. + +[lib] +name = "benches" + +[dependencies] +chrono = { path = "..", features = ["__internal_bench", "serde"] } + +[[bench]] +name = "chrono" +harness = false + +[[bench]] +name = "serde" +harness = false + +[dev-dependencies] +criterion = "0.5.0" +serde_json = "1" diff --git a/benches/chrono.rs b/bench/benches/chrono.rs similarity index 99% rename from benches/chrono.rs rename to bench/benches/chrono.rs index 3f74923af2..8cf7b15616 100644 --- a/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -1,5 +1,4 @@ //! Benchmarks for chrono that just depend on std -#![cfg(feature = "__internal_bench")] use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; diff --git a/benches/serde.rs b/bench/benches/serde.rs similarity index 95% rename from benches/serde.rs rename to bench/benches/serde.rs index e9de4408e9..ee4deb5a49 100644 --- a/benches/serde.rs +++ b/bench/benches/serde.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "__internal_bench")] - use criterion::{black_box, criterion_group, criterion_main, Criterion}; use chrono::NaiveDateTime; diff --git a/bench/src/lib.rs b/bench/src/lib.rs new file mode 100644 index 0000000000..d87371caaa --- /dev/null +++ b/bench/src/lib.rs @@ -0,0 +1 @@ +// This file only exists to make `benches` a valid crate. From 5a6b2b40a781c19ad34a3593313468d922fceeea Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 4 Sep 2023 18:55:05 +0200 Subject: [PATCH 513/999] Fix arbitrary string slicing in `parse_rfc3339_relaxed` --- src/datetime/tests.rs | 1 + src/format/parse.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2077b99a6b..a9de65353b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -804,6 +804,7 @@ fn test_datetime_from_str() { .unwrap()) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + assert!("2015-02-18T23:16:9.15øøø".parse::>().is_err()); // no test for `DateTime`, we cannot verify that much. } diff --git a/src/format/parse.rs b/src/format/parse.rs index caac24c8bd..a884903d22 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -586,7 +586,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult Ok(_) => return Err(NOT_ENOUGH), }; s = s.trim_start(); - let (s, offset) = if s.len() >= 3 && "UTC".eq_ignore_ascii_case(&s[..3]) { + let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { (&s[3..], 0) } else { scan::timezone_offset(s, scan::colon_or_space, true, false, true)? From 53b3f6bd57d05a498962e800abd4f7c855e83edc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 3 Sep 2023 13:39:48 +0200 Subject: [PATCH 514/999] Parse to `NaiveDateTime` in `test_utc_datetime_from_str_with_spaces` --- src/datetime/tests.rs | 51 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index a9de65353b..8de083fa11 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -312,6 +312,7 @@ fn ymdhms_nano( } // local helper function to easily create a DateTime +#[cfg(any(feature = "alloc", feature = "std"))] fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() } @@ -958,39 +959,37 @@ fn test_utc_datetime_from_str() { } #[test] -fn test_utc_datetime_from_str_with_spaces() { - let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); +fn test_datetime_parse_from_str_with_spaces() { + let parse_from_str = NaiveDateTime::parse_from_str; + let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); // with varying spaces - should succeed - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!( - Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), - Ok(dt), - ); - assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); - assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); // with varying spaces - should fail // leading space in data - assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); // trailing space in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); // trailing tab in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); // mismatched newlines - assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); // trailing literal in data - assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); + assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); } #[test] From d56049f52a577185bed27e40d3f978fb8884b486 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 5 Sep 2023 13:32:04 +0200 Subject: [PATCH 515/999] Move `test_datetime_parse_from_str_with_spaces`to `naive::datetime` --- src/datetime/tests.rs | 34 ---------------------------------- src/naive/datetime/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 8de083fa11..8ee5ec2445 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -958,40 +958,6 @@ fn test_utc_datetime_from_str() { // no test for `DateTime`, we cannot verify that much. } -#[test] -fn test_datetime_parse_from_str_with_spaces() { - let parse_from_str = NaiveDateTime::parse_from_str; - let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); - // with varying spaces - should succeed - assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); - assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); - assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); - assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); - assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); - // with varying spaces - should fail - // leading space in data - assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); - // trailing space in data - assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); - // trailing tab in data - assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); - // mismatched newlines - assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); - // trailing literal in data - assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); -} - #[test] fn test_datetime_parse_from_str() { let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index b1062e169c..66f3ff9957 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -338,6 +338,40 @@ fn test_datetime_parse_from_str() { ); } +#[test] +fn test_datetime_parse_from_str_with_spaces() { + let parse_from_str = NaiveDateTime::parse_from_str; + let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + // with varying spaces - should fail + // leading space in data + assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + #[test] fn test_datetime_add_sub_invariant() { // issue #37 From 935e7b595397544a5a55d7748e37be9fca44a091 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 3 Sep 2023 13:46:56 +0200 Subject: [PATCH 516/999] Parse to `NaiveDateTime` in `parse_rfc850` test The format string doesn't contain any offset info. --- src/format/parse.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index a884903d22..418fa424eb 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -598,7 +598,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult #[cfg(test)] mod tests { use crate::format::*; - use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; + use crate::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Timelike, Utc}; macro_rules! parsed { ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { @@ -1704,7 +1704,10 @@ mod tests { assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); // Check that it parses correctly - assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); + assert_eq!( + NaiveDateTime::parse_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT), + Ok(dt.naive_utc()) + ); // Check that the rest of the weekdays parse correctly (this test originally failed because // Sunday parsed incorrectly). @@ -1736,7 +1739,7 @@ mod tests { ]; for val in &testdates { - assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); + assert_eq!(NaiveDateTime::parse_from_str(val.1, RFC850_FMT), Ok(val.0.naive_utc())); } let test_dates_fail = [ @@ -1755,7 +1758,7 @@ mod tests { ]; for val in &test_dates_fail { - assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); + assert!(NaiveDateTime::parse_from_str(val, RFC850_FMT).is_err()); } } From 754ad18ed3ef29341a019818fab1c43e63855434 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 3 Sep 2023 13:49:13 +0200 Subject: [PATCH 517/999] Remove remaining uses of `TimeZone::datetime_from_str()` from tests and docs --- src/datetime/tests.rs | 6 +----- src/format/mod.rs | 4 ++-- src/lib.rs | 10 +++------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 8ee5ec2445..53d46682e8 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -898,7 +898,7 @@ fn test_parse_datetime_utc() { } #[test] -fn test_utc_datetime_from_str() { +fn test_parse_from_str() { let edt = FixedOffset::east_opt(570 * 60).unwrap(); let edt0 = FixedOffset::east_opt(0).unwrap(); let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); @@ -909,10 +909,6 @@ fn test_utc_datetime_from_str() { assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") .is_err()); - assert_eq!( - 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() diff --git a/src/format/mod.rs b/src/format/mod.rs index 2a16e1defb..d22cdf071a 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -18,14 +18,14 @@ //! # Example #![cfg_attr(not(feature = "std"), doc = "```ignore")] #![cfg_attr(feature = "std", doc = "```rust")] -//! use chrono::{TimeZone, Utc}; +//! use chrono::{NaiveDateTime, TimeZone, Utc}; //! //! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); //! //! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); //! assert_eq!(formatted, "2020-11-10 00:01:32"); //! -//! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?; +//! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc(); //! assert_eq!(parsed, date_time); //! # Ok::<(), chrono::ParseError>(()) //! ``` diff --git a/src/lib.rs b/src/lib.rs index 7da889b9d6..73919a1724 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -298,16 +298,12 @@ //! Ok(fixed_dt.clone())); //! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! -//! // method 3 -//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -//! assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); -//! //! // oops, the year is missing! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); //! // oops, the format string does not include the year at all! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); //! // oops, the weekday is incorrect! -//! assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); //! ``` //! //! Again : See [`format::strftime`](./format/strftime/index.html#specifiers) From 2a96a795caac805dac3d406ce144168fcb5cd6fe Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 3 Sep 2023 13:49:33 +0200 Subject: [PATCH 518/999] Deprecated `TimeZone::datetime_from_str` --- src/offset/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index ea53915309..419550e292 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -444,6 +444,7 @@ pub trait TimeZone: Sized + Clone { /// /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. + #[deprecated(since = "0.4.29", note = "use `DateTime::parse_from_str` instead")] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; From d7444990b7bd0ddfe2ef73314809f0278e4794c8 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 31 Aug 2023 16:58:04 +0200 Subject: [PATCH 519/999] Add Discord link to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 288d17314b..dde079804e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Chrono GitHub Actions][gh-image]][gh-checks] [![Chrono on crates.io][cratesio-image]][cratesio] [![Chrono on docs.rs][docsrs-image]][docsrs] -[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] +[![Chat][discord-image]][discord] [![codecov.io][codecov-img]][codecov-link] [gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg @@ -13,8 +13,8 @@ [cratesio]: https://crates.io/crates/chrono [docsrs-image]: https://docs.rs/chrono/badge.svg [docsrs]: https://docs.rs/chrono -[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg -[gitter]: https://gitter.im/chrono-rs/chrono +[discord-image]: https://img.shields.io/discord/976380008299917365?logo=discord +[discord]: https://discord.gg/sXpav4PS7M [codecov-img]: https://img.shields.io/codecov/c/github/chronotope/chrono?logo=codecov [codecov-link]: https://codecov.io/gh/chronotope/chrono From 8509c253646c3e378018d85f801999170926b75b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 5 Sep 2023 17:02:33 +0200 Subject: [PATCH 520/999] Bump version to 0.4.29 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index b38c28af06..7d70318271 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-08-29 -version: 0.4.28 +date-released: 2023-09-05 +version: 0.4.29 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 47a7159a95..a2b2554376 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.28" +version = "0.4.29" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 84334df989db941be889c59678379becde4d17cf Mon Sep 17 00:00:00 2001 From: Truong Tan Dat Date: Wed, 6 Sep 2023 15:48:15 +0200 Subject: [PATCH 521/999] Update readme --- src/lib.rs | 64 ++++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 73919a1724..3bcb789400 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,21 @@ //! # Chrono: Date and Time for Rust //! -//! It aims to be a feature-complete superset of -//! the [time](https://github.com/rust-lang-deprecated/time) library. -//! In particular, -//! -//! * Chrono strictly adheres to ISO 8601. -//! * Chrono is timezone-aware by default, with separate timezone-naive types. -//! * Chrono is space-optimal and (while not being the primary goal) reasonably efficient. -//! -//! There were several previous attempts to bring a good date and time library to Rust, -//! which Chrono builds upon and should acknowledge: -//! -//! * [Initial research on -//! the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -//! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -//! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) + +//! Chrono aims to provide all functionality needed to do correct operations on dates and times in the +//! [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): +//! +//! * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware +//! by default, with separate timezone-naive types. +//! * Operations that may produce an invalid or ambiguous date and time return `Option` or +//! [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). +//! * Configurable parsing and formatting with a `strftime` inspired date and time formatting syntax. +//! * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with +//! the current timezone of the OS. +//! * Types and operations are implemented to be reasonably efficient. +//! +//! Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate +//! [Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for +//! full timezone support. //! //! ### Features //! @@ -29,10 +30,13 @@ //! and traits. //! - `clock`: Enables reading the system time (`now`) that depends on the standard library for //! UNIX-like operating systems and the Windows API (`winapi`) for Windows. +//! - `wasmbind`: Interface with the JS Date API for the `wasm32` target. //! //! Optional features: //! //! - [`serde`][]: Enable serialization/deserialization via serde. +//! - `rkyv`: Enable serialization/deserialization via rkyv. +//! - `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate. //! - `unstable-locales`: Enable localization. This adds various methods with a //! `_localized` suffix. The implementation and API may change or even be //! removed in a patch release. Feedback welcome. @@ -353,30 +357,18 @@ //! //! ## Limitations //! -//! Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -//! Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. -//! -//! Date types are limited in about +/- 262,000 years from the common epoch. -//! Time types are limited in the nanosecond accuracy. -//! -//! [Leap seconds are supported in the representation but -//! Chrono doesn't try to make use of them](./naive/struct.NaiveTime.html#leap-second-handling). -//! (The main reason is that leap seconds are not really predictable.) -//! Almost *every* operation over the possible leap seconds will ignore them. -//! Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -//! if you want. +//! Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +//! Date types are limited to about +/- 262,000 years from the common epoch. +//! Time types are limited to nanosecond accuracy. +//! Leap seconds can be represented, but Chrono does not fully support them. +//! See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). //! -//! Chrono inherently does not support an inaccurate or partial date and time representation. -//! Any operation that can be ambiguous will return `None` in such cases. -//! For example, "a month later" of 2014-01-30 is not well-defined -//! and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. +//! ## Rust version requirements //! -//! Non ISO week handling is not yet supported. -//! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -//! crate ([sources](https://github.com/bcourtine/chrono-ext/)). +//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. //! -//! Advanced time zone handling is not yet supported. -//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. +//! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done +//! lightly. #![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] #![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 From 1903778a1529761e72c6d201ef034cd17ca5e6b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 7 May 2023 08:16:43 +0200 Subject: [PATCH 522/999] Add NaiveDate::leap_year --- src/naive/date.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index a56e3628da..53d698e3b1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1412,6 +1412,21 @@ impl NaiveDate { NaiveWeek { date: *self, start } } + /// Returns `true` if this is a leap year. + /// + /// ``` + /// # use chrono::NaiveDate; + /// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); + /// ``` + pub const fn leap_year(&self) -> bool { + self.ymdf & (0b1000) == 0 + } + // This duplicates `Datelike::year()`, because trait methods can't be const yet. #[inline] const fn year(&self) -> i32 { @@ -3205,6 +3220,16 @@ mod tests { assert!(dt.with_ordinal0(4294967295).is_none()); } + #[test] + fn test_leap_year() { + for year in 0..=MAX_YEAR { + let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + assert_eq!(date.leap_year(), is_leap); + assert_eq!(date.leap_year(), date.with_ordinal(366).is_some()); + } + } + // 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 From f4aefc7f3359be30d9b391277197c06dddc2cab0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 4 Sep 2023 19:19:02 +0200 Subject: [PATCH 523/999] Clarify `Timelike::num_seconds_from_midnight` is a simple mapping --- src/traits.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index 079db53d83..1094bd822b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -316,6 +316,11 @@ pub trait Timelike: Sized { fn with_nanosecond(&self, nano: u32) -> Option; /// Returns the number of non-leap seconds past the last midnight. + /// + /// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399. + /// + /// This method is not intended to provide the real number of seconds since midnight on a given + /// day. It does not take things like DST transitions into account. #[inline] fn num_seconds_from_midnight(&self) -> u32 { self.hour() * 3600 + self.minute() * 60 + self.second() From 8f5becd2774daabb172ea846fd5e5a0e192461f5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 May 2023 19:37:03 +0200 Subject: [PATCH 524/999] Drop time 0.1 as optional dependency --- Cargo.toml | 3 +-- src/lib.rs | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2b2554376..c2e058ad92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,14 +23,13 @@ libc = [] winapi = ["windows-targets"] std = [] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] -oldtime = ["time"] +oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] __internal_bench = [] __doctest = [] [dependencies] -time = { version = "0.1.43", optional = true } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } diff --git a/src/lib.rs b/src/lib.rs index 3bcb789400..c407171a8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,10 +385,6 @@ #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "oldtime")] -#[cfg_attr(docsrs, doc(cfg(feature = "oldtime")))] -extern crate time as oldtime; -#[cfg(not(feature = "oldtime"))] mod oldtime; // this reexport is to aid the transition and should not be in the prelude! pub use oldtime::Duration; From 27ea7e90073466032cbce08c5a3f340c38d66fe2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 May 2023 19:42:42 +0200 Subject: [PATCH 525/999] Rename `oldtime` module to `duration` --- src/date.rs | 4 ++-- src/datetime/mod.rs | 2 +- src/datetime/tests.rs | 2 +- src/{oldtime.rs => duration.rs} | 0 src/format/parsed.rs | 2 +- src/lib.rs | 7 +++---- src/naive/date.rs | 4 ++-- src/naive/datetime/mod.rs | 2 +- src/naive/datetime/tests.rs | 2 +- src/naive/time/mod.rs | 2 +- src/naive/time/tests.rs | 2 +- src/offset/fixed.rs | 2 +- src/round.rs | 2 +- 13 files changed, 16 insertions(+), 17 deletions(-) rename src/{oldtime.rs => duration.rs} (100%) diff --git a/src/date.rs b/src/date.rs index 15f3d436d1..5e84692370 100644 --- a/src/date.rs +++ b/src/date.rs @@ -13,13 +13,13 @@ use core::{fmt, hash}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::duration::Duration as OldDuration; #[cfg(feature = "unstable-locales")] use crate::format::Locale; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; -use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::{Datelike, Weekday}; @@ -576,7 +576,7 @@ where mod tests { use super::Date; - use crate::oldtime::Duration; + use crate::duration::Duration; use crate::{FixedOffset, NaiveDate, Utc}; #[cfg(feature = "clock")] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ec6ed4fe0e..650c63d0c0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -14,6 +14,7 @@ use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; +use crate::duration::Duration as OldDuration; #[cfg(feature = "unstable-locales")] use crate::format::Locale; use crate::format::{ @@ -26,7 +27,6 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; -use crate::oldtime::Duration as OldDuration; #[allow(deprecated)] use crate::Date; use crate::{Datelike, Months, Timelike, Weekday}; diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 53d46682e8..cf24cb4963 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,9 +1,9 @@ use super::DateTime; +use crate::duration::Duration as OldDuration; use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike}; #[derive(Clone)] diff --git a/src/oldtime.rs b/src/duration.rs similarity index 100% rename from src/oldtime.rs rename to src/duration.rs diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 431833ec7d..ad0d0013a3 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -5,9 +5,9 @@ //! They can be constructed incrementally while being checked for consistency. use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; +use crate::duration::Duration as OldDuration; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, Timelike, Weekday}; /// Parsed parts of date and time. There are two classes of methods: diff --git a/src/lib.rs b/src/lib.rs index c407171a8b..16ae5d42ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,11 +385,10 @@ #[cfg(feature = "alloc")] extern crate alloc; -mod oldtime; -// this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::Duration; +mod duration; +pub use duration::Duration; #[cfg(feature = "std")] -pub use oldtime::OutOfRangeError; +pub use duration::OutOfRangeError; use core::fmt; diff --git a/src/naive/date.rs b/src/naive/date.rs index 53d698e3b1..860593498d 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -16,6 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "unstable-locales")] use pure_rust_locales::Locale; +use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ @@ -24,7 +25,6 @@ use crate::format::{ }; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use crate::oldtime::Duration as OldDuration; use crate::{expect, try_opt}; use crate::{Datelike, Weekday}; @@ -2390,7 +2390,7 @@ mod serde { #[cfg(test)] mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; - use crate::oldtime::Duration; + use crate::duration::Duration; use crate::{Datelike, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 4943e764d4..09268aeffd 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,13 +13,13 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; -use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; #[cfg(feature = "rustc-serialize")] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 66f3ff9957..5ded98f385 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::oldtime::Duration as OldDuration; +use crate::duration::Duration as OldDuration; use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 4348e4ca79..43e614bab0 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -12,13 +12,13 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::duration::Duration as OldDuration; #[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, }; -use crate::oldtime::Duration as OldDuration; use crate::Timelike; use crate::{expect, try_opt}; diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 0a45381dea..b77ca87339 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,5 +1,5 @@ use super::NaiveTime; -use crate::oldtime::Duration as OldDuration; +use crate::duration::Duration as OldDuration; use crate::Timelike; #[test] diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index acfae81085..53e734cc16 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -11,9 +11,9 @@ use core::str::FromStr; use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; +use crate::duration::Duration as OldDuration; use crate::format::{scan, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use crate::oldtime::Duration as OldDuration; use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. diff --git a/src/round.rs b/src/round.rs index 69ecef9e8b..9271353b77 100644 --- a/src/round.rs +++ b/src/round.rs @@ -2,7 +2,7 @@ // See README.md and LICENSE.txt for details. use crate::datetime::DateTime; -use crate::oldtime::Duration; +use crate::duration::Duration; use crate::NaiveDateTime; use crate::TimeZone; use crate::Timelike; From 9d7fafe69b56aa1d79e6070ebb5cdecdacee85c6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 29 May 2023 12:09:39 +0200 Subject: [PATCH 526/999] Remove mention of `oldtime` from documentation --- README.md | 1 - src/lib.rs | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index dde079804e..c3222ddaa3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ Optional features: * `serde`: Enable serialization/deserialization via serde. * `rkyv`: Enable serialization/deserialization via rkyv. * `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). -* `old_time`: compatability with the `Duration` type of the `time` 0.1 crate (deprecated). * `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate. * `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. The implementation and API may change or even be removed in a patch release. Feedback welcome. diff --git a/src/lib.rs b/src/lib.rs index 16ae5d42ca..66368d9407 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ //! - `unstable-locales`: Enable localization. This adds various methods with a //! `_localized` suffix. The implementation and API may change or even be //! removed in a patch release. Feedback welcome. +//! - `oldtime`: this feature no langer has a function, but once offered compatibility with the +//! `time` 0.1 crate. //! //! [`serde`]: https://github.com/serde-rs/serde //! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen @@ -60,14 +62,6 @@ //! nanoseconds and does not represent "nominal" components such as days or //! months. //! -//! When the `oldtime` feature is enabled, [`Duration`] is an alias for the -//! [`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -//! type from v0.1 of the time crate. time v0.1 is deprecated, so new code -//! should disable the `oldtime` feature and use the `chrono::Duration` type -//! instead. The `oldtime` feature is enabled by default for backwards -//! compatibility, but future versions of Chrono are likely to remove the -//! feature entirely. -//! //! Chrono does not yet natively support //! the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, //! but it will be supported in the future. From 8509da4979db0adca872b21752e52969746428fa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 May 2023 20:01:50 +0200 Subject: [PATCH 527/999] Apply Clippy suggestions for duration module --- src/duration.rs | 81 +++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 64ade7be6a..296c071093 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -22,11 +22,11 @@ use rkyv::{Archive, Deserialize, Serialize}; /// The number of nanoseconds in a microsecond. const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. -const NANOS_PER_MILLI: i32 = 1000_000; +const NANOS_PER_MILLI: i32 = 1_000_000; /// The number of nanoseconds in seconds. const NANOS_PER_SEC: i32 = 1_000_000_000; /// The number of microseconds per second. -const MICROS_PER_SEC: i64 = 1000_000; +const MICROS_PER_SEC: i64 = 1_000_000; /// The number of milliseconds per second. const MILLIS_PER_SEC: i64 = 1000; /// The number of seconds in a minute. @@ -34,9 +34,9 @@ const SECS_PER_MINUTE: i64 = 60; /// The number of seconds in an hour. const SECS_PER_HOUR: i64 = 3600; /// The number of (non-leap) seconds in days. -const SECS_PER_DAY: i64 = 86400; +const SECS_PER_DAY: i64 = 86_400; /// The number of (non-leap) seconds in a week. -const SECS_PER_WEEK: i64 = 604800; +const SECS_PER_WEEK: i64 = 604_800; macro_rules! try_opt { ($e:expr) => { @@ -128,7 +128,7 @@ impl Duration { pub const fn milliseconds(milliseconds: i64) -> Duration { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs: secs, nanos: nanos } + Duration { secs, nanos } } /// Makes a new `Duration` with given number of microseconds. @@ -136,14 +136,14 @@ impl Duration { pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); let nanos = micros as i32 * NANOS_PER_MICRO; - Duration { secs: secs, nanos: nanos } + Duration { secs, nanos } } /// Makes a new `Duration` with given number of nanoseconds. #[inline] pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); - Duration { secs: secs, nanos: nanos as i32 } + Duration { secs, nanos: nanos as i32 } } /// Returns the total number of whole weeks in the duration. @@ -224,7 +224,7 @@ impl Duration { nanos -= NANOS_PER_SEC; secs = try_opt!(secs.checked_add(1)); } - let d = Duration { secs: secs, nanos: nanos }; + let d = Duration { secs, nanos }; // Even if d is within the bounds of i64 seconds, // it might still overflow i64 milliseconds. if d < MIN || d > MAX { @@ -243,7 +243,7 @@ impl Duration { nanos += NANOS_PER_SEC; secs = try_opt!(secs.checked_sub(1)); } - let d = Duration { secs: secs, nanos: nanos }; + let d = Duration { secs, nanos }; // Even if d is within the bounds of i64 seconds, // it might still overflow i64 milliseconds. if d < MIN || d > MAX { @@ -338,7 +338,7 @@ impl Add for Duration { nanos -= NANOS_PER_SEC; secs += 1; } - Duration { secs: secs, nanos: nanos } + Duration { secs, nanos } } } @@ -352,7 +352,7 @@ impl Sub for Duration { nanos += NANOS_PER_SEC; secs -= 1; } - Duration { secs: secs, nanos: nanos } + Duration { secs, nanos } } } @@ -364,7 +364,7 @@ impl Mul for Duration { let total_nanos = self.nanos as i64 * rhs as i64; let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); let secs = self.secs * rhs as i64 + extra_secs; - Duration { secs: secs, nanos: nanos as i32 } + Duration { secs, nanos: nanos as i32 } } } @@ -384,7 +384,7 @@ impl Div for Duration { nanos += NANOS_PER_SEC; secs -= 1; } - Duration { secs: secs, nanos: nanos } + Duration { secs, nanos } } } @@ -491,19 +491,19 @@ mod tests { assert!(Duration::seconds(1) != Duration::zero()); assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); assert_eq!( - Duration::seconds(86399) + Duration::seconds(4), + Duration::seconds(86_399) + Duration::seconds(4), Duration::days(1) + Duration::seconds(3) ); - assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000)); - assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000)); + assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863_000)); + assert_eq!(Duration::days(10) - Duration::seconds(1_000_000), Duration::seconds(-136_000)); assert_eq!( - Duration::days(2) + Duration::seconds(86399) + Duration::nanoseconds(1234567890), - Duration::days(3) + Duration::nanoseconds(234567890) + Duration::days(2) + Duration::seconds(86_399) + Duration::nanoseconds(1_234_567_890), + Duration::days(3) + Duration::nanoseconds(234_567_890) ); assert_eq!(-Duration::days(3), Duration::days(-3)); assert_eq!( -(Duration::days(3) + Duration::seconds(70)), - Duration::days(-4) + Duration::seconds(86400 - 70) + Duration::days(-4) + Duration::seconds(86_400 - 70) ); } @@ -512,10 +512,10 @@ mod tests { assert_eq!(Duration::zero().num_days(), 0); assert_eq!(Duration::days(1).num_days(), 1); assert_eq!(Duration::days(-1).num_days(), -1); - assert_eq!(Duration::seconds(86399).num_days(), 0); - assert_eq!(Duration::seconds(86401).num_days(), 1); - assert_eq!(Duration::seconds(-86399).num_days(), 0); - assert_eq!(Duration::seconds(-86401).num_days(), -1); + assert_eq!(Duration::seconds(86_399).num_days(), 0); + assert_eq!(Duration::seconds(86_401).num_days(), 1); + assert_eq!(Duration::seconds(-86_399).num_days(), 0); + assert_eq!(Duration::seconds(-86_401).num_days(), -1); assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); } @@ -561,7 +561,7 @@ mod tests { assert_eq!(MIN.num_microseconds(), None); // overflow checks - const MICROS_PER_DAY: i64 = 86400_000_000; + const MICROS_PER_DAY: i64 = 86_400_000_000; assert_eq!( Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) @@ -585,7 +585,7 @@ mod tests { assert_eq!(MIN.num_nanoseconds(), None); // overflow checks - const NANOS_PER_DAY: i64 = 86400_000_000_000; + const NANOS_PER_DAY: i64 = 86_400_000_000_000; assert_eq!( Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) @@ -629,6 +629,7 @@ mod tests { } #[test] + #[allow(clippy::erasing_op)] fn test_duration_mul() { assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); assert_eq!(Duration::zero() * i32::MIN, Duration::zero()); @@ -693,7 +694,7 @@ mod tests { assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S"); assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S"); assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(), "P7DT6.543S"); - assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S"); + assert_eq!(Duration::seconds(-86_401).to_string(), "-P1DT1S"); assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S"); // the format specifier should have no effect on `Duration` @@ -706,11 +707,14 @@ mod tests { #[test] fn test_to_std() { assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); - assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); - assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000))); - assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000))); + assert_eq!(Duration::seconds(86_401).to_std(), Ok(StdDuration::new(86_401, 0))); + assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123_000_000))); + assert_eq!( + Duration::milliseconds(123_765).to_std(), + Ok(StdDuration::new(123, 765_000_000)) + ); assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); - assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000))); + assert_eq!(MAX.to_std(), Ok(StdDuration::new(9_223_372_036_854_775, 807_000_000))); assert_eq!(Duration::seconds(-1).to_std(), Err(OutOfRangeError(()))); assert_eq!(Duration::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); } @@ -718,23 +722,26 @@ mod tests { #[test] fn test_from_std() { assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); - assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0))); + assert_eq!(Ok(Duration::seconds(86_401)), Duration::from_std(StdDuration::new(86_401, 0))); assert_eq!( Ok(Duration::milliseconds(123)), - Duration::from_std(StdDuration::new(0, 123000000)) + Duration::from_std(StdDuration::new(0, 123_000_000)) ); assert_eq!( - Ok(Duration::milliseconds(123765)), - Duration::from_std(StdDuration::new(123, 765000000)) + Ok(Duration::milliseconds(123_765)), + Duration::from_std(StdDuration::new(123, 765_000_000)) ); assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777))); - assert_eq!(Ok(MAX), Duration::from_std(StdDuration::new(9223372036854775, 807000000))); assert_eq!( - Duration::from_std(StdDuration::new(9223372036854776, 0)), + Ok(MAX), + Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_000)) + ); + assert_eq!( + Duration::from_std(StdDuration::new(9_223_372_036_854_776, 0)), Err(OutOfRangeError(())) ); assert_eq!( - Duration::from_std(StdDuration::new(9223372036854775, 807000001)), + Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_001)), Err(OutOfRangeError(())) ); } From 7387fe7aba675f3d37cf24239e038cdd66636233 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 4 Sep 2023 22:06:16 +0200 Subject: [PATCH 528/999] Add history of chrono and time 0.1 to main documentation --- src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 66368d9407..140fe6229b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -363,6 +363,103 @@ //! //! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done //! lightly. +//! +//! Chrono inherently does not support an inaccurate or partial date and time representation. +//! Any operation that can be ambiguous will return `None` in such cases. +//! For example, "a month later" of 2014-01-30 is not well-defined +//! and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. +//! +//! Non ISO week handling is not yet supported. +//! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) +//! crate ([sources](https://github.com/bcourtine/chrono-ext/)). +//! +//! Advanced time zone handling is not yet supported. +//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. +//! +//! ## Relation between chrono and time 0.1 +//! +//! Since Rust 0.7 the standard library had a `time` module. It moved to `libextra`, and then to a +//! `libtime` library shipped alongside the standard library. In 2014 the work on chrono started in +//! order to provide a full-featured date and time library in Rust. Some improvements from chrono +//! made it into the standard library, notably the `chrono::Duration` type was moved to +//! `std::time::Duration` ([rust#15934]). +//! +//! In preparation of Rust 1.0 at the end of 2014 `libtime` was moved out of the Rust distro and +//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), just like the +//! `num` and `rand` crates. Naturally chrono gained `time` as a dependency. `time` also started +//! re-exporting `std::time::Duration`. Later the standard library was changed to have a more +//! limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the `time` crate kept the +//! full functionality with `time::Duration`. And `time::Duration` was part of the public API of +//! chrono. +//! +//! By 2016 `time` 0.1 lived under the `rust-lang-deprecated` organisation and was not actively +//! maintained ([time#136]). Chrono absorbed the platform functionality and `Duration` type of the +//! time crate in [chrono#478] (the work started in [chrono#286]). But because of the +//! `time::Duration` type and to not make this a breaking change `time` would still remain as a +//! dependency. Crates could opt to use chrono without the `old-time` feature to drop the +//! dependency. During this time @jhpratt would take over `time` and release what amounts to a new +//! crate under time 0.2. +//! +//! [rust#15934]: https://github.com/rust-lang/rust/pull/15934 +//! [rust#18832]: https://github.com/rust-lang/rust/pull/18832#issuecomment-62448221 +//! [rust#18858]: https://github.com/rust-lang/rust/pull/18858 +//! [rust#24920]: https://github.com/rust-lang/rust/pull/24920 +//! [RFC 1040]: https://rust-lang.github.io/rfcs/1040-duration-reform.html +//! [time#136]: https://github.com/time-rs/time/issues/136 +//! [chrono#286]: https://github.com/chronotope/chrono/pull/286 +//! [chrono#478]: https://github.com/chronotope/chrono/pull/478 +//! +//! #### Security advisories +//! +//! In november 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the time crate, +//! and the start of quite some frustration for the rust ecosystem. @quininer found that calls to +//! `localtime_r` may be unsound ([chrono#499]). Eventually, almost a year later, this was also made +//! into a security advisory against chrono as [RUSTSEC-2020-0159], which had platform code similar +//! to `time`. +//! +//! The issue: on Unix-like systems a process is given a time zone id or description via the `TZ` +//! environment variable. We need this time zone data to calculate the current local time from a +//! value that is in UTC, such as the time from the system clock. `time` 0.1 and chrono used the +//! POSIX function `localtime_r` to do the conversion to local time, which reads the `TZ` variable. +//! +//! Rust assumes the environment to be writable and uses locks to access it from multiple threads. +//! So do some other programming languages and libraries, but there is no shared locking mechanism. +//! More importantly POSIX declares modifying the environment in a multi-threaded process as unsafe, +//! and `getenv` in libc can't be changed to just take a lock because it returns a pointer to the +//! data (see [rust#27970] for more discussion). +//! +//! Since version 4.20 chrono no longer uses `localtime_r` but uses rust code to query the time zone +//! (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data from the +//! system time zone database directly. This involves the logic for POSIX TZ Strings, and parsing +//! TZif files and working with their transition tables. The code mostly comes from the work by +//! @x-hgg-x on the [tz-rs crate]. +//! +//! So when reading the `TZ` environment variable chrono now respects the Rust locks. However in +//! general it is probably best to avoid modifying the environment. +//! +//! The good thing to come from this is that we can improve beyond what the platform offers. +//! +//! [CVE-2020-26235]: https://nvd.nist.gov/vuln/detail/CVE-2020-26235 +//! [RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071 +//! [chrono#499]: https://github.com/chronotope/chrono/pull/499 +//! [RUSTSEC-2020-0159]: https://rustsec.org/advisories/RUSTSEC-2020-0159.html +//! [rust#27970]: https://github.com/rust-lang/rust/issues/27970 +//! [chrono#677]: https://github.com/chronotope/chrono/pull/677 +//! [tz-rs crate]: https://crates.io/crates/tz-rs +//! +//! #### Removing time 0.1 +//! +//! Still users of chrono would get a security advisory because of the time 0.1 dependency, which +//! was never fixed. We were careful not to break backwards compatibility with the `time::Duration` +//! type. But how many crates actually depend on this compatibility with time 0.1? Remember it was +//! unmaintained for multiple years, and now had much improved new releases. After a primitive +//! crater-like run it turned out only a tiny number of crates would break ([chrono#1095]), which we +//! reached out to if still maintained. +//! +//! With 0.4.30 chrono finally drops the time 0.1 dependency, making an end to the *many* security +//! advisory warnings against crates depending on chrono. +//! +//! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 #![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] #![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 From eee59e373aced18381be25a7a354ee695aba7338 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Sep 2023 12:42:10 +0200 Subject: [PATCH 529/999] Rewrite history sections for clarity and consistency --- src/lib.rs | 98 ++++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 140fe6229b..cdecf235a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,27 +378,28 @@ //! //! ## Relation between chrono and time 0.1 //! -//! Since Rust 0.7 the standard library had a `time` module. It moved to `libextra`, and then to a -//! `libtime` library shipped alongside the standard library. In 2014 the work on chrono started in -//! order to provide a full-featured date and time library in Rust. Some improvements from chrono -//! made it into the standard library, notably the `chrono::Duration` type was moved to -//! `std::time::Duration` ([rust#15934]). +//! Rust first had a `time` module added to `std` in its 0.7 release. It later moved to +//! `libextra`, and then to a `libtime` library shipped alongside the standard library. In 2014 +//! work on chrono started in order to provide a full-featured date and time library in Rust. +//! Some improvements from chrono made it into the standard library; notably, `chrono::Duration` +//! was included as `std::time::Duration` ([rust#15934]) in 2014. //! //! In preparation of Rust 1.0 at the end of 2014 `libtime` was moved out of the Rust distro and -//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), just like the -//! `num` and `rand` crates. Naturally chrono gained `time` as a dependency. `time` also started -//! re-exporting `std::time::Duration`. Later the standard library was changed to have a more -//! limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the `time` crate kept the -//! full functionality with `time::Duration`. And `time::Duration` was part of the public API of -//! chrono. +//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), like the +//! `num` and `rand` crates. Of course chrono kept its dependency on this `time` crate. `time` +//! started re-exporting `std::time::Duration` during this period. Later, the standard library was +//! changed to have a more limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the +//! `time` crate kept the full functionality with `time::Duration`. `time::Duration` had been a +//! part of chrono's public API. //! //! By 2016 `time` 0.1 lived under the `rust-lang-deprecated` organisation and was not actively -//! maintained ([time#136]). Chrono absorbed the platform functionality and `Duration` type of the -//! time crate in [chrono#478] (the work started in [chrono#286]). But because of the -//! `time::Duration` type and to not make this a breaking change `time` would still remain as a -//! dependency. Crates could opt to use chrono without the `old-time` feature to drop the -//! dependency. During this time @jhpratt would take over `time` and release what amounts to a new -//! crate under time 0.2. +//! maintained ([time#136]). chrono absorbed the platform functionality and `Duration` type of the +//! `time` crate in [chrono#478] (the work started in [chrono#286]). In order to preserve +//! compatibility with downstream crates depending on `time` and `chrono` sharing a `Duration` +//! type, chrono kept depending on time 0.1. chrono offered the option to opt out of the `time` +//! dependency by disabling the `oldtime` feature (swapping it out for an effectively similar +//! chrono type). In 2019, @jhpratt took over maintenance on the `time` crate and released what +//! amounts to a new crate as `time` 0.2. //! //! [rust#15934]: https://github.com/rust-lang/rust/pull/15934 //! [rust#18832]: https://github.com/rust-lang/rust/pull/18832#issuecomment-62448221 @@ -409,35 +410,29 @@ //! [chrono#286]: https://github.com/chronotope/chrono/pull/286 //! [chrono#478]: https://github.com/chronotope/chrono/pull/478 //! -//! #### Security advisories +//! ## Security advisories //! -//! In november 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the time crate, -//! and the start of quite some frustration for the rust ecosystem. @quininer found that calls to -//! `localtime_r` may be unsound ([chrono#499]). Eventually, almost a year later, this was also made -//! into a security advisory against chrono as [RUSTSEC-2020-0159], which had platform code similar -//! to `time`. +//! In November of 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the `time` crate. +//! @quininer had found that calls to `localtime_r` may be unsound ([chrono#499]). Eventually, almost +//! a year later, this was also made into a security advisory against chrono as [RUSTSEC-2020-0159], +//! which had platform code similar to `time`. //! -//! The issue: on Unix-like systems a process is given a time zone id or description via the `TZ` -//! environment variable. We need this time zone data to calculate the current local time from a -//! value that is in UTC, such as the time from the system clock. `time` 0.1 and chrono used the -//! POSIX function `localtime_r` to do the conversion to local time, which reads the `TZ` variable. +//! On Unix-like systems a process is given a timezone id or description via the `TZ` environment +//! variable. We need this timezone data to calculate the current local time from a value that is +//! in UTC, such as the time from the system clock. `time` 0.1 and chrono used the POSIX function +//! `localtime_r` to do the conversion to local time, which reads the `TZ` variable. //! //! Rust assumes the environment to be writable and uses locks to access it from multiple threads. -//! So do some other programming languages and libraries, but there is no shared locking mechanism. -//! More importantly POSIX declares modifying the environment in a multi-threaded process as unsafe, -//! and `getenv` in libc can't be changed to just take a lock because it returns a pointer to the -//! data (see [rust#27970] for more discussion). +//! Some other programming languages and libraries use similar locking strategies, but these are +//! typically not shared across languages. More importantly, POSIX declares modifying the +//! environment in a multi-threaded process as unsafe, and `getenv` in libc can't be changed to +//! take a lock because it returns a pointer to the data (see [rust#27970] for more discussion). //! -//! Since version 4.20 chrono no longer uses `localtime_r` but uses rust code to query the time zone -//! (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data from the -//! system time zone database directly. This involves the logic for POSIX TZ Strings, and parsing -//! TZif files and working with their transition tables. The code mostly comes from the work by -//! @x-hgg-x on the [tz-rs crate]. -//! -//! So when reading the `TZ` environment variable chrono now respects the Rust locks. However in -//! general it is probably best to avoid modifying the environment. -//! -//! The good thing to come from this is that we can improve beyond what the platform offers. +//! Since version 4.20 chrono no longer uses `localtime_r`, instead using Rust code to query the +//! timezone (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data +//! from the system timezone database directly. The code for this was forked from the [tz-rs crate] +//! by @x-hgg-x. As such, chrono now respects the Rust lock when reading the `TZ` environment +//! variable. In general, code should avoid modifying the environment. //! //! [CVE-2020-26235]: https://nvd.nist.gov/vuln/detail/CVE-2020-26235 //! [RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071 @@ -447,17 +442,20 @@ //! [chrono#677]: https://github.com/chronotope/chrono/pull/677 //! [tz-rs crate]: https://crates.io/crates/tz-rs //! -//! #### Removing time 0.1 +//! ## Removing time 0.1 //! -//! Still users of chrono would get a security advisory because of the time 0.1 dependency, which -//! was never fixed. We were careful not to break backwards compatibility with the `time::Duration` -//! type. But how many crates actually depend on this compatibility with time 0.1? Remember it was -//! unmaintained for multiple years, and now had much improved new releases. After a primitive -//! crater-like run it turned out only a tiny number of crates would break ([chrono#1095]), which we -//! reached out to if still maintained. +//! Because time 0.1 has been unmaintained for years, however, the security advisory mentioned +//! above has not been addressed. While chrono maintainers were careful not to break backwards +//! compatibility with the `time::Duration` type, there has been a long stream of issues from +//! users inquiring about the time 0.1 dependency with the vulnerability. We investigated the +//! potential breakage of removing the time 0.1 dependency in [chrono#1095] using a crater-like +//! experiment and determined that the potential for breaking (public) dependencies is very low. +//! We reached out to those few crates that did still depend on compatibility with time 0.1. //! -//! With 0.4.30 chrono finally drops the time 0.1 dependency, making an end to the *many* security -//! advisory warnings against crates depending on chrono. +//! As such, for chrono 0.4.30 we have decided to swap out the time 0.1 `Duration` implementation +//! for a local one that will offer a strict superset of the existing API going forward. This +//! will prevent most downstream users from being affected by the security vulnerability in time +//! 0.1 while minimizing the ecosystem impact of semver-incompatible version churn. //! //! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 From 101ca7e96d57ee751db2b44e6320ee1e06adeda0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 7 Sep 2023 10:32:32 +0200 Subject: [PATCH 530/999] Bump version to 0.4.30 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7d70318271..5dd5b1fb54 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-09-05 -version: 0.4.29 +date-released: 2023-09-07 +version: 0.4.30 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index c2e058ad92..4c7d115299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.29" +version = "0.4.30" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 77d0bec82d5ceb656d10415ebd21e9dacefcc09e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 7 Sep 2023 16:26:43 +0200 Subject: [PATCH 531/999] Add more documentation about the RFC 2822 obsolete date format --- src/datetime/mod.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 650c63d0c0..2701cd1776 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -666,10 +666,24 @@ impl DateTime { /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. /// /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP - /// and email headers. + /// and email headers. It is the 2001 revision of RFC 822, and is itself revised as RFC 5322 in + /// 2008. /// - /// The RFC 2822 standard allows arbitrary intermixed whitespace. - /// See [RFC 2822 Appendix A.5] + /// # Support for the obsolete date format + /// + /// - A 2-digit year is interpreted to be a year in 1950-2049. + /// - The standard allows comments and whitespace between many of the tokens. See [4.3] and + /// [Appendix A.5] + /// - Single letter 'military' time zone names are parsed as a `-0000` offset. + /// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because + /// the meaning is now ambiguous, the standard says they should be be considered as `-0000` + /// unless there is out-of-band information confirming their meaning. + /// The exception is `Z`, which remains identical to `+0000`. + /// + /// [4.3]: https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + /// [Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 + /// + /// # Example /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; @@ -678,8 +692,6 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` - /// - /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); From 2960f2c4544e3e3ce7c8973cb24c9e6a947f9ede Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 9 Sep 2023 07:49:56 +0200 Subject: [PATCH 532/999] Remove `__doctest` feature --- Cargo.toml | 2 -- src/lib.rs | 8 -------- 2 files changed, 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c7d115299..1b6b98beef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales", "alloc"] __internal_bench = [] -__doctest = [] [dependencies] num-traits = { version = "0.2", default-features = false } @@ -58,7 +57,6 @@ android-tzdata = { version = "0.1.1", optional = true } serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } bincode = { version = "1.3.0" } -doc-comment = { version = "0.3" } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/lib.rs b/src/lib.rs index cdecf235a0..677fdd189b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -481,14 +481,6 @@ pub use duration::OutOfRangeError; use core::fmt; -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -use doc_comment::doctest; - -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -doctest!("../README.md"); - /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { #[doc(no_inline)] From 85d4f7f590c0cc03dae70b4cd4db42fc59f31fea Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 08:21:37 +0200 Subject: [PATCH 533/999] Bump actions/checkout from 3 to 4 --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 8 ++++---- .github/workflows/test.yml | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 8b4f537ea8..88634264cf 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -11,7 +11,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # nightly is required for --doctests, see cargo-llvm-cov#2 - name: Install Rust (nightly) run: rustup update nightly diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1bbceb150c..920a702b97 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc @@ -41,13 +41,13 @@ jobs: cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 check-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - run: RUSTFLAGS="--cfg docsrs" cargo deadlinks -- --features=serde @@ -58,7 +58,7 @@ jobs: cffconvert: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false - uses: citation-file-format/cffconvert-github-action@2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c98174cb8..0975cd88a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test --all-features --color=always -- --color=always @@ -27,7 +27,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.57 @@ -52,7 +52,7 @@ jobs: rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} @@ -70,7 +70,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 @@ -95,7 +95,7 @@ jobs: target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -115,7 +115,7 @@ jobs: ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -130,7 +130,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown @@ -149,7 +149,7 @@ jobs: - wasm32-wasi runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasi @@ -169,7 +169,7 @@ jobs: - x86_64-sun-solaris runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} @@ -180,7 +180,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross test --lib --all-features --target i686-unknown-linux-gnu --color=always @@ -191,7 +191,7 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - run: cargo +nightly doc --all-features --no-deps env: From 155e9da327fa191e305f7417998171ec6a1575cd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 08:21:49 +0200 Subject: [PATCH 534/999] Make dependabot target 0.4.x branch --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0f67083a2f..c5271c4ecf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,11 +4,14 @@ updates: directory: "/" schedule: interval: "weekly" + target-branch: "0.4.x" - package-ecosystem: "cargo" directory: "/fuzz/" schedule: interval: "weekly" + target-branch: "0.4.x" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + target-branch: "0.4.x" From 90a9913cf8360ad883c0c93cf302ce1596b3be31 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 08:23:17 +0200 Subject: [PATCH 535/999] CI: remove exception for `__doctest` feature in `cargo hack check` --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0975cd88a2..f1bdd2a4c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,7 +76,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps serde,rkyv \ - --skip __internal_bench,__doctest,iana-time-zone,pure-rust-locales,libc,winapi \ + --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks From ccd3737bccd7cbf551d14ac2f528d3f56b9806b0 Mon Sep 17 00:00:00 2001 From: Charles Samborski Date: Sun, 10 Sep 2023 01:47:08 +0200 Subject: [PATCH 536/999] Add `DateTime::::from_timestamp` This commit adds the new constructor `from_timestamp` to build a `DateTime` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime` was a common issue: - https://github.com/chronotope/chrono/issues/88 - https://github.com/chronotope/chrono/issues/200 - https://github.com/chronotope/chrono/issues/832 This commit should make `DateTime` creation more discoverable and intuitive. This commit respects the current convention of preferring fallible functions. It avoids however the `_opt` suffix as there is no panicking variant. See [this issue](https://github.com/chronotope/chrono/issues/815) for discussion about error handling and panics. Closes https://github.com/chronotope/chrono/issues/832 --- src/datetime/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 14 +++++++------- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2701cd1776..78c3974ccb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -209,6 +209,18 @@ impl DateTime { /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC /// (aka "UNIX timestamp"). + /// + /// The reverse operation of creating a [`DateTime`] from a timestamp can be performed + /// using [`from_timestamp`](DateTime::from_timestamp) or [`TimeZone::timestamp_opt`]. + /// + /// ``` + /// use chrono::{DateTime, TimeZone, Utc}; + /// + /// let dt: DateTime = Utc.with_ymd_and_hms(2015, 5, 15, 0, 0, 0).unwrap(); + /// assert_eq!(dt.timestamp(), 1431648000); + /// + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` #[inline] #[must_use] pub fn timestamp(&self) -> i64 { @@ -559,6 +571,37 @@ impl DateTime { pub const MAX_UTC: DateTime = DateTime { datetime: NaiveDateTime::MAX, offset: Utc }; } +impl DateTime { + /// Makes a new [`DateTime`] from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp) and + /// [`timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos). + /// + /// Returns `None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]. + /// + /// # Example + /// + /// ``` + /// use chrono::{DateTime, Utc}; + /// + /// let dt: DateTime = DateTime::::from_timestamp(1431648000, 0).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub fn from_timestamp(secs: i64, nsecs: u32) -> Option { + NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) + } +} + impl Default for DateTime { fn default() -> Self { Utc.from_utc_datetime(&NaiveDateTime::default()) diff --git a/src/lib.rs b/src/lib.rs index 677fdd189b..e4d4479efd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,22 +309,22 @@ //! //! ### Conversion from and to EPOCH timestamps //! -//! Use [`Utc.timestamp(seconds, nanoseconds)`](./offset/trait.TimeZone.html#method.timestamp) -//! to construct a [`DateTime`](./struct.DateTime.html) from a UNIX timestamp +//! Use [`DateTime::from_timestamp(seconds, nanoseconds)`](DateTime::from_timestamp) +//! to construct a [`DateTime`] from a UNIX timestamp //! (seconds, nanoseconds that passed since January 1st 1970). //! -//! Use [`DateTime.timestamp`](./struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -//! from a [`DateTime`](./struct.DateTime.html). Additionally, you can use -//! [`DateTime.timestamp_subsec_nanos`](./struct.DateTime.html#method.timestamp_subsec_nanos) +//! Use [`DateTime.timestamp`](DateTime::timestamp) to get the timestamp (in seconds) +//! from a [`DateTime`]. Additionally, you can use +//! [`DateTime.timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos) //! to get the number of additional number of nanoseconds. //! #![cfg_attr(not(feature = "std"), doc = "```ignore")] #![cfg_attr(feature = "std", doc = "```rust")] //! // We need the trait in scope to use Utc::timestamp(). -//! use chrono::{DateTime, TimeZone, Utc}; +//! use chrono::{DateTime, Utc}; //! //! // Construct a datetime from epoch: -//! let dt = Utc.timestamp_opt(1_500_000_000, 0).unwrap(); +//! let dt: DateTime = DateTime::from_timestamp(1_500_000_000, 0).unwrap(); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: From 084b0215762f9e0cdaf08c1b6b1057622d1703f3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 10:34:27 +0200 Subject: [PATCH 537/999] Format day in RFC 2822 without padding (#1272) --- src/datetime/tests.rs | 4 ++++ src/format/formatting.rs | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index cf24cb4963..da3c09cb7d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -460,6 +460,10 @@ fn test_datetime_rfc2822() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 1, 23, 16, 9).unwrap().to_rfc2822(), + "Sun, 1 Feb 2015 23:16:09 +0000" + ); // timezone +05 assert_eq!( edt.from_local_datetime( diff --git a/src/format/formatting.rs b/src/format/formatting.rs index aa2608820d..bf37ba51d8 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -637,7 +637,12 @@ fn write_rfc2822_inner( w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; w.write_str(", ")?; - write_hundreds(w, d.day() as u8)?; + let day = d.day(); + if day < 10 { + w.write_char((b'0' + day as u8) as char)?; + } else { + write_hundreds(w, day as u8)?; + } w.write_char(' ')?; w.write_str(short_months(locale)[d.month0() as usize])?; w.write_char(' ')?; From d68f151b810d434c69d1eedbc0f232fba93785d2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 29 Jul 2023 10:55:32 +0200 Subject: [PATCH 538/999] Add benchmark --- bench/benches/chrono.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 8cf7b15616..483549f95d 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -6,7 +6,7 @@ use chrono::format::StrftimeItems; use chrono::prelude::*; #[cfg(feature = "unstable-locales")] use chrono::Locale; -use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags}; +use chrono::{DateTime, Duration, FixedOffset, Local, Utc, __BenchYearFlags}; fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { c.bench_function("bench_datetime_parse_from_rfc2822", |b| { @@ -195,6 +195,15 @@ fn bench_format_manual(c: &mut Criterion) { }) }); } + +fn bench_naivedate_add_signed(c: &mut Criterion) { + let date = NaiveDate::from_ymd_opt(2023, 7, 29).unwrap(); + let extra = Duration::days(25); + c.bench_function("bench_naivedate_add_signed", |b| { + b.iter(|| black_box(date).checked_add_signed(extra).unwrap()) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -210,6 +219,7 @@ criterion_group!( bench_format, bench_format_with_items, bench_format_manual, + bench_naivedate_add_signed, ); #[cfg(feature = "unstable-locales")] From 36ec37b1d16a23dd55d6c5f625ee3edf775f782b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 29 Jul 2023 10:44:56 +0200 Subject: [PATCH 539/999] Optimize `NaiveDate::add_days` for small values --- src/naive/date.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 860593498d..0dcb36fcda 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -782,9 +782,15 @@ impl NaiveDate { /// Add a duration of `i32` days to the date. pub(crate) const fn add_days(self, days: i32) -> Option { - if days == 0 { - return Some(self); + // fast path if the result is within the same year + const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + if let Some(ordinal) = ((self.ymdf & ORDINAL_MASK) >> 4).checked_add(days) { + if ordinal > 0 && ordinal <= 365 { + let year_and_flags = self.ymdf & !ORDINAL_MASK; + return Some(NaiveDate { ymdf: year_and_flags | (ordinal << 4) }); + } } + // do the full check let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); From 323f29aab19fcf9d1b65da149450ded2bf70d71f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 27 Jul 2023 20:44:46 +0200 Subject: [PATCH 540/999] Add `NaiveDateTime::timestamp_nanos_opt` --- src/naive/datetime/mod.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 09268aeffd..9c5a9c8b2e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -460,6 +460,25 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[inline] + #[must_use] + pub fn timestamp_nanos(&self) -> i64 { + self.timestamp_nanos_opt() + .expect("value can not be represented in a timestamp with nanosecond precision.") + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. /// /// # Example /// @@ -467,12 +486,12 @@ impl NaiveDateTime { /// use chrono::{NaiveDate, NaiveDateTime}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); /// /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); /// /// const A_BILLION: i64 = 1_000_000_000; - /// let nanos = dt.timestamp_nanos(); + /// let nanos = dt.timestamp_nanos_opt().unwrap(); /// assert_eq!(nanos, 1_000_000_000_000_000_555); /// assert_eq!( /// Some(dt), @@ -481,11 +500,8 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - 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.") + pub fn timestamp_nanos_opt(&self) -> Option { + self.timestamp().checked_mul(1_000_000_000)?.checked_add(self.time.nanosecond() as i64) } /// Returns the number of milliseconds since the last whole non-leap second. From 08edc5ae930c670326527fd1a740951d1c986e44 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:54:27 +0200 Subject: [PATCH 541/999] Add `DateTime::timestamp_nanos_opt` --- src/datetime/mod.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 78c3974ccb..df9768be82 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -265,6 +265,22 @@ impl DateTime { self.datetime.timestamp_micros() } + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # 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. + #[inline] + #[must_use] + pub fn timestamp_nanos(&self) -> i64 { + self.timestamp_nanos_opt() + .expect("value can not be represented in a timestamp with nanosecond precision.") + } + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. /// /// # Panics @@ -281,15 +297,15 @@ impl DateTime { /// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); /// /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); /// ``` #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() + pub fn timestamp_nanos_opt(&self) -> Option { + self.datetime.timestamp_nanos_opt() } /// Returns the number of milliseconds since the last second boundary. From 310b60a48c0b5fba2d9bfb8c75a801ff40b9ad2e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:55:30 +0200 Subject: [PATCH 542/999] Use `timestamp_nanos_opt` in `duration_round` and `duration_trunc` --- src/round.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/round.rs b/src/round.rs index 9271353b77..c8e7b18f23 100644 --- a/src/round.rs +++ b/src/round.rs @@ -142,9 +142,6 @@ pub trait DurationRound: Sized { fn duration_trunc(self, duration: Duration) -> Result; } -/// The maximum number of seconds a DateTime can be to be represented as nanoseconds -const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; - impl DurationRound for DateTime { type Err = RoundingError; @@ -181,10 +178,7 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = naive.timestamp_nanos(); + let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; if span > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } @@ -223,10 +217,7 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = naive.timestamp_nanos(); + let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; if span > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } From 6302624c43840f6f7d684c610e74cd54c14d2d49 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 22:27:47 +0200 Subject: [PATCH 543/999] Use `timestamp_nanos_opt` in serialization; add panic warning --- src/datetime/serde.rs | 24 ++++++++++++++++++++++-- src/naive/datetime/serde.rs | 24 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 61f7f68840..4044eb586b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -157,6 +157,14 @@ pub mod ts_nanoseconds { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error 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: /// /// ```rust @@ -180,7 +188,9 @@ pub mod ts_nanoseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_nanos()) + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) } /// Deserialize a [`DateTime`] from a nanosecond timestamp @@ -286,6 +296,14 @@ pub mod ts_nanoseconds_option { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error 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: /// /// ```rust @@ -310,7 +328,9 @@ pub mod ts_nanoseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), None => serializer.serialize_none(), } } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 49dfb8390d..86d8b20f4c 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -91,6 +91,14 @@ pub mod ts_nanoseconds { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error 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: /// /// ```rust @@ -114,7 +122,9 @@ pub mod ts_nanoseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_nanos()) + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) } /// Deserialize a `NaiveDateTime` from a nanoseconds timestamp @@ -218,6 +228,14 @@ pub mod ts_nanoseconds_option { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error 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: /// /// ```rust @@ -242,7 +260,9 @@ pub mod ts_nanoseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), None => serializer.serialize_none(), } } From 94ad32a40dfd79eb2ebe020074803454ee255d22 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 22:00:04 +0200 Subject: [PATCH 544/999] Switch tests to `timestamp_nanos_opt` --- src/naive/datetime/tests.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 5ded98f385..39187de67e 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -386,7 +386,7 @@ fn test_nanosecond_range() { const A_BILLION: i64 = 1_000_000_000; let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); assert_eq!( parsed, NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() @@ -394,29 +394,23 @@ fn test_nanosecond_range() { let minimum = "1677-09-21T00:12:44.000000000"; let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); assert_eq!( parsed, NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() ); -} -#[test] -#[should_panic] -fn test_nanosecond_just_beyond_range() { + // Just beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); let beyond_max = parsed + OldDuration::milliseconds(300); - let _ = beyond_max.timestamp_nanos(); -} + assert!(beyond_max.timestamp_nanos_opt().is_none()); -#[test] -#[should_panic] -fn test_nanosecond_far_beyond_range() { + // Far beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); let beyond_max = parsed + OldDuration::days(365); - let _ = beyond_max.timestamp_nanos(); + assert!(beyond_max.timestamp_nanos_opt().is_none()); } #[test] From a174b8afad1e753c8857c785293d761e8a6fef7a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:55:45 +0200 Subject: [PATCH 545/999] Deprecate `timestamp_nanos` --- src/datetime/mod.rs | 1 + src/naive/datetime/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index df9768be82..e77043e298 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -274,6 +274,7 @@ impl DateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 9c5a9c8b2e..b2f7d63bf0 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -460,6 +460,7 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { From 03a12c79950eaa822d5e125153d688c7b7a8865a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 22:15:10 +0200 Subject: [PATCH 546/999] Change example for leap seconds not on the minute boundary --- src/naive/time/mod.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 43e614bab0..9fa4b8929a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -171,22 +171,29 @@ mod tests; /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// -/// There are hypothetical leap seconds not on the minute boundary -/// nevertheless supported by Chrono. -/// They are allowed for the sake of completeness and consistency; -/// there were several "exotic" time zone offsets with fractional minutes prior to UTC after all. -/// For such cases the human-readable representation is ambiguous -/// and would be read back to the next non-leap second. +/// There are hypothetical leap seconds not on the minute boundary nevertheless supported by Chrono. +/// They are allowed for the sake of completeness and consistency; there were several "exotic" time +/// zone offsets with fractional minutes prior to UTC after all. +/// For such cases the human-readable representation is ambiguous and would be read back to the next +/// non-leap second. /// /// ``` -/// use chrono::{DateTime, Utc, TimeZone, NaiveDate}; -/// -/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 56, 4, 1_000).unwrap().and_local_timezone(Utc).unwrap(); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// -/// let dt = Utc.with_ymd_and_hms(2015, 6, 30, 23, 56, 5).unwrap(); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); +/// use chrono::{FixedOffset, NaiveDate, TimeZone}; +/// +/// let paramaribo_pre1945 = FixedOffset::east_opt(-13236).unwrap(); // -03:40:36 +/// let leap_sec_2015 = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); +/// let dt1 = paramaribo_pre1945.from_utc_datetime(&leap_sec_2015); +/// assert_eq!(format!("{:?}", dt1), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt1.time()), "20:19:24"); +/// +/// let next_sec = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); +/// let dt2 = paramaribo_pre1945.from_utc_datetime(&next_sec); +/// assert_eq!(format!("{:?}", dt2), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt2.time()), "20:19:24"); +/// +/// assert!(dt1.time() != dt2.time()); +/// assert!(dt1.time().to_string() == dt2.time().to_string()); /// ``` /// /// Since Chrono alone cannot determine any existence of leap seconds, From 60283ab55cbc9ea6caa86e19bf5da2c086cdf4bc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:25:35 +0200 Subject: [PATCH 547/999] Don't create strange leap seconds in tests --- src/naive/datetime/mod.rs | 6 ++-- src/naive/time/mod.rs | 6 ++-- src/naive/time/tests.rs | 62 +++++++++++++++++++-------------------- src/round.rs | 12 ++++---- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index b2f7d63bf0..d42c757bea 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1460,11 +1460,11 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 333_333_333).unwrap())); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 1_333_333_333).unwrap())); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 1_333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` #[inline] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 9fa4b8929a..e9e2e02970 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1022,9 +1022,9 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{NaiveTime, Timelike}; - /// # let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), - /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 1_333_333_333).unwrap())); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// let strange_leap_second = dt.with_nanosecond(1_333_333_333).unwrap(); + /// assert_eq!(strange_leap_second.nanosecond(), 1_333_333_333); /// ``` #[inline] fn with_nanosecond(&self, nano: u32) -> Option { diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index b77ca87339..f7a6ef5f92 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -13,12 +13,12 @@ fn test_time_from_hms_milli() { Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap()) ); assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_000_000).unwrap()) + NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None); } #[test] @@ -36,12 +36,12 @@ fn test_time_from_hms_micro() { Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap()) ); assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_999_000).unwrap()) + NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None); } #[test] @@ -94,19 +94,19 @@ fn test_time_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 7, 900), OldDuration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), OldDuration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), OldDuration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), OldDuration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), OldDuration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::days(-1), hmsm(3, 5, 8, 300)); + check!(hmsm(3, 5, 59, 900), OldDuration::zero(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 900), OldDuration::milliseconds(100), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 59, 500)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 59, 1_200)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(800), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(1800), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), OldDuration::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), OldDuration::seconds(-86399), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), OldDuration::days(12345), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::days(1), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::days(-1), hmsm(3, 6, 0, 300)); // regression tests for #37 check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-990), hmsm(23, 59, 59, 10)); @@ -132,12 +132,12 @@ fn test_time_overflowing_add() { // overflowing_add_signed with leap seconds may be counter-intuitive assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(1)), - (hmsm(3, 4, 5, 678), 86_400) + hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(1)), + (hmsm(3, 4, 59, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(-1)), - (hmsm(3, 4, 6, 678), -86_400) + hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(-1)), + (hmsm(3, 5, 0, 678), -86_400) ); } @@ -184,14 +184,14 @@ fn test_time_sub() { // treats the leap second as if it coincides with the prior non-leap second, // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), OldDuration::milliseconds(400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); // additional equality: `time1 + duration = time2` is equivalent to // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. assert_eq!(hmsm(3, 5, 6, 800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); } #[test] diff --git a/src/round.rs b/src/round.rs index c8e7b18f23..9c0e76a0f3 100644 --- a/src/round.rs +++ b/src/round.rs @@ -760,16 +760,16 @@ mod tests { #[test] fn issue1010() { - let dt = NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288).unwrap(); - let span = Duration::microseconds(-7019067213869040); + let dt = NaiveDateTime::from_timestamp_opt(-4_227_854_320, 678_774_288).unwrap(); + let span = Duration::microseconds(-7_019_067_213_869_040); assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(320041586, 1920103021).unwrap(); - let span = Duration::nanoseconds(-8923838508697114584); + let dt = NaiveDateTime::from_timestamp_opt(320_041_586, 920_103_021).unwrap(); + let span = Duration::nanoseconds(-8_923_838_508_697_114_584); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(-2621440, 0).unwrap(); - let span = Duration::nanoseconds(-9223372036854771421); + let dt = NaiveDateTime::from_timestamp_opt(-2_621_440, 0).unwrap(); + let span = Duration::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } } From 202af6cfda9bfdb195dc96377fcdeee7ed024b65 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 17:07:34 +0200 Subject: [PATCH 548/999] Don't generate leap seconds that are not 60 in NaiveTime's Arbitrary impl --- src/naive/time/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index e9e2e02970..88e9d50ce8 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -208,9 +208,14 @@ pub struct NaiveTime { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NaiveTime { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { - let secs = u.int_in_range(0..=86_399)?; - let nano = u.int_in_range(0..=1_999_999_999)?; - let time = NaiveTime::from_num_seconds_from_midnight_opt(secs, nano) + let mins = u.int_in_range(0..=1439)?; + let mut secs = u.int_in_range(0..=60)?; + let mut nano = u.int_in_range(0..=999_999_999)?; + if secs == 60 { + secs = 59; + nano += 1_000_000_000; + } + let time = NaiveTime::from_num_seconds_from_midnight_opt(mins * 60 + secs, nano) .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); Ok(time) } From 61b7ffbb7a95df577c308eb0f2ab5c68e1566cf1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:31:00 +0200 Subject: [PATCH 549/999] Deny leap second if secs != 59 in `from_hms_nano_opt` --- src/naive/date.rs | 24 ++++++++++++------------ src/naive/time/mod.rs | 33 ++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0dcb36fcda..677c5fa6f0 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -867,8 +867,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -882,8 +882,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -917,8 +917,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -946,8 +946,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -981,8 +981,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -996,8 +996,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 88e9d50ce8..72644e2647 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -177,6 +177,10 @@ mod tests; /// For such cases the human-readable representation is ambiguous and would be read back to the next /// non-leap second. /// +/// A `NaiveTime` with a leap second that is not on a minute boundary can only be created from a +/// [`DateTime`](crate::DateTime) with fractional minutes as offset, or using +/// [`Timelike::with_nanosecond()`]. +/// /// ``` /// use chrono::{FixedOffset, NaiveDate, TimeZone}; /// @@ -239,8 +243,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead. + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -282,8 +286,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -318,8 +322,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -333,8 +337,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -369,8 +373,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -384,8 +388,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -409,7 +413,10 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { - if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { + if (hour >= 24 || min >= 60 || sec >= 60) + || (nano >= 1_000_000_000 && sec != 59) + || nano >= 2_000_000_000 + { return None; } let secs = hour * 3600 + min * 60 + sec; From 6665804676e55e9e2375eed7c10cc9e0910abf11 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:36:52 +0200 Subject: [PATCH 550/999] Deny leap second if secs != 59 in `from_num_seconds_from_midnight_opt` --- src/datetime/mod.rs | 12 +++++++++--- src/naive/datetime/mod.rs | 15 ++++++++------- src/naive/time/mod.rs | 10 +++++----- src/offset/mod.rs | 12 ++++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e77043e298..01c17827d2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -596,12 +596,18 @@ impl DateTime { /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp) and /// [`timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos). /// - /// Returns `None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. - /// /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]. /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. + /// /// # Example /// /// ``` diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index d42c757bea..e2008563b2 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -110,9 +110,9 @@ impl NaiveDateTime { /// For a non-naive version of this function see /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). /// - /// The nanosecond part can exceed 1,000,000,000 in order to represent the - /// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX - /// timestamp" cannot represent a leap second unambiguously.) + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Panics /// @@ -196,8 +196,8 @@ impl NaiveDateTime { /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Errors @@ -216,8 +216,9 @@ impl NaiveDateTime { /// /// assert!(from_timestamp_opt(0, 0).is_some()); /// assert!(from_timestamp_opt(0, 999_999_999).is_some()); - /// assert!(from_timestamp_opt(0, 1_500_000_000).is_some()); // leap second - /// assert!(from_timestamp_opt(0, 2_000_000_000).is_none()); + /// assert!(from_timestamp_opt(0, 1_500_000_000).is_none()); // invalid leap second + /// assert!(from_timestamp_opt(59, 1_500_000_000).is_some()); // leap second + /// assert!(from_timestamp_opt(59, 2_000_000_000).is_none()); /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); /// ``` #[inline] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 72644e2647..da35a49644 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -425,8 +425,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Panics /// @@ -440,8 +440,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Errors /// @@ -463,7 +463,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { - if secs >= 86_400 || nano >= 2_000_000_000 { + if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { return None; } Some(NaiveTime { secs, frac: nano }) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 419550e292..7f5857b234 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -347,6 +347,12 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Panics + /// /// Panics on the out-of-range number of seconds and/or invalid nanosecond, /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] @@ -358,6 +364,12 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// /// Returns `LocalResult::None` on out-of-range number of seconds and/or /// invalid nanosecond, otherwise always returns `LocalResult::Single`. /// From 3c4846a88235a38105a047b0acd34ce239a1cfb5 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Thu, 14 Sep 2023 14:12:03 +0200 Subject: [PATCH 551/999] Upgrade pure-rust-locales to 0.7.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1b6b98beef..e5e712262b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ __internal_bench = [] num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.6", optional = true } +pure-rust-locales = { version = "0.7", optional = true } rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } From 861d4e12487181e71744478a320db20f56bd61af Mon Sep 17 00:00:00 2001 From: Eden Mikitas Date: Tue, 12 Sep 2023 20:47:50 +0300 Subject: [PATCH 552/999] Make TimeZone::timestamp_millis_opt use NaiveDateTime::from_timestamp_millis to reduce code duplication --- src/offset/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 7f5857b234..df688b22bb 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -415,12 +415,10 @@ pub trait TimeZone: Sized + Clone { /// }; /// ``` fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { - let (mut secs, mut millis) = (millis / 1000, millis % 1000); - if millis < 0 { - secs -= 1; - millis += 1000; + match NaiveDateTime::from_timestamp_millis(millis) { + Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), + None => LocalResult::None, } - self.timestamp_opt(secs, millis as u32 * 1_000_000) } /// Makes a new `DateTime` from the number of non-leap nanoseconds From 1df8db3a547bf50929d3d1774306f996b63e9e7c Mon Sep 17 00:00:00 2001 From: Eden Mikitas Date: Tue, 12 Sep 2023 21:05:20 +0300 Subject: [PATCH 553/999] Add TimeZone::timestamp_micros Use NaiveDateTime::from_timestamp_micros instead of implementing the same logic again. --- src/offset/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index df688b22bb..7c96f72a2c 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -443,6 +443,22 @@ pub trait TimeZone: Sized + Clone { self.timestamp_opt(secs, nanos as u32).unwrap() } + /// Makes a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). + /// + /// #Example + /// ``` + /// use chrono::{Utc, TimeZone}; + /// + /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); + /// ``` + fn timestamp_micros(&self, micros: i64) -> LocalResult> { + match NaiveDateTime::from_timestamp_micros(micros) { + Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), + None => LocalResult::None, + } + } + /// Parses a string with the specified format string and returns a /// `DateTime` with the current offset. /// @@ -568,4 +584,18 @@ mod tests { Utc.timestamp_nanos(i64::default()); Utc.timestamp_nanos(i64::min_value()); } + + #[test] + fn test_negative_micros() { + let dt = Utc.timestamp_micros(-1_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); + let dt = Utc.timestamp_micros(-999_999).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000001 UTC"); + let dt = Utc.timestamp_micros(-1).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999 UTC"); + let dt = Utc.timestamp_micros(-60_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); + let dt = Utc.timestamp_micros(-3_600_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); + } } From 46ad2c2b2c901eb20e43a7fca025aac02605bda4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 15 Sep 2023 08:46:12 +0200 Subject: [PATCH 554/999] Add `UNIX_EPOCH` constants --- src/datetime/mod.rs | 3 +++ src/naive/datetime/mod.rs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 01c17827d2..0df12bc36e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -623,6 +623,9 @@ impl DateTime { pub fn from_timestamp(secs: i64, nsecs: u32) -> Option { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) } + + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. + pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; } impl Default for DateTime { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e2008563b2..ed0c2286a7 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -20,7 +20,7 @@ use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; -use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; +use crate::{expect, DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -967,8 +967,13 @@ impl NaiveDateTime { /// The minimum possible `NaiveDateTime`. pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; + /// The maximum possible `NaiveDateTime`. pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; + + /// The Unix Epoch, 1970-01-01 00:00:00. + pub const UNIX_EPOCH: Self = + expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); } impl Datelike for NaiveDateTime { From 2afdde8f7f23f087b5027662e2882dba0663fef7 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Fri, 15 Sep 2023 13:34:17 +0200 Subject: [PATCH 555/999] fix: underflow during datetime->nanos conversion Fixes #1289. --- src/datetime/tests.rs | 33 +++++++++++++++++++++++++++++++++ src/naive/datetime/mod.rs | 21 ++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index da3c09cb7d..63dfe59b4e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1486,3 +1486,36 @@ fn locale_decimal_point() { assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); } + +/// This is an extended test for . +#[test] +fn nano_roundrip() { + const BILLION: i64 = 1_000_000_000; + + for nanos in [ + i64::MIN, + i64::MIN + 1, + i64::MIN + 2, + i64::MIN + BILLION - 1, + i64::MIN + BILLION, + i64::MIN + BILLION + 1, + -BILLION - 1, + -BILLION, + -BILLION + 1, + 0, + BILLION - 1, + BILLION, + BILLION + 1, + i64::MAX - BILLION - 1, + i64::MAX - BILLION, + i64::MAX - BILLION + 1, + i64::MAX - 2, + i64::MAX - 1, + i64::MAX, + ] { + println!("nanos: {}", nanos); + let dt = Utc.timestamp_nanos(nanos); + let nanos2 = dt.timestamp_nanos_opt().expect("value roundtrips"); + assert_eq!(nanos, nanos2); + } +} diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index ed0c2286a7..cd41207ce5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -503,7 +503,26 @@ impl NaiveDateTime { #[inline] #[must_use] pub fn timestamp_nanos_opt(&self) -> Option { - self.timestamp().checked_mul(1_000_000_000)?.checked_add(self.time.nanosecond() as i64) + let mut timestamp = self.timestamp(); + let mut timestamp_subsec_nanos = i64::from(self.timestamp_subsec_nanos()); + + // subsec nanos are always non-negative, however the timestamp itself (both in seconds and in nanos) can be + // negative. Now i64::MIN is NOT dividable by 1_000_000_000, so + // + // (timestamp * 1_000_000_000) + nanos + // + // may underflow (even when in theory we COULD represent the datetime as i64) because we add the non-negative + // nanos AFTER the multiplication. This is fixed by converting the negative case to + // + // ((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000) + // + // Also see . + if timestamp < 0 && timestamp_subsec_nanos > 0 { + timestamp_subsec_nanos -= 1_000_000_000; + timestamp += 1; + } + + timestamp.checked_mul(1_000_000_000).and_then(|ns| ns.checked_add(timestamp_subsec_nanos)) } /// Returns the number of milliseconds since the last whole non-leap second. From e730c6ac45649a6a636abf30b796304bc46ecd15 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 15 Sep 2023 16:31:48 +0200 Subject: [PATCH 556/999] Bump version to 0.4.31 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 5dd5b1fb54..877b1b48b4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-09-07 -version: 0.4.30 +date-released: 2023-09-15 +version: 0.4.31 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index e5e712262b..a2d1a63e48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.30" +version = "0.4.31" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From bc9d070227c0d4391a194331ca22c04762dbc719 Mon Sep 17 00:00:00 2001 From: Markus Legner Date: Mon, 18 Sep 2023 10:06:35 +0200 Subject: [PATCH 557/999] improve doc comment and tests for timestamp_nanos_opt - The doc comment for `DateTime::timestamp_nanos_opt` incorrectly described a panic, which is actually an error. - The lower and upper limits of valid timestamps were not precise (both for `DateTime` and `NaiveDateTime` and both `timestamp_nanos` and `timestamp_nanos_opt`). This commit adds the precise limits to all methods and doctests for those limits to `DateTime::timestamp_nanos_opt`. --- src/datetime/mod.rs | 26 +++++++++++++++++++------- src/naive/datetime/mod.rs | 8 ++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 0df12bc36e..633913b8d4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -272,8 +272,8 @@ impl DateTime { /// 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. + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] @@ -284,13 +284,13 @@ impl DateTime { /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. /// - /// # Panics + /// # Errors /// - /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on - /// an out of range `DateTime`. + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` 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. + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. /// /// # Example /// @@ -302,6 +302,18 @@ impl DateTime { /// /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21).unwrap().and_hms_nano_opt(0, 12, 43, 145_224_192).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(-9_223_372_036_854_775_808)); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11).unwrap().and_hms_nano_opt(23, 47, 16, 854_775_807).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(9_223_372_036_854_775_807)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21).unwrap().and_hms_nano_opt(0, 12, 43, 145_224_191).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11).unwrap().and_hms_nano_opt(23, 47, 16, 854_775_808).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); /// ``` #[inline] #[must_use] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index cd41207ce5..db974c4842 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -459,8 +459,8 @@ impl NaiveDateTime { /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on /// an out of range `NaiveDateTime`. /// - /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and - /// 2262-04-11T23:47:16.854775804. + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] @@ -479,8 +479,8 @@ impl NaiveDateTime { /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns /// `None` on an out of range `NaiveDateTime`. /// - /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and - /// 2262-04-11T23:47:16.854775804. + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. /// /// # Example /// From 5fc9be8800650d2945956fb687482edf6a332767 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 11:23:13 +0200 Subject: [PATCH 558/999] Export `Archived*` types in `rkyv` module --- src/lib.rs | 20 ++++++++++++++++++++ src/naive/mod.rs | 6 +++--- src/offset/mod.rs | 6 +++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e4d4479efd..11179f917b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -569,6 +569,26 @@ pub mod serde { pub use super::datetime::serde::*; } +/// Zero-copy serialization/deserialization with rkyv. +/// +/// This module re-exports the `Archived*` versions of chrono's types. +#[cfg(feature = "rkyv")] +#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))] +pub mod rkyv { + pub use crate::datetime::ArchivedDateTime; + pub use crate::duration::ArchivedDuration; + pub use crate::month::ArchivedMonth; + pub use crate::naive::date::ArchivedNaiveDate; + pub use crate::naive::datetime::ArchivedNaiveDateTime; + pub use crate::naive::isoweek::ArchivedIsoWeek; + pub use crate::naive::time::ArchivedNaiveTime; + pub use crate::offset::fixed::ArchivedFixedOffset; + #[cfg(feature = "clock")] + pub use crate::offset::local::ArchivedLocal; + pub use crate::offset::utc::ArchivedUtc; + pub use crate::weekday::ArchivedWeekday; +} + /// Out of range error type used in various converting APIs #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct OutOfRange { diff --git a/src/naive/mod.rs b/src/naive/mod.rs index d3d69196f4..044e7540b0 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -4,11 +4,11 @@ //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), //! but can be also used for the simpler date and time handling. -mod date; +pub(crate) mod date; pub(crate) mod datetime; mod internals; -mod isoweek; -mod time; +pub(crate) mod isoweek; +pub(crate) mod time; pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator, NaiveWeek}; #[allow(deprecated)] diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 7c96f72a2c..c3154ee894 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -26,15 +26,15 @@ use crate::Weekday; #[allow(deprecated)] use crate::{Date, DateTime}; -mod fixed; +pub(crate) mod fixed; pub use self::fixed::FixedOffset; #[cfg(feature = "clock")] -mod local; +pub(crate) mod local; #[cfg(feature = "clock")] pub use self::local::Local; -mod utc; +pub(crate) mod utc; pub use self::utc::Utc; /// The conversion result from the local time to the timezone-aware datetime types. From c4fc4c4b75cf3bfc2fdd4a0db96e2fbeb3f7d214 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 11:31:07 +0200 Subject: [PATCH 559/999] Show `rkyv` support on docs.rs --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a2d1a63e48..567a207db4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ bincode = { version = "1.3.0" } wasm-bindgen-test = "0.3" [package.metadata.docs.rs] -features = ["serde"] +features = ["serde, rkyv"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] From 7d2c9fc3f869cf2346f13c592453f3a0359413ff Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 21:00:41 +0200 Subject: [PATCH 560/999] Switch docs.rs to `auto_docs_cfg` --- src/date.rs | 4 --- src/datetime/mod.rs | 40 --------------------------- src/datetime/rustc_serialize.rs | 4 --- src/datetime/serde.rs | 3 -- src/format/formatting.rs | 7 ----- src/format/mod.rs | 3 -- src/format/strftime.rs | 1 - src/lib.rs | 9 +----- src/month.rs | 2 -- src/naive/date.rs | 6 ---- src/naive/datetime/mod.rs | 2 -- src/naive/datetime/rustc_serialize.rs | 2 -- src/naive/datetime/serde.rs | 2 -- src/naive/mod.rs | 1 - src/naive/time/mod.rs | 2 -- src/naive/time/rustc_serialize.rs | 2 -- src/naive/time/serde.rs | 2 -- src/offset/utc.rs | 1 - src/round.rs | 1 - src/weekday.rs | 2 -- 20 files changed, 1 insertion(+), 95 deletions(-) diff --git a/src/date.rs b/src/date.rs index 5e84692370..6cbe851fac 100644 --- a/src/date.rs +++ b/src/date.rs @@ -334,7 +334,6 @@ where { /// Formats the date with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -349,7 +348,6 @@ where /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { @@ -358,7 +356,6 @@ where /// Formats the date with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -383,7 +380,6 @@ where /// See the [`crate::format::strftime`] module /// on the supported escape sequences. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 633913b8d4..12e2660297 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -537,7 +537,6 @@ impl DateTime { /// 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"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); @@ -548,7 +547,6 @@ impl DateTime { /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339(&self) -> String { // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. @@ -585,7 +583,6 @@ impl DateTime { /// "2018-01-26T10:30:09+08:00"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { let mut result = String::with_capacity(38); @@ -647,7 +644,6 @@ impl Default for DateTime { } #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Default for DateTime { fn default() -> Self { Local.from_utc_datetime(&NaiveDateTime::default()) @@ -673,7 +669,6 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -696,7 +691,6 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -709,7 +703,6 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -722,7 +715,6 @@ impl From> for DateTime { /// Convert a `DateTime` instance into a `DateTime` instance. #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From> for DateTime { /// Convert this `DateTime` instance into a `DateTime` instance. /// @@ -868,7 +860,6 @@ where { /// Formats the combined date and time with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -893,7 +884,6 @@ where /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { @@ -902,7 +892,6 @@ where /// Formats the combined date and time with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -930,7 +919,6 @@ where /// See the [`crate::format::strftime`] module on the supported escape /// sequences. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( @@ -1401,7 +1389,6 @@ impl str::FromStr for DateTime { /// # Ok::<(), chrono::ParseError>(()) /// ``` #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl str::FromStr for DateTime { type Err = ParseError; @@ -1411,7 +1398,6 @@ impl str::FromStr for DateTime { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { @@ -1432,7 +1418,6 @@ impl From for DateTime { } #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { DateTime::::from(t).with_timezone(&Local) @@ -1440,7 +1425,6 @@ impl From for DateTime { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { let sec = dt.timestamp(); @@ -1459,14 +1443,6 @@ impl From> for SystemTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] -#[cfg_attr( - docsrs, - doc(cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))) -)] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { DateTime::::from(&date) @@ -1478,14 +1454,6 @@ impl From for DateTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] -#[cfg_attr( - docsrs, - doc(cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))) -)] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { Utc.timestamp_millis_opt(date.get_time() as i64).unwrap() @@ -1497,14 +1465,6 @@ impl From<&js_sys::Date> for DateTime { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] -#[cfg_attr( - docsrs, - doc(cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))) -)] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 115ec9eb3f..51ef5b7230 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] - use super::{DateTime, SecondsFormat}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -82,7 +80,6 @@ impl Decodable for TsSeconds { } #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Decodable for DateTime { fn decode(d: &mut D) -> Result, D::Error> { match d.read_str()?.parse::>() { @@ -93,7 +90,6 @@ impl Decodable for DateTime { } #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[allow(deprecated)] impl Decodable for TsSeconds { #[allow(deprecated)] diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 4044eb586b..85655c99a6 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] - use core::fmt; use serde::{de, ser}; @@ -107,7 +105,6 @@ impl<'de> de::Deserialize<'de> for DateTime { /// See [the `serde` module](./serde/index.html) for alternate /// serialization formats. #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where diff --git a/src/format/formatting.rs b/src/format/formatting.rs index bf37ba51d8..f456f10e84 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -46,7 +46,6 @@ use locales::*; /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. #[cfg(any(feature = "alloc", feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat { /// The date view, if any. @@ -103,7 +102,6 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time and locale. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_locale( date: Option, @@ -116,7 +114,6 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_offset_and_locale( date: Option, @@ -157,7 +154,6 @@ impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for De /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -177,7 +173,6 @@ where } /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -193,7 +188,6 @@ pub fn format_item( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -215,7 +209,6 @@ where /// Formats single formatting item #[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_item_localized( w: &mut fmt::Formatter, date: Option<&NaiveDate>, diff --git a/src/format/mod.rs b/src/format/mod.rs index d22cdf071a..6ee0837ea3 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -333,13 +333,11 @@ pub enum Item<'a> { Literal(&'a str), /// Same as `Literal` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -437,7 +435,6 @@ impl fmt::Display for ParseError { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/format/strftime.rs b/src/format/strftime.rs index d7551457f7..a72e6353d0 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -191,7 +191,6 @@ impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } diff --git a/src/lib.rs b/src/lib.rs index 11179f917b..c91bc06777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -469,7 +469,7 @@ // can remove this if/when rustc-serialize support is removed // keeps clippy happy in the meantime #![cfg_attr(feature = "rustc-serialize", allow(deprecated))] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "alloc")] extern crate alloc; @@ -487,11 +487,9 @@ pub mod prelude { #[allow(deprecated)] pub use crate::Date; #[cfg(feature = "clock")] - #[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use crate::Local; #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[doc(no_inline)] pub use crate::Locale; #[doc(no_inline)] @@ -514,7 +512,6 @@ pub use date::{Date, MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] -#[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] pub use datetime::rustc_serialize::TsSeconds; #[allow(deprecated)] pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; @@ -522,7 +519,6 @@ pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub use format::Locale; pub use format::{ParseError, ParseResult}; @@ -532,7 +528,6 @@ pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] #[doc(no_inline)] pub use offset::Local; #[doc(no_inline)] @@ -564,7 +559,6 @@ pub use naive::__BenchYearFlags; /// [1]: https://tools.ietf.org/html/rfc3339 /// [2]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } @@ -573,7 +567,6 @@ pub mod serde { /// /// This module re-exports the `Archived*` versions of chrono's types. #[cfg(feature = "rkyv")] -#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))] pub mod rkyv { pub use crate::datetime::ArchivedDateTime; pub use crate::duration::ArchivedDuration; diff --git a/src/month.rs b/src/month.rs index 146a98ebe5..abe337404f 100644 --- a/src/month.rs +++ b/src/month.rs @@ -236,7 +236,6 @@ pub struct ParseMonthError { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for ParseMonthError {} impl fmt::Display for ParseMonthError { @@ -252,7 +251,6 @@ impl fmt::Debug for ParseMonthError { } #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod month_serde { use super::Month; use serde::{de, ser}; diff --git a/src/naive/date.rs b/src/naive/date.rs index 677c5fa6f0..d2a91f1807 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1265,7 +1265,6 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -1309,7 +1308,6 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { @@ -1318,7 +1316,6 @@ impl NaiveDate { /// Formats the date with the specified formatting items and locale. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -1338,7 +1335,6 @@ impl NaiveDate { /// See the [`crate::format::strftime`] module on the supported escape /// sequences. #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[inline] #[must_use] pub fn format_localized<'a>( @@ -2276,7 +2272,6 @@ where } #[cfg(feature = "rustc-serialize")] -#[cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] mod rustc_serialize { use super::NaiveDate; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -2311,7 +2306,6 @@ mod rustc_serialize { } #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod serde { use super::NaiveDate; use core::fmt; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index db974c4842..79b8ab739a 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -894,7 +894,6 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -938,7 +937,6 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs index 201fe7aa26..d2b8a87114 100644 --- a/src/naive/datetime/rustc_serialize.rs +++ b/src/naive/datetime/rustc_serialize.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] - use super::NaiveDateTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use std::ops::Deref; diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 86d8b20f4c..0a6cfd915d 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] - use core::fmt; use serde::{de, ser}; diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 044e7540b0..c8c281b00e 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -34,7 +34,6 @@ pub use self::internals::YearFlags as __BenchYearFlags; /// [1]: https://serde.rs/attributes.html#field-attributes /// [2]: https://tools.ietf.org/html/rfc3339 #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { pub use super::datetime::serde::*; } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index da35a49644..8bc5e957d8 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -778,7 +778,6 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -824,7 +823,6 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ``` #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs index 128fcec8ab..3c5c47a18b 100644 --- a/src/naive/time/rustc_serialize.rs +++ b/src/naive/time/rustc_serialize.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] - use super::NaiveTime; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index cf3c4e3d5e..0992fb59f2 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -1,5 +1,3 @@ -#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] - use super::NaiveTime; use core::fmt; use serde::{de, ser}; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index d27f7877e1..17e575a599 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -46,7 +46,6 @@ use crate::{Date, DateTime}; pub struct Utc; #[cfg(feature = "clock")] -#[cfg_attr(docsrs, doc(cfg(feature = "clock")))] impl Utc { /// Returns a `Date` which corresponds to the current date. #[deprecated( diff --git a/src/round.rs b/src/round.rs index 9c0e76a0f3..795ffb04cd 100644 --- a/src/round.rs +++ b/src/round.rs @@ -291,7 +291,6 @@ impl fmt::Display for RoundingError { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/weekday.rs b/src/weekday.rs index a7abd94153..e3c241aeb6 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -227,7 +227,6 @@ pub struct ParseWeekdayError { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for ParseWeekdayError {} impl fmt::Display for ParseWeekdayError { @@ -245,7 +244,6 @@ impl fmt::Debug for ParseWeekdayError { // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] mod weekday_serde { use super::Weekday; use core::fmt; From a1a0f89dabeadac31d7ca54b1efce4802655ded4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 21:03:05 +0200 Subject: [PATCH 561/999] Show `arbitrary` and `unstable-locales` features on docs.rs --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 567a207db4..571b2e9f70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ bincode = { version = "1.3.0" } wasm-bindgen-test = "0.3" [package.metadata.docs.rs] -features = ["serde, rkyv"] +features = ["arbitrary, rkyv, serde, unstable-locales"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] From c892679e8d0160265000208bb78b5e6e0f13c2d7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 21:57:38 +0200 Subject: [PATCH 562/999] CI: run `cargo-deadlinks` for all features --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 920a702b97..87c9f553c4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - - run: RUSTFLAGS="--cfg docsrs" cargo deadlinks -- --features=serde + - run: cargo deadlinks -- --all-features - run: cargo doc --all-features --no-deps env: RUSTDOCFLAGS: -Dwarnings From a32d77acb0ab63ca171f54cfd6c9e64030e50e2b Mon Sep 17 00:00:00 2001 From: Ihor Ranchynskyi Date: Fri, 22 Sep 2023 12:02:15 +0100 Subject: [PATCH 563/999] duplicate derives on ArchivedT types (for rkyv feature) (#1271) Duplicate derives for `ArchiveT` types for `rkyv` feature --- src/duration.rs | 4 ++++ src/month.rs | 4 ++++ src/naive/date.rs | 4 ++++ src/naive/datetime/mod.rs | 4 ++++ src/naive/isoweek.rs | 4 ++++ src/naive/time/mod.rs | 4 ++++ src/offset/fixed.rs | 1 + src/offset/local/mod.rs | 1 + src/offset/utc.rs | 1 + src/weekday.rs | 1 + 10 files changed, 28 insertions(+) diff --git a/src/duration.rs b/src/duration.rs index 296c071093..570c6ce5f3 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -52,6 +52,10 @@ macro_rules! try_opt { /// This also allows for the negative duration; see individual methods for details. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] pub struct Duration { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC diff --git a/src/month.rs b/src/month.rs index abe337404f..a409bb155d 100644 --- a/src/month.rs +++ b/src/month.rs @@ -31,6 +31,10 @@ use crate::OutOfRange; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { /// January diff --git a/src/naive/date.rs b/src/naive/date.rs index d2a91f1807..886dbb4f95 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -192,6 +192,10 @@ impl Days { /// [proleptic Gregorian date]: crate::NaiveDate#calendar-date #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] pub struct NaiveDate { ymdf: DateImpl, // (year << 13) | of } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 79b8ab739a..9aabfe583f 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -74,6 +74,10 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 8edf738ba0..deede94d65 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -18,6 +18,10 @@ use rkyv::{Archive, Deserialize, Serialize}; /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8bc5e957d8..3268890ec6 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -204,6 +204,10 @@ mod tests; /// **there is absolutely no guarantee that the leap second read has actually happened**. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr( + feature = "rkyv", + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] pub struct NaiveTime { secs: u32, frac: u32, diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 53e734cc16..23acd7b0d8 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -24,6 +24,7 @@ use crate::{DateTime, ParseError, Timelike}; /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)))] pub struct FixedOffset { local_minus_utc: i32, } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 557f8db03e..ffd2844d55 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -105,6 +105,7 @@ mod tz_info; /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, Debug)))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 17e575a599..3b0a85d56a 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -42,6 +42,7 @@ use crate::{Date, DateTime}; /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; diff --git a/src/weekday.rs b/src/weekday.rs index e3c241aeb6..fb88c3b929 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -32,6 +32,7 @@ use crate::OutOfRange; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. From bbec5318cc4f43ab775e5ff2d15ea17c2d0e5c9b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 14:29:24 +0200 Subject: [PATCH 564/999] Import `fmt::core::Display` --- src/format/formatting.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f456f10e84..ea6ed1b13f 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -7,8 +7,9 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; -use core::fmt; -use core::fmt::Write; +#[cfg(any(feature = "alloc", feature = "std"))] +use core::fmt::Display; +use core::fmt::{self, Write}; #[cfg(any( feature = "alloc", @@ -87,7 +88,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { items: I, ) -> DelayedFormat where - Off: Offset + fmt::Display, + Off: Offset + Display, { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { @@ -123,7 +124,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { locale: Locale, ) -> DelayedFormat where - Off: Offset + fmt::Display, + Off: Offset + Display, { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } @@ -131,7 +132,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[cfg(any(feature = "alloc", feature = "std"))] -impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { +impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] { From b48bb3a4ba74e098c30d2eb66f4aa2aa42c7371e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Sep 2023 21:38:33 +0200 Subject: [PATCH 565/999] Move formatting from standalone functions to `Display` impl --- src/format/formatting.rs | 75 +++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index ea6ed1b13f..684e8e92ed 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -135,20 +135,22 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] - { - if let Some(locale) = self.locale { - return format_localized( - f, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - self.items.clone(), - locale, - ); - } + let locale = self.locale; + #[cfg(not(feature = "unstable-locales"))] + let locale = None; + + let mut result = String::new(); + for item in self.items.clone() { + format_inner( + &mut result, + self.date.as_ref(), + self.time.as_ref(), + self.off.as_ref(), + item.borrow(), + locale, + )?; } - - format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) + f.pad(&result) } } @@ -166,12 +168,17 @@ where I: Iterator + Clone, B: Borrow>, { - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), None)?; + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items, + #[cfg(feature = "unstable-locales")] + locale: None, } - w.pad(&result) + .fmt(w) } + /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std"))] pub fn format_item( @@ -181,9 +188,15 @@ pub fn format_item( off: Option<&(String, FixedOffset)>, item: &Item<'_>, ) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, None)?; - w.pad(&result) + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items: [item].into_iter(), + #[cfg(feature = "unstable-locales")] + locale: None, + } + .fmt(w) } /// Tries to format given arguments with given formatting items. @@ -201,11 +214,14 @@ where I: Iterator + Clone, B: Borrow>, { - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items, + locale: Some(locale), } - w.pad(&result) + .fmt(w) } /// Formats single formatting item @@ -218,9 +234,14 @@ pub fn format_item_localized( item: &Item<'_>, locale: Locale, ) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, Some(locale))?; - w.pad(&result) + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items: [item].into_iter(), + locale: Some(locale), + } + .fmt(w) } #[cfg(any(feature = "alloc", feature = "std"))] From 41e34aded41d9dafda5cda2c5f2f42c5cfc8b279 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 14:30:46 +0200 Subject: [PATCH 566/999] Deprecate standalone `format*` functions --- src/format/formatting.rs | 4 ++++ src/format/mod.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 684e8e92ed..4e572d548a 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -157,6 +157,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> Display for Delayed /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std"))] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -181,6 +182,7 @@ where /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std"))] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -202,6 +204,7 @@ pub fn format_item( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "unstable-locales")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -226,6 +229,7 @@ where /// Formats single formatting item #[cfg(feature = "unstable-locales")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_item_localized( w: &mut fmt::Formatter, date: Option<&NaiveDate>, diff --git a/src/format/mod.rs b/src/format/mod.rs index 6ee0837ea3..d5256ab1e9 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -62,8 +62,10 @@ pub(crate) use formatting::write_rfc2822; ))] pub(crate) use formatting::write_rfc3339; #[cfg(any(feature = "alloc", feature = "std"))] +#[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] +#[allow(deprecated)] pub use formatting::{format_item_localized, format_localized}; #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] pub use locales::Locale; From 45f1a4d5544e9de9f42ba84badd457aa06ed956e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 07:39:51 +0200 Subject: [PATCH 567/999] Add benchmark for `DateTime::with_*` --- bench/benches/chrono.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 483549f95d..38f8513d41 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -204,6 +204,13 @@ fn bench_naivedate_add_signed(c: &mut Criterion) { }); } +fn bench_datetime_with(c: &mut Criterion) { + let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2023, 9, 23, 7, 36, 0).unwrap(); + c.bench_function("bench_datetime_with", |b| { + b.iter(|| black_box(black_box(dt).with_hour(12)).unwrap()) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -220,6 +227,7 @@ criterion_group!( bench_format_with_items, bench_format_manual, bench_naivedate_add_signed, + bench_datetime_with, ); #[cfg(feature = "unstable-locales")] From bfc7b2623978ef912c85c454c04a419dec04413b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 12:25:42 +0200 Subject: [PATCH 568/999] Make `std` imply `alloc` feature --- Cargo.toml | 2 +- src/date.rs | 8 ++--- src/datetime/mod.rs | 12 +++---- src/datetime/tests.rs | 16 ++++----- src/format/formatting.rs | 71 +++++++++++++++---------------------- src/format/mod.rs | 23 +++++------- src/format/parse.rs | 6 ++-- src/format/strftime.rs | 12 +++---- src/naive/date.rs | 8 ++--- src/naive/datetime/mod.rs | 8 ++--- src/naive/datetime/tests.rs | 4 +-- src/naive/isoweek.rs | 4 +-- src/naive/time/mod.rs | 8 ++--- 13 files changed, 81 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 571b2e9f70..195cf4434d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] winapi = ["windows-targets"] -std = [] +std = ["alloc"] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] diff --git a/src/date.rs b/src/date.rs index 6cbe851fac..78d5dc9f01 100644 --- a/src/date.rs +++ b/src/date.rs @@ -4,7 +4,7 @@ //! ISO 8601 calendar date with time zone. #![allow(deprecated)] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; @@ -333,7 +333,7 @@ where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -347,7 +347,7 @@ where /// Formats the date with the specified format string. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 12e2660297..cfd2815153 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -21,7 +21,7 @@ use crate::format::{ parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, TOO_LONG, }; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::format::{write_rfc3339, DelayedFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -536,7 +536,7 @@ impl DateTime { /// /// 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"))] + #[cfg(feature = "alloc")] #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); @@ -546,7 +546,7 @@ impl DateTime { } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[must_use] pub fn to_rfc3339(&self) -> String { // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. @@ -582,7 +582,7 @@ impl DateTime { /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { let mut result = String::with_capacity(38); @@ -859,7 +859,7 @@ where Tz::Offset: fmt::Display, { /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -883,7 +883,7 @@ where /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 63dfe59b4e..3872e14b36 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -273,7 +273,7 @@ fn ymdhms_milli( // local helper function to easily create a DateTime #[allow(clippy::too_many_arguments)] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn ymdhms_micro( fixedoffset: &FixedOffset, year: i32, @@ -293,7 +293,7 @@ fn ymdhms_micro( // local helper function to easily create a DateTime #[allow(clippy::too_many_arguments)] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn ymdhms_nano( fixedoffset: &FixedOffset, year: i32, @@ -312,7 +312,7 @@ fn ymdhms_nano( } // local helper function to easily create a DateTime -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() } @@ -451,7 +451,7 @@ fn test_datetime_with_timezone() { } #[test] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn test_datetime_rfc2822() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); @@ -577,7 +577,7 @@ fn test_datetime_rfc2822() { } #[test] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn test_datetime_rfc3339() { let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); let edt0 = FixedOffset::east_opt(0).unwrap(); @@ -663,7 +663,7 @@ fn test_datetime_rfc3339() { } #[test] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn test_rfc3339_opts() { use crate::SecondsFormat::*; let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); @@ -694,7 +694,7 @@ fn test_rfc3339_opts() { #[test] #[should_panic] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); @@ -1470,7 +1470,7 @@ fn test_test_deprecated_from_offset() { } #[test] -#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] fn locale_decimal_point() { use crate::Locale::{ar_SY, nl_NL}; let dt = diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 4e572d548a..805866bb4a 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -3,50 +3,35 @@ //! Date and time formatting routines. -#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::fmt::Display; use core::fmt::{self, Write}; -#[cfg(any( - feature = "alloc", - feature = "std", - feature = "serde", - feature = "rustc-serialize" -))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use crate::datetime::SecondsFormat; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::offset::Offset; -#[cfg(any( - feature = "alloc", - feature = "std", - feature = "serde", - feature = "rustc-serialize" -))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, Weekday}; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use super::locales; -#[cfg(any( - feature = "alloc", - feature = "std", - feature = "serde", - feature = "rustc-serialize" -))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use locales::*; /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct DelayedFormat { /// The date view, if any. @@ -64,7 +49,7 @@ pub struct DelayedFormat { locale: Option, } -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] @@ -131,7 +116,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } } -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] @@ -156,7 +141,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> Display for Delayed /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format<'a, I, B>( w: &mut fmt::Formatter, @@ -181,7 +166,7 @@ where } /// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_item( w: &mut fmt::Formatter, @@ -248,7 +233,7 @@ pub fn format_item_localized( .fmt(w) } -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] fn format_inner( w: &mut impl Write, date: Option<&NaiveDate>, @@ -261,7 +246,7 @@ fn format_inner( match *item { Item::Literal(s) | Item::Space(s) => w.write_str(s), - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, ref pad) => { @@ -484,7 +469,7 @@ fn format_inner( } } -#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -566,7 +551,7 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] -#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, @@ -629,7 +614,7 @@ pub(crate) fn write_rfc3339( .format(w, off) } -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( w: &mut impl Write, @@ -639,7 +624,7 @@ pub(crate) fn write_rfc2822( write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) } -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( w: &mut impl Write, @@ -699,15 +684,15 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { } #[cfg(test)] -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] mod tests { use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; use crate::FixedOffset; - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_date_format() { let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); @@ -752,7 +737,7 @@ mod tests { } #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_time_format() { let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); @@ -788,7 +773,7 @@ mod tests { } #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_datetime_format() { let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); @@ -806,7 +791,7 @@ mod tests { } #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_datetime_format_alignment() { let datetime = Utc .with_ymd_and_hms(2007, 1, 2, 12, 34, 56) diff --git a/src/format/mod.rs b/src/format/mod.rs index d5256ab1e9..58e6a4d027 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -30,7 +30,7 @@ //! # Ok::<(), chrono::ParseError>(()) //! ``` -#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::boxed::Box; use core::fmt; use core::str::FromStr; @@ -48,28 +48,23 @@ pub(crate) mod scan; pub mod strftime; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] pub(crate) mod locales; pub(crate) use formatting::write_hundreds; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] pub(crate) use formatting::write_rfc2822; -#[cfg(any( - feature = "alloc", - feature = "std", - feature = "serde", - feature = "rustc-serialize" -))] +#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) use formatting::write_rfc3339; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] #[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] #[allow(deprecated)] pub use formatting::{format_item_localized, format_localized}; -#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] pub use locales::Locale; -#[cfg(all(not(feature = "unstable-locales"), any(feature = "alloc", feature = "std")))] +#[cfg(all(not(feature = "unstable-locales"), feature = "alloc"))] pub(crate) use locales::Locale; pub(crate) use parse::parse_rfc3339; pub use parse::{parse, parse_and_remainder}; @@ -334,12 +329,12 @@ pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same as `Literal` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. diff --git a/src/format/parse.rs b/src/format/parse.rs index 418fa424eb..04628ac861 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -311,7 +311,7 @@ where s = &s[prefix.len()..]; } - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { return Err((s, TOO_SHORT)); @@ -326,7 +326,7 @@ where s = s.trim_start(); } - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] Item::OwnedSpace(_) => { s = s.trim_start(); } @@ -1700,7 +1700,7 @@ mod tests { let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); // Check that it parses correctly diff --git a/src/format/strftime.rs b/src/format/strftime.rs index a72e6353d0..557034d15a 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -500,7 +500,7 @@ mod tests { use crate::format::Locale; use crate::format::{fixed, internal_fixed, num, num0, nums}; use crate::format::{Fixed, InternalInternal, Numeric::*}; - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] @@ -662,7 +662,7 @@ mod tests { } #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_strftime_docs() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -773,7 +773,7 @@ mod tests { } #[test] - #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] fn test_strftime_docs_localized() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -826,7 +826,7 @@ mod tests { /// /// See . #[test] - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn test_parse_only_timezone_offset_permissive_no_panic() { use crate::NaiveDate; use crate::{FixedOffset, TimeZone}; @@ -847,7 +847,7 @@ mod tests { } #[test] - #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] fn test_strftime_localized_korean() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -876,7 +876,7 @@ mod tests { } #[test] - #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] fn test_strftime_localized_japanese() { let dt = FixedOffset::east_opt(34200) .unwrap() diff --git a/src/naive/date.rs b/src/naive/date.rs index 886dbb4f95..0990e2d5a7 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,7 +3,7 @@ //! ISO 8601 calendar date without timezone. -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; @@ -17,7 +17,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use pure_rust_locales::Locale; use crate::duration::Duration as OldDuration; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, @@ -1268,7 +1268,7 @@ impl NaiveDate { /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -1311,7 +1311,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 9aabfe583f..c5c3c0b231 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 date and time without timezone. -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -14,7 +14,7 @@ use core::{fmt, str}; use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; @@ -897,7 +897,7 @@ impl NaiveDateTime { /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -940,7 +940,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 39187de67e..9ee22dfde6 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -19,7 +19,7 @@ fn test_datetime_from_timestamp_millis() { for (timestamp_millis, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } @@ -57,7 +57,7 @@ fn test_datetime_from_timestamp_micros() { for (timestamp_micros, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index deede94d65..606699659b 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -162,13 +162,13 @@ mod tests { assert_eq!(minweek.year(), internals::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 3268890ec6..bf1cb2b1c5 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 time without timezone. -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; @@ -13,7 +13,7 @@ use core::{fmt, str}; use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; -#[cfg(any(feature = "alloc", feature = "std"))] +#[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, @@ -781,7 +781,7 @@ impl NaiveTime { /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat @@ -826,7 +826,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { From 68a141eba0534b23246f739a42cc3a3a2c16aff6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 12:39:48 +0200 Subject: [PATCH 569/999] Don't make `unstable-locales` depend on `alloc` feature --- Cargo.toml | 2 +- src/date.rs | 6 +++--- src/datetime/mod.rs | 6 +++--- src/format/formatting.rs | 8 +++++--- src/format/mod.rs | 10 +++++----- src/lib.rs | 8 ++++---- src/naive/date.rs | 6 +++--- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 195cf4434d..c5f2c8cbf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ std = ["alloc"] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] -unstable-locales = ["pure-rust-locales", "alloc"] +unstable-locales = ["pure-rust-locales"] __internal_bench = [] [dependencies] diff --git a/src/date.rs b/src/date.rs index 78d5dc9f01..e82a3f0eee 100644 --- a/src/date.rs +++ b/src/date.rs @@ -14,7 +14,7 @@ use core::{fmt, hash}; use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] use crate::format::Locale; #[cfg(feature = "alloc")] use crate::format::{DelayedFormat, Item, StrftimeItems}; @@ -355,7 +355,7 @@ where } /// Formats the date with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -379,7 +379,7 @@ where /// Formats the date with the specified format string and locale. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized<'a>( diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index cfd2815153..d0e63d5732 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -15,7 +15,7 @@ use core::{fmt, hash, str}; use std::time::{SystemTime, UNIX_EPOCH}; use crate::duration::Duration as OldDuration; -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] use crate::format::Locale; use crate::format::{ parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed, @@ -891,7 +891,7 @@ where } /// Formats the combined date and time with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -918,7 +918,7 @@ where /// /// See the [`crate::format::strftime`] module on the supported escape /// sequences. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized<'a>( diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 805866bb4a..46e9663440 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -22,10 +22,12 @@ use crate::{NaiveDate, NaiveTime, Weekday}; #[cfg(feature = "alloc")] use super::locales; +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use super::Locale; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(feature = "alloc")] -use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; +use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; #[cfg(feature = "alloc")] use locales::*; @@ -188,7 +190,7 @@ pub fn format_item( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, @@ -213,7 +215,7 @@ where } /// Formats single formatting item -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] pub fn format_item_localized( w: &mut fmt::Formatter, diff --git a/src/format/mod.rs b/src/format/mod.rs index 58e6a4d027..46723eff9c 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -48,7 +48,9 @@ pub(crate) mod scan; pub mod strftime; -#[cfg(feature = "alloc")] +#[allow(unused)] +// TODO: remove '#[allow(unused)]' once we use this module for parsing or something else that does +// not require `alloc`. pub(crate) mod locales; pub(crate) use formatting::write_hundreds; @@ -59,13 +61,11 @@ pub(crate) use formatting::write_rfc3339; #[cfg(feature = "alloc")] #[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[allow(deprecated)] pub use formatting::{format_item_localized, format_localized}; -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +#[cfg(feature = "unstable-locales")] pub use locales::Locale; -#[cfg(all(not(feature = "unstable-locales"), feature = "alloc"))] -pub(crate) use locales::Locale; pub(crate) use parse::parse_rfc3339; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; diff --git a/src/lib.rs b/src/lib.rs index c91bc06777..7841afb42c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,7 +227,7 @@ //! # #[allow(unused_imports)] //! use chrono::prelude::*; //! -//! # #[cfg(feature = "unstable-locales")] +//! # #[cfg(all(feature = "unstable-locales", feature = "alloc"))] //! # fn test() { //! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); @@ -244,9 +244,9 @@ //! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap().and_local_timezone(Utc).unwrap(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } -//! # #[cfg(not(feature = "unstable-locales"))] +//! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))] //! # fn test() {} -//! # if cfg!(feature = "unstable-locales") { +//! # if cfg!(all(feature = "unstable-locales", feature = "alloc")) { //! # test(); //! # } //! ``` @@ -489,7 +489,7 @@ pub mod prelude { #[cfg(feature = "clock")] #[doc(no_inline)] pub use crate::Local; - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[doc(no_inline)] pub use crate::Locale; #[doc(no_inline)] diff --git a/src/naive/date.rs b/src/naive/date.rs index 0990e2d5a7..907b1285d1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -13,7 +13,7 @@ use core::{fmt, str}; use rkyv::{Archive, Deserialize, Serialize}; /// L10n locales. -#[cfg(feature = "unstable-locales")] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] use pure_rust_locales::Locale; use crate::duration::Duration as OldDuration; @@ -1319,7 +1319,7 @@ impl NaiveDate { } /// Formats the date with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized_with_items<'a, I, B>( @@ -1338,7 +1338,7 @@ impl NaiveDate { /// /// See the [`crate::format::strftime`] module on the supported escape /// sequences. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] #[must_use] pub fn format_localized<'a>( From 4d28a8932539cd2934a27db6630322a32431cbaf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 13:40:21 +0200 Subject: [PATCH 570/999] Remove `format::{format_localized, format_item_localized}` --- src/format/formatting.rs | 47 ---------------------------------------- src/format/mod.rs | 3 --- 2 files changed, 50 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 46e9663440..8cb83c7521 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -188,53 +188,6 @@ pub fn format_item( .fmt(w) } -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] -#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] -pub fn format_localized<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, - locale: Locale, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - DelayedFormat { - date: date.copied(), - time: time.copied(), - off: off.cloned(), - items, - locale: Some(locale), - } - .fmt(w) -} - -/// Formats single formatting item -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] -#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] -pub fn format_item_localized( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Locale, -) -> fmt::Result { - DelayedFormat { - date: date.copied(), - time: time.copied(), - off: off.cloned(), - items: [item].into_iter(), - locale: Some(locale), - } - .fmt(w) -} - #[cfg(feature = "alloc")] fn format_inner( w: &mut impl Write, diff --git a/src/format/mod.rs b/src/format/mod.rs index 46723eff9c..df75b4ffbc 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -61,9 +61,6 @@ pub(crate) use formatting::write_rfc3339; #[cfg(feature = "alloc")] #[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] -#[allow(deprecated)] -pub use formatting::{format_item_localized, format_localized}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; pub(crate) use parse::parse_rfc3339; From b5cdcc66fb64d093ca29eea08c8cd3c48548905b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:38:30 +0200 Subject: [PATCH 571/999] Add `NaiveTime::overflowing_(add|sub)_offset` --- src/naive/time/mod.rs | 28 +++++++++++++++++++++++++++- src/naive/time/tests.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index bf1cb2b1c5..53617b9f6e 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -19,8 +19,8 @@ use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, }; -use crate::Timelike; use crate::{expect, try_opt}; +use crate::{FixedOffset, Timelike}; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; @@ -754,6 +754,32 @@ impl NaiveTime { OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) } + /// Adds given `FixedOffset` to the current time, and returns the number of days that should be + /// added to a date as a result of the offset (either `-1`, `0`, or `1` because the offset is + /// always less than 24h). + /// + /// This method is similar to [`overflowing_add_signed`](#method.overflowing_add_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_add_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 + offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + + /// Subtracts given `FixedOffset` from the current time, and returns the number of days that + /// should be added to a date as a result of the offset (either `-1`, `0`, or `1` because the + /// offset is always less than 24h). + /// + /// This method is similar to [`overflowing_sub_signed`](#method.overflowing_sub_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_sub_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 - offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + /// Formats the time with the specified formatting items. /// Otherwise it is the same as the ordinary [`format`](#method.format) method. /// diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index f7a6ef5f92..30c1af9537 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,6 +1,6 @@ use super::NaiveTime; use crate::duration::Duration as OldDuration; -use crate::Timelike; +use crate::{FixedOffset, Timelike}; #[test] fn test_time_from_hms_milli() { @@ -350,3 +350,29 @@ fn test_time_parse_from_str() { assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } + +#[test] +fn test_overflowing_offset() { + let hmsm = |h, m, s, n| NaiveTime::from_hms_milli_opt(h, m, s, n).unwrap(); + + let positive_offset = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + // regular time + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(9, 6, 7, 890), 0)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(1, 6, 7, 890), 0)); + // leap second is preserved, and wrap to next day + let t = hmsm(23, 59, 59, 1_000); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(3, 59, 59, 1_000), 1)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(19, 59, 59, 1_000), 0)); + // wrap to previous day + let t = hmsm(1, 2, 3, 456); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(21, 2, 3, 456), -1)); + // an odd offset + let negative_offset = FixedOffset::west_opt(((2 * 60) + 3) * 60 + 4).unwrap(); + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(negative_offset), (hmsm(3, 3, 3, 890), 0)); + assert_eq!(t.overflowing_sub_offset(negative_offset), (hmsm(7, 9, 11, 890), 0)); + + assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset); + assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset); +} From 00fa07baa1c7c9505156066102309d5b56c52da4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:41:01 +0200 Subject: [PATCH 572/999] Use `overflowing_(add|sub)_offset` in `Add` and `Sub` impls --- src/offset/fixed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 23acd7b0d8..57ceccbe6c 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -204,7 +204,7 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + self.overflowing_add_offset(rhs).0 } } @@ -213,7 +213,7 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + self.overflowing_sub_offset(rhs).0 } } From a47e0e3c1601616005f9b6b885430e518af18e7c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:38:30 +0200 Subject: [PATCH 573/999] Move `Add` and `Sub` impls to `naive::time` module --- src/naive/time/mod.rs | 18 ++++++++++++++++++ src/offset/fixed.rs | 20 +------------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 53617b9f6e..b9de19d0cb 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1180,6 +1180,15 @@ impl AddAssign for NaiveTime { } } +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_add_offset(rhs).0 + } +} + /// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. /// It is the same as the addition with a negated `Duration`. @@ -1262,6 +1271,15 @@ impl SubAssign for NaiveTime { } } +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_sub_offset(rhs).0 + } +} + /// Subtracts another `NaiveTime` from the current time. /// Returns a `Duration` within +/- 1 day. /// This does not overflow or underflow at all. diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 57ceccbe6c..8038e1a5e5 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -13,7 +13,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; use crate::duration::Duration as OldDuration; use crate::format::{scan, OUT_OF_RANGE}; -use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::naive::{NaiveDate, NaiveDateTime}; use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. @@ -199,24 +199,6 @@ where (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() } -impl Add for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveTime { - self.overflowing_add_offset(rhs).0 - } -} - -impl Sub for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveTime { - self.overflowing_sub_offset(rhs).0 - } -} - impl Add for NaiveDateTime { type Output = NaiveDateTime; From b18e663a279343b296667c1f56e12d1b55d8bdc1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 May 2023 15:34:04 +0200 Subject: [PATCH 574/999] Fix *_DAYS_FROM_YEAR_0 calculation --- src/naive/date.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 907b1285d1..20866b7b87 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3235,22 +3235,16 @@ mod tests { } // 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 + // = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) + // = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 + // = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR) const MAX_DAYS_FROM_YEAR_0: i32 = - MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365; + (MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400; // 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; + // = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000) + const MIN_DAYS_FROM_YEAR_0: i32 = + MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400; // only used for testing, but duplicated in naive::datetime const MAX_BITS: usize = 44; From 0953e4a89c2a39fff47a2b977abf36d9533a1b65 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 17:21:09 +0200 Subject: [PATCH 575/999] Also report correct ordinal in `test_date_bounds` --- src/naive/date.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 20866b7b87..700958e171 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2405,13 +2405,14 @@ mod tests { let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); assert!( NaiveDate::MIN == calculated_min, - "`NaiveDate::MIN` should have a year flag {:?}", + "`NaiveDate::MIN` should have year flag {:?}", calculated_min.of().flags() ); assert!( NaiveDate::MAX == calculated_max, - "`NaiveDate::MAX` should have a year flag {:?}", - calculated_max.of().flags() + "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", + calculated_max.of().flags(), + calculated_max.of().ordinal() ); // let's also check that the entire range do not exceed 2^44 seconds From 3bcd73c68465a13b4d0aafaf8c9dbec7fff8f9be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:42:40 +0200 Subject: [PATCH 576/999] Add `NaiveDateTime::checked_(add|sub)_offset` --- src/naive/datetime/mod.rs | 37 ++++++++++++++++++++++-- src/naive/datetime/tests.rs | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c5c3c0b231..12a7adb715 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -20,8 +20,10 @@ use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; -use crate::{expect, DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; - +use crate::{ + expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeZone, Timelike, + Weekday, +}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -705,6 +707,37 @@ impl NaiveDateTime { Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) } + /// Adds given `FixedOffset` to the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_add_signed`](#method.checked_add_offset), but preserves + /// leap seconds. + #[must_use] + pub const fn checked_add_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_sub_signed`](#method.checked_sub_signed), but preserves + /// leap seconds. + pub const fn checked_sub_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + /// Subtracts given `Duration` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 9ee22dfde6..4558bf7f42 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -433,3 +433,60 @@ fn test_and_utc() { assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); } + +#[test] +fn test_checked_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none()); +} + +#[test] +fn test_checked_sub_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none()); + + assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); + assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); +} From a49eb9d80fff1cbaed03898534ee77874f7254fa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:43:45 +0200 Subject: [PATCH 577/999] Use `checked_(add|sub)_offset` in `Add` and `Sub` impls --- src/offset/fixed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 8038e1a5e5..588795d6e9 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -204,7 +204,7 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + self.checked_add_offset(rhs).unwrap() } } @@ -213,7 +213,7 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + self.checked_sub_offset(rhs).unwrap() } } From c836a1f93c0c7f5c4fbe76879b8276f91554076c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:51:22 +0200 Subject: [PATCH 578/999] Move `Add` and `Sub` impls to `naive::datetime` --- src/naive/datetime/mod.rs | 18 ++++++++++++++++++ src/offset/fixed.rs | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 12a7adb715..67e12c71f7 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1619,6 +1619,15 @@ impl AddAssign for NaiveDateTime { } } +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_add_offset(rhs).unwrap() + } +} + impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1746,6 +1755,15 @@ impl SubAssign for NaiveDateTime { } } +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_sub_offset(rhs).unwrap() + } +} + /// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 588795d6e9..013f822479 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -199,24 +199,6 @@ where (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() } -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_add_offset(rhs).unwrap() - } -} - -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_sub_offset(rhs).unwrap() - } -} - impl Add for DateTime { type Output = DateTime; From b1473c4e3946b82474d292b7d7f65e0f6b7daa56 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:51:22 +0200 Subject: [PATCH 579/999] Move `Add` and `Sub` impls for `Fixedoffset` to `datetime` modules --- src/datetime/mod.rs | 28 ++++++++++++++++++++++++++++ src/offset/fixed.rs | 38 +------------------------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index d0e63d5732..b6fbb823b0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1247,6 +1247,25 @@ impl AddAssign for DateTime { } } +fn add_with_leapsecond(lhs: &T, rhs: i32) -> T +where + T: Timelike + Add, +{ + // extract and temporarily remove the fractional part and later recover it + let nanos = lhs.nanosecond(); + let lhs = lhs.with_nanosecond(0).unwrap(); + (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() +} + +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> DateTime { + add_with_leapsecond(&self, rhs.local_minus_utc()) + } +} + impl Add for DateTime { type Output = DateTime; @@ -1294,6 +1313,15 @@ impl SubAssign for DateTime { } } +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> DateTime { + add_with_leapsecond(&self, -rhs.local_minus_utc()) + } +} + impl Sub for DateTime { type Output = DateTime; diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 013f822479..6d3aebdb68 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -4,17 +4,14 @@ //! The time zone which has a fixed offset from UTC. use core::fmt; -use core::ops::{Add, Sub}; use core::str::FromStr; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; -use crate::duration::Duration as OldDuration; -use crate::format::{scan, OUT_OF_RANGE}; +use crate::format::{scan, ParseError, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime}; -use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// @@ -184,39 +181,6 @@ impl arbitrary::Arbitrary<'_> for FixedOffset { } } -// addition or subtraction of FixedOffset to/from Timelike values is the same as -// adding or subtracting the offset's local_minus_utc value -// but keep keeps the leap second information. -// this should be implemented more efficiently, but for the time being, this is generic right now. - -fn add_with_leapsecond(lhs: &T, rhs: i32) -> T -where - T: Timelike + Add, -{ - // extract and temporarily remove the fractional part and later recover it - let nanos = lhs.nanosecond(); - let lhs = lhs.with_nanosecond(0).unwrap(); - (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - #[cfg(test)] mod tests { use super::FixedOffset; From d450b68b833fabc9b3f31d39d0490fd40e85e616 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 14:52:26 +0200 Subject: [PATCH 580/999] Use `checked_(add|sub)_offset` in `Add` and `Sub` impls of `DateTime` --- src/datetime/mod.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b6fbb823b0..59178743dc 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1247,22 +1247,13 @@ impl AddAssign for DateTime { } } -fn add_with_leapsecond(lhs: &T, rhs: i32) -> T -where - T: Timelike + Add, -{ - // extract and temporarily remove the fractional part and later recover it - let nanos = lhs.nanosecond(); - let lhs = lhs.with_nanosecond(0).unwrap(); - (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() -} - impl Add for DateTime { type Output = DateTime; #[inline] - fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc()) + fn add(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = self.naive_utc().checked_add_offset(rhs).unwrap(); + self } } @@ -1317,8 +1308,9 @@ impl Sub for DateTime { type Output = DateTime; #[inline] - fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc()) + fn sub(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = self.naive_utc().checked_sub_offset(rhs).unwrap(); + self } } From e116f5c101c25137c8f212df9b11c4b9d5781ed3 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 24 Sep 2023 18:07:08 -0700 Subject: [PATCH 581/999] tests with date command set env LC_ALL For tests calling `date` Unix tool, also set environment variable `LC_ALL=c` to make more sure to avoid locality problems. --- tests/dateutils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 52181e8316..8d3ce9c102 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -121,6 +121,7 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim let output = process::Command::new(path) .env("LANG", "c") + .env("LC_ALL", "c") .arg("-d") .arg(format!( "{}-{:02}-{:02} {:02}:{:02}:{:02}", From be8f2fd31dc9c41101921adff647be5fcf460f0a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 14:49:26 +0200 Subject: [PATCH 582/999] Update `deny.toml` --- deny.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/deny.toml b/deny.toml index 0e486c8474..186290e039 100644 --- a/deny.toml +++ b/deny.toml @@ -4,8 +4,6 @@ copyleft = "deny" [advisories] ignore = [ - "RUSTSEC-2020-0071", # time 0.1, doesn't affect the API we use - "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility ] unmaintained = "deny" From f0c62b44941accf55b37d20b714fed7d3d566bfd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 11:15:12 +0200 Subject: [PATCH 583/999] Use `expect` instead of `unwrap` in `Add` and `Sub` impls --- src/datetime/mod.rs | 14 ++++++++------ src/naive/date.rs | 8 ++++---- src/naive/datetime/mod.rs | 12 ++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 59178743dc..b05744e653 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1252,7 +1252,8 @@ impl Add for DateTime { #[inline] fn add(mut self, rhs: FixedOffset) -> DateTime { - self.datetime = self.naive_utc().checked_add_offset(rhs).unwrap(); + self.datetime = + self.naive_utc().checked_add_offset(rhs).expect("`DateTime + FixedOffset` overflowed"); self } } @@ -1261,7 +1262,7 @@ impl Add for DateTime { type Output = DateTime; fn add(self, rhs: Months) -> Self::Output { - self.checked_add_months(rhs).unwrap() + self.checked_add_months(rhs).expect("`DateTime + Months` out of range") } } @@ -1309,7 +1310,8 @@ impl Sub for DateTime { #[inline] fn sub(mut self, rhs: FixedOffset) -> DateTime { - self.datetime = self.naive_utc().checked_sub_offset(rhs).unwrap(); + self.datetime = + self.naive_utc().checked_sub_offset(rhs).expect("`DateTime - FixedOffset` overflowed"); self } } @@ -1318,7 +1320,7 @@ impl Sub for DateTime { type Output = DateTime; fn sub(self, rhs: Months) -> Self::Output { - self.checked_sub_months(rhs).unwrap() + self.checked_sub_months(rhs).expect("`DateTime - Months` out of range") } } @@ -1344,7 +1346,7 @@ impl Add for DateTime { type Output = DateTime; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`DateTime + Days` out of range") } } @@ -1352,7 +1354,7 @@ impl Sub for DateTime { type Output = DateTime; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`DateTime - Days` out of range") } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 700958e171..4b3cbe84bd 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1889,7 +1889,7 @@ impl Add for NaiveDate { /// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); /// ``` fn add(self, months: Months) -> Self::Output { - self.checked_add_months(months).unwrap() + self.checked_add_months(months).expect("`NaiveDate + Months` out of range") } } @@ -1914,7 +1914,7 @@ impl Sub for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); /// ``` fn sub(self, months: Months) -> Self::Output { - self.checked_sub_months(months).unwrap() + self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") } } @@ -1922,7 +1922,7 @@ impl Add for NaiveDate { type Output = NaiveDate; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`NaiveDate + Days` out of range") } } @@ -1930,7 +1930,7 @@ impl Sub for NaiveDate { type Output = NaiveDate; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`NaiveDate - Days` out of range") } } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 67e12c71f7..69ae76613b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1624,7 +1624,7 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_add_offset(rhs).unwrap() + self.checked_add_offset(rhs).expect("`NaiveDateTime + FixedOffset` out of range") } } @@ -1668,7 +1668,7 @@ impl Add for NaiveDateTime { /// ); /// ``` fn add(self, rhs: Months) -> Self::Output { - Self { date: self.date.checked_add_months(rhs).unwrap(), time: self.time } + self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") } } @@ -1760,7 +1760,7 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_sub_offset(rhs).unwrap() + self.checked_sub_offset(rhs).expect("`NaiveDateTime - FixedOffset` out of range") } } @@ -1792,7 +1792,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, rhs: Months) -> Self::Output { - Self { date: self.date.checked_sub_months(rhs).unwrap(), time: self.time } + self.checked_sub_months(rhs).expect("`NaiveDateTime - Months` out of range") } } @@ -1848,7 +1848,7 @@ impl Add for NaiveDateTime { type Output = NaiveDateTime; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`NaiveDateTime + Days` out of range") } } @@ -1856,7 +1856,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`NaiveDateTime - Days` out of range") } } From 483da3b8343b1de43681d4530a71450703e3e4aa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 16:33:53 +0200 Subject: [PATCH 584/999] Clamp `std::time::Duration` before converting to prevent panic --- src/naive/time/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index b9de19d0cb..46a3ca4697 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1165,18 +1165,19 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: Duration) -> NaiveTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.overflowing_add_signed(rhs).0 + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `chrono::Duration`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + self.overflowing_add_signed(d).0 } } impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - *self += rhs; + *self = *self + rhs; } } @@ -1256,18 +1257,19 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: Duration) -> NaiveTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.overflowing_sub_signed(rhs).0 + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `chrono::Duration`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + self.overflowing_sub_signed(d).0 } } impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - *self -= rhs; + *self = *self - rhs; } } From 78230395a6facb88085d2ec16d5253e50ca3c546 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:21:05 +0200 Subject: [PATCH 585/999] Move doc comments from method to trait (for consistency) --- src/naive/date.rs | 74 +++++++++++++++++++-------------------- src/naive/datetime/mod.rs | 72 ++++++++++++++++++------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4b3cbe84bd..3b41640f8c 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1865,54 +1865,54 @@ impl AddAssign for NaiveDate { } } +/// An addition of months to `NaiveDate` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, Months}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); +/// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); +/// ``` impl Add for NaiveDate { type Output = NaiveDate; - /// An addition of months to `NaiveDate` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Months}; - /// - /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - /// - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); - /// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); - /// ``` fn add(self, months: Months) -> Self::Output { self.checked_add_months(months).expect("`NaiveDate + Months` out of range") } } +/// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, Months}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); +/// ``` impl Sub for NaiveDate { type Output = NaiveDate; - /// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Months}; - /// - /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - /// - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); - /// ``` fn sub(self, months: Months) -> Self::Output { self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 69ae76613b..06ddf7ebb1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1628,45 +1628,45 @@ impl Add for NaiveDateTime { } } +/// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), +/// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), +/// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// ); +/// ``` impl Add for NaiveDateTime { type Output = NaiveDateTime; - /// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{Months, NaiveDate}; - /// - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), - /// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), - /// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), - /// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() - /// ); - /// ``` fn add(self, rhs: Months) -> Self::Output { self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") } From e3091d1b3e15c967a4b90d02f64ca9da2e7a268c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:22:05 +0200 Subject: [PATCH 586/999] Consistently document `Add` and `Sub` impls of `NaiveDate` --- src/naive/date.rs | 65 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 3b41640f8c..9677b0c439 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1826,10 +1826,15 @@ impl Datelike for NaiveDate { } } -/// An addition of `Duration` to `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. +/// Add `chrono::Duration` to `NaiveDate`. /// -/// Panics on underflow or overflow. Use [`NaiveDate::checked_add_signed`] to detect that. +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. /// /// # Example /// @@ -1858,6 +1863,15 @@ impl Add for NaiveDate { } } +/// Add-assign of `chrono::Duration` to `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDate { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1865,11 +1879,15 @@ impl AddAssign for NaiveDate { } } -/// An addition of months to `NaiveDate` clamped to valid days in resulting month. +/// Add `Months` to `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_months` to get an `Option` instead. /// /// # Example /// @@ -1893,11 +1911,15 @@ impl Add for NaiveDate { } } -/// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. +/// Subtract `Months` from `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_sub_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_months` to get an `Option` instead. /// /// # Example /// @@ -1918,6 +1940,12 @@ impl Sub for NaiveDate { } } +/// Add `Days` to `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_days` to get an `Option` instead. impl Add for NaiveDate { type Output = NaiveDate; @@ -1926,6 +1954,12 @@ impl Add for NaiveDate { } } +/// Subtract `Days` from `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_days` to get an `Option` instead. impl Sub for NaiveDate { type Output = NaiveDate; @@ -1934,11 +1968,16 @@ impl Sub for NaiveDate { } } -/// A subtraction of `Duration` from `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. +/// Subtract `chrono::Duration` from `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. /// -/// Panics on underflow or overflow. Use [`NaiveDate::checked_sub_signed`] to detect that. +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. /// /// # Example /// @@ -1967,6 +2006,16 @@ impl Sub for NaiveDate { } } +/// Subtract-assign `chrono::Duration` from `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// It is the same as the addition with a negated `Duration`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDate { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { From ea45eeded7fe48d738c714f6f4b528ddd76b2b7d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 16:13:15 +0200 Subject: [PATCH 587/999] Document `Add` and `Sub` impls of `NaiveTime` --- src/naive/time/mod.rs | 44 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 46a3ca4697..8418ffc29c 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1094,7 +1094,9 @@ impl Timelike for NaiveTime { } } -/// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. +/// Add `chrono::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap @@ -1153,6 +1155,10 @@ impl Add for NaiveTime { } } +/// Add-assign `chrono::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1160,6 +1166,10 @@ impl AddAssign for NaiveTime { } } +/// Add `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl Add for NaiveTime { type Output = NaiveTime; @@ -1174,6 +1184,10 @@ impl Add for NaiveTime { } } +/// Add-assign `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1181,6 +1195,10 @@ impl AddAssign for NaiveTime { } } +/// Add `FixedOffset` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl Add for NaiveTime { type Output = NaiveTime; @@ -1190,9 +1208,11 @@ impl Add for NaiveTime { } } -/// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// It is the same as the addition with a negated `Duration`. +/// Subtract `chrono::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +/// This is the same as addition with a negated `Duration`. /// /// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap /// second ever**, except when the `NaiveTime` itself represents a leap second in which case the @@ -1245,6 +1265,10 @@ impl Sub for NaiveTime { } } +/// Subtract-assign `chrono::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1252,6 +1276,10 @@ impl SubAssign for NaiveTime { } } +/// Subtract `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl Sub for NaiveTime { type Output = NaiveTime; @@ -1266,6 +1294,10 @@ impl Sub for NaiveTime { } } +/// Subtract-assign `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1273,6 +1305,10 @@ impl SubAssign for NaiveTime { } } +/// Subtract `FixedOffset` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl Sub for NaiveTime { type Output = NaiveTime; From 353a29e4edece897511375ebb3b65cdf7613d1ad Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:54:00 +0200 Subject: [PATCH 588/999] Consistently document `Add` and `Sub` impls of `NaiveDateTime` --- src/naive/datetime/mod.rs | 115 +++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 06ddf7ebb1..af399d8a67 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1533,7 +1533,7 @@ impl Timelike for NaiveDateTime { } } -/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. +/// Add `chrono::Duration` to `NaiveDateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1541,8 +1541,8 @@ impl Timelike for NaiveDateTime { /// /// # Panics /// -/// Panics if the resulting date would be out of range. Use [`NaiveDateTime::checked_add_signed`] -/// to detect that. +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. /// /// # Example /// @@ -1594,6 +1594,16 @@ impl Add for NaiveDateTime { } } +/// Add `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1605,6 +1615,16 @@ impl Add for NaiveDateTime { } } +/// Add-assign `chrono::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1612,6 +1632,16 @@ impl AddAssign for NaiveDateTime { } } +/// Add-assign `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1619,6 +1649,12 @@ impl AddAssign for NaiveDateTime { } } +/// Add `FixedOffset` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_offset` to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1628,11 +1664,15 @@ impl Add for NaiveDateTime { } } -/// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. +/// Add `Months` to `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `checked_add_months` to get an `Option` instead. /// /// # Example /// @@ -1672,15 +1712,18 @@ impl Add for NaiveDateTime { } } -/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. -/// It is the same as the addition with a negated `Duration`. +/// Subtract `chrono::Duration` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `Duration`. /// /// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case /// the assumption becomes that **there is exactly a single leap second ever**. /// -/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_sub_signed`] -/// to detect that. +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. /// /// # Example /// @@ -1730,6 +1773,16 @@ impl Sub for NaiveDateTime { } } +/// Subtract `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1741,6 +1794,18 @@ impl Sub for NaiveDateTime { } } +/// Subtract-assign `chrono::Duration` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1748,6 +1813,16 @@ impl SubAssign for NaiveDateTime { } } +/// Subtract-assign `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1755,6 +1830,12 @@ impl SubAssign for NaiveDateTime { } } +/// Subtract `FixedOffset` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_offset` to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1764,11 +1845,15 @@ impl Sub for NaiveDateTime { } } -/// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. +/// Subtract `Months` from `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`NaiveDateTime::checked_sub_months`] for details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_months`] to get an `Option` instead. /// /// # Example /// @@ -1844,6 +1929,12 @@ impl Sub for NaiveDateTime { } } +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_days` to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1852,6 +1943,12 @@ impl Add for NaiveDateTime { } } +/// Subtract `Days` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_days` to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; From 652bd7842ab5a6856f241c23260789cbf3ab6c37 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 12:28:06 +0200 Subject: [PATCH 589/999] Document `Add` and `Sub` impls of `DateTime` --- src/datetime/mod.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b05744e653..3efd303817 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1208,6 +1208,16 @@ impl hash::Hash for DateTime { } } +/// Add `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1217,6 +1227,16 @@ impl Add for DateTime { } } +/// Add `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1228,6 +1248,16 @@ impl Add for DateTime { } } +/// Add-assign `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1238,6 +1268,16 @@ impl AddAssign for DateTime { } } +/// Add-assign `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1247,6 +1287,11 @@ impl AddAssign for DateTime { } } +/// Add `FixedOffset` to the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. impl Add for DateTime { type Output = DateTime; @@ -1258,6 +1303,19 @@ impl Add for DateTime { } } +/// Add `Months` to `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_add_months`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1266,6 +1324,18 @@ impl Add for DateTime { } } +/// Subtract `chrono::Duration` from `DateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1275,6 +1345,16 @@ impl Sub for DateTime { } } +/// Subtract `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1286,6 +1366,18 @@ impl Sub for DateTime { } } +/// Subtract-assign `chrono::Duration` from `DateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1296,6 +1388,16 @@ impl SubAssign for DateTime { } } +/// Subtract-assign `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1305,6 +1407,11 @@ impl SubAssign for DateTime { } } +/// Subtract `FixedOffset` from the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. impl Sub for DateTime { type Output = DateTime; @@ -1316,6 +1423,19 @@ impl Sub for DateTime { } } +/// Subtract `Months` from `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`DateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_sub_months`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1342,6 +1462,16 @@ impl Sub<&DateTime> for DateTime { } } +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1350,6 +1480,16 @@ impl Add for DateTime { } } +/// Subtract `Days` from `DateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; From cf62e4648c5c42f0ccc17483d48cdaeaa9cb3788 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 3 May 2023 17:15:26 +0200 Subject: [PATCH 590/999] Fix panic in default impl of `TimeZone::from_local_datetime` --- src/naive/datetime/tests.rs | 24 +++++++++++++++++-- src/offset/mod.rs | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 4558bf7f42..067438ce71 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,7 +1,6 @@ use super::NaiveDateTime; use crate::duration::Duration as OldDuration; -use crate::NaiveDate; -use crate::{Datelike, FixedOffset, Utc}; +use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, Utc}; #[test] fn test_datetime_from_timestamp_millis() { @@ -490,3 +489,24 @@ fn test_checked_sub_offset() { assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); } + +#[test] +fn test_and_timezone_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = NaiveDateTime::MAX.and_local_timezone(offset); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, LocalResult::None); + } + let local_min = NaiveDateTime::MIN.and_local_timezone(offset); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, LocalResult::None); + } + } +} diff --git a/src/offset/mod.rs b/src/offset/mod.rs index c3154ee894..fae7da00d3 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -500,8 +500,24 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - self.offset_from_local_datetime(local) - .map(|offset| DateTime::from_naive_utc_and_offset(*local - offset.fix(), offset)) + // Return `LocalResult::None` when the offset pushes a value out of range, instead of + // panicking. + match self.offset_from_local_datetime(local) { + LocalResult::None => LocalResult::None, + LocalResult::Single(offset) => match local.checked_sub_offset(offset.fix()) { + Some(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)), + None => LocalResult::None, + }, + LocalResult::Ambiguous(o1, o2) => { + match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { + (Some(d1), Some(d2)) => LocalResult::Ambiguous( + DateTime::from_naive_utc_and_offset(d1, o1), + DateTime::from_naive_utc_and_offset(d2, o2), + ), + _ => LocalResult::None, + } + } + } } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. @@ -531,6 +547,32 @@ pub trait TimeZone: Sized + Clone { mod tests { use super::*; + #[test] + fn test_fixed_offset_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = offset.from_utc_datetime(&NaiveDateTime::MAX); + assert_eq!(local_max.naive_utc(), NaiveDateTime::MAX); + let local_min = offset.from_utc_datetime(&NaiveDateTime::MIN); + assert_eq!(local_min.naive_utc(), NaiveDateTime::MIN); + + let local_max = offset.from_local_datetime(&NaiveDateTime::MAX); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, LocalResult::None); + } + let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, LocalResult::None); + } + } + } + #[test] fn test_negative_millis() { let dt = Utc.timestamp_millis_opt(-1000).unwrap(); From 00324314910fedb1192bedbcb167b0ffca673e41 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 19:07:21 +0200 Subject: [PATCH 591/999] Remove obsolete overflow check --- src/format/parsed.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index ad0d0013a3..fb9d113442 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -634,11 +634,6 @@ impl Parsed { let datetime = self.to_naive_datetime_with_offset(offset)?; let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; - // this is used to prevent an overflow when calling FixedOffset::from_local_datetime - datetime - .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) - .ok_or(OUT_OF_RANGE)?; - match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), From 068b4bd6772b6e35be3756856478f261070b9885 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 20:26:05 +0200 Subject: [PATCH 592/999] Add `DateTime::to_utc` --- src/datetime/mod.rs | 8 ++++++++ src/datetime/tests.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3efd303817..e580050bee 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -379,6 +379,14 @@ impl DateTime { self.with_timezone(&self.offset().fix()) } + /// Turn this `DateTime` into a `DateTime`, dropping the offset and associated timezone + /// information. + #[inline] + #[must_use] + pub fn to_utc(&self) -> DateTime { + DateTime { datetime: self.datetime, offset: Utc } + } + /// Adds given `Duration` to the current date and time. /// /// # Errors diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3872e14b36..03c4949a14 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1428,6 +1428,14 @@ fn test_datetime_fixed_offset() { assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); } +#[test] +fn test_datetime_to_utc() { + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 2, 22, 23, 24, 25).unwrap(); + let dt_utc: DateTime = dt.to_utc(); + assert_eq!(dt, dt_utc); +} + #[test] fn test_add_sub_months() { let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); From b72752fd41f844b4dacb0974fe7826d8bd367be4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 18 May 2023 09:13:03 +0200 Subject: [PATCH 593/999] Adjust MIN_YEAR and MAX_YEAR --- src/naive/date.rs | 27 +++++++++++++++------------ src/naive/datetime/mod.rs | 10 +++++----- src/naive/internals.rs | 11 +++++++++-- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9677b0c439..6a99a2438c 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1457,10 +1457,10 @@ impl NaiveDate { self.of().weekday() } - /// The minimum possible `NaiveDate` (January 1, 262145 BCE). - pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; - /// The maximum possible `NaiveDate` (December 31, 262143 CE). - pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). + pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; + /// The maximum possible `NaiveDate` (December 31, 262142 CE). + pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; } impl Datelike for NaiveDate { @@ -2277,8 +2277,8 @@ where to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), Some(r#""-0001-12-31""#.into()) ); - assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262144-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262143-12-31""#.into())); + assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262143-01-01""#.into())); + assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262142-12-31""#.into())); } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] @@ -2301,8 +2301,8 @@ where from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) ); - assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(NaiveDate::MIN)); - assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(NaiveDate::MAX)); + assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); // bad formats assert!(from_str(r#""""#).is_err()); @@ -3155,9 +3155,12 @@ mod tests { #[test] fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); assert_eq!( - NaiveDate::from_ymd_opt(-262144, 1, 3).unwrap().iter_days().rev().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), + 2 + ); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), 2 ); } @@ -3165,11 +3168,11 @@ mod tests { #[test] fn test_week_iterator_limit() { assert_eq!( - NaiveDate::from_ymd_opt(262143, 12, 12).unwrap().iter_weeks().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2 ); assert_eq!( - NaiveDate::from_ymd_opt(-262144, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), 2 ); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index af399d8a67..535be49f89 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -2123,11 +2123,11 @@ where ); assert_eq!( to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), - Some(r#""-262144-01-01T00:00:00""#.into()) + Some(r#""-262143-01-01T00:00:00""#.into()) ); assert_eq!( to_string(&NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), - Some(r#""+262143-12-31T23:59:60.999999999""#.into()) + Some(r#""+262142-12-31T23:59:60.999999999""#.into()) ); } @@ -2166,15 +2166,15 @@ where Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap()) ); assert_eq!( - from_str(r#""-262144-01-01T00:00:00""#).ok(), + from_str(r#""-262143-01-01T00:00:00""#).ok(), Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) ); assert_eq!( - from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), + from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); assert_eq!( - from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored + from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 910419f387..c6d7536a0c 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -21,8 +21,15 @@ use core::fmt; /// The internal date representation: `year << 13 | Of` pub(super) type DateImpl = i32; -pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13; -pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: DateImpl = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: DateImpl = (i32::MIN >> 13) + 1; /// The year flags (aka the dominical letter). /// From 39773653e661a6537044d15de0d1ebdb1e1ba38c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 14:24:48 +0200 Subject: [PATCH 594/999] Add `NaiveDate::{BEFORE_MIN, AFTER_MAX}` --- src/naive/date.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 6a99a2438c..1c57b89d62 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1461,6 +1461,13 @@ impl NaiveDate { pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; + + /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). + pub(crate) const BEFORE_MIN: NaiveDate = + NaiveDate { ymdf: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; + /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). + pub(crate) const AFTER_MAX: NaiveDate = + NaiveDate { ymdf: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { @@ -2444,6 +2451,7 @@ mod serde { mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; use crate::duration::Duration; + use crate::naive::internals::YearFlags; use crate::{Datelike, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, @@ -2473,6 +2481,14 @@ mod tests { "The entire `NaiveDate` range somehow exceeds 2^{} seconds", MAX_BITS ); + + const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; + assert_eq!(BEFORE_MIN.of().flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); + + const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; + assert_eq!(AFTER_MAX.of().flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); } #[test] From 6f94e45c0e478c7633f275571478ed1cb4ca2449 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 12:20:35 +0200 Subject: [PATCH 595/999] Tests and nicer panic message for `DateTime::naive_local` --- src/datetime/mod.rs | 4 +++- src/datetime/tests.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e580050bee..83b1dced29 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -513,7 +513,9 @@ impl DateTime { #[inline] #[must_use] pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() + self.datetime + .checked_add_offset(self.offset.fix()) + .expect("Local time out of range for `NaiveDateTime`") } /// Retrieve the elapsed years from now to the given [`DateTime`]. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 03c4949a14..5d8876a120 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1331,6 +1331,20 @@ fn test_datetime_sub_assign() { assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); } +#[test] +#[should_panic] +fn test_local_beyond_min_datetime() { + let min = FixedOffset::west_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MIN); + let _ = min.naive_local(); +} + +#[test] +#[should_panic] +fn test_local_beyond_max_datetime() { + let max = FixedOffset::east_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MAX); + let _ = max.naive_local(); +} + #[test] #[cfg(feature = "clock")] fn test_datetime_sub_assign_local() { From 4ca5a5292f2b5d3ec3e3fa394b186d30724e3fb4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 May 2023 17:03:30 +0200 Subject: [PATCH 596/999] Add `overflowing_naive_local` and `NaiveDateTime::overflowing_add_offset` --- src/datetime/mod.rs | 11 +++++++++++ src/naive/datetime/mod.rs | 16 ++++++++++++++++ src/naive/datetime/tests.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 83b1dced29..58cc7af288 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -518,6 +518,17 @@ impl DateTime { .expect("Local time out of range for `NaiveDateTime`") } + /// Returns the naive local datetime. + /// + /// This makes use of the buffer space outside of the representable range of values of + /// `NaiveDateTime`. The result can be used as intermediate value, but should never be exposed + /// outside chrono. + #[inline] + #[must_use] + pub(crate) fn overflowing_naive_local(&self) -> NaiveDateTime { + self.datetime.overflowing_add_offset(self.offset.fix()) + } + /// Retrieve the elapsed years from now to the given [`DateTime`]. /// /// # Errors diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 535be49f89..64f52d8d19 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -738,6 +738,22 @@ impl NaiveDateTime { Some(NaiveDateTime { date, time }) } + /// Adds given `FixedOffset` to the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + pub(crate) fn overflowing_add_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + /// Subtracts given `Duration` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 067438ce71..97604db74f 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -490,6 +490,32 @@ fn test_checked_sub_offset() { assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); } +#[test] +fn test_overflowing_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() + }; + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN); +} + #[test] fn test_and_timezone_min_max_dates() { for offset_hour in -23..=23 { From e6580db09516d131822dcadbf9646bc23ac40b33 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 17:34:29 +0200 Subject: [PATCH 597/999] Use `overflowing_naive_local` in methods that don't return `DateTime` --- src/datetime/mod.rs | 40 ++++++++++++++--------------- src/datetime/tests.rs | 60 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 58cc7af288..4e1befc978 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -22,7 +22,7 @@ use crate::format::{ StrftimeItems, TOO_LONG, }; #[cfg(feature = "alloc")] -use crate::format::{write_rfc3339, DelayedFormat}; +use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -561,7 +561,7 @@ impl DateTime { #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); - crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) + write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) .expect("writing rfc2822 datetime to string should never fail"); result } @@ -572,7 +572,7 @@ impl DateTime { pub fn to_rfc3339(&self) -> String { // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. let mut result = String::with_capacity(32); - let naive = self.naive_local(); + let naive = self.overflowing_naive_local(); let offset = self.offset.fix(); write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) .expect("writing rfc3339 datetime to string should never fail"); @@ -888,7 +888,7 @@ where I: Iterator + Clone, B: Borrow>, { - let local = self.naive_local(); + let local = self.overflowing_naive_local(); DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) } @@ -924,7 +924,7 @@ where I: Iterator + Clone, B: Borrow>, { - let local = self.naive_local(); + let local = self.overflowing_naive_local(); DelayedFormat::new_with_offset_and_locale( Some(local.date()), Some(local.time()), @@ -954,39 +954,39 @@ where impl Datelike for DateTime { #[inline] fn year(&self) -> i32 { - self.naive_local().year() + self.overflowing_naive_local().year() } #[inline] fn month(&self) -> u32 { - self.naive_local().month() + self.overflowing_naive_local().month() } #[inline] fn month0(&self) -> u32 { - self.naive_local().month0() + self.overflowing_naive_local().month0() } #[inline] fn day(&self) -> u32 { - self.naive_local().day() + self.overflowing_naive_local().day() } #[inline] fn day0(&self) -> u32 { - self.naive_local().day0() + self.overflowing_naive_local().day0() } #[inline] fn ordinal(&self) -> u32 { - self.naive_local().ordinal() + self.overflowing_naive_local().ordinal() } #[inline] fn ordinal0(&self) -> u32 { - self.naive_local().ordinal0() + self.overflowing_naive_local().ordinal0() } #[inline] fn weekday(&self) -> Weekday { - self.naive_local().weekday() + self.overflowing_naive_local().weekday() } #[inline] fn iso_week(&self) -> IsoWeek { - self.naive_local().iso_week() + self.overflowing_naive_local().iso_week() } #[inline] @@ -1105,19 +1105,19 @@ impl Datelike for DateTime { impl Timelike for DateTime { #[inline] fn hour(&self) -> u32 { - self.naive_local().hour() + self.overflowing_naive_local().hour() } #[inline] fn minute(&self) -> u32 { - self.naive_local().minute() + self.overflowing_naive_local().minute() } #[inline] fn second(&self) -> u32 { - self.naive_local().second() + self.overflowing_naive_local().second() } #[inline] fn nanosecond(&self) -> u32 { - self.naive_local().nanosecond() + self.overflowing_naive_local().nanosecond() } /// Makes a new `DateTime` with the hour number changed. @@ -1521,7 +1521,7 @@ impl Sub for DateTime { impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.naive_local().fmt(f)?; + self.overflowing_naive_local().fmt(f)?; self.offset.fmt(f) } } @@ -1531,7 +1531,7 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.naive_local().fmt(f)?; + self.overflowing_naive_local().fmt(f)?; f.write_char(' ')?; self.offset.fmt(f) } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 5d8876a120..29de0dc93f 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,7 +4,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike}; +use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike, Weekday}; #[derive(Clone)] struct DstTester; @@ -1331,6 +1331,64 @@ fn test_datetime_sub_assign() { assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); } +#[test] +fn test_min_max_getters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_min.to_rfc2822(), ""); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!( + beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), + "-262144-12-31T22:00:00-02:00" + ); + assert_eq!(beyond_min.year(), -262144); + assert_eq!(beyond_min.month(), 12); + assert_eq!(beyond_min.month0(), 11); + assert_eq!(beyond_min.day(), 31); + assert_eq!(beyond_min.day0(), 30); + assert_eq!(beyond_min.ordinal(), 366); + assert_eq!(beyond_min.ordinal0(), 365); + assert_eq!(beyond_min.weekday(), Weekday::Wed); + assert_eq!(beyond_min.iso_week().year(), -262143); + assert_eq!(beyond_min.iso_week().week(), 1); + assert_eq!(beyond_min.hour(), 22); + assert_eq!(beyond_min.minute(), 0); + assert_eq!(beyond_min.second(), 0); + assert_eq!(beyond_min.nanosecond(), 0); + + assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_max.to_rfc2822(), ""); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!( + beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), + "+262143-01-01T01:59:59.999999999+02:00" + ); + assert_eq!(beyond_max.year(), 262143); + assert_eq!(beyond_max.month(), 1); + assert_eq!(beyond_max.month0(), 0); + assert_eq!(beyond_max.day(), 1); + assert_eq!(beyond_max.day0(), 0); + assert_eq!(beyond_max.ordinal(), 1); + assert_eq!(beyond_max.ordinal0(), 0); + assert_eq!(beyond_max.weekday(), Weekday::Tue); + assert_eq!(beyond_max.iso_week().year(), 262143); + assert_eq!(beyond_max.iso_week().week(), 1); + assert_eq!(beyond_max.hour(), 1); + assert_eq!(beyond_max.minute(), 59); + assert_eq!(beyond_max.second(), 59); + assert_eq!(beyond_max.nanosecond(), 999_999_999); +} + #[test] #[should_panic] fn test_local_beyond_min_datetime() { From e28f2afa700032108abf5fea26f57a00b4286ef6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 18:10:01 +0200 Subject: [PATCH 598/999] Use `overflowing_naive_local` in `map_local` This fixes out-of-range panics in all the `with_*` methods that use `map_local`. --- src/datetime/mod.rs | 4 +++- src/datetime/tests.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 4e1befc978..5c9e888b6e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -750,7 +750,9 @@ fn map_local(dt: &DateTime, mut f: F) -> Option Option, { - f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + f(dt.overflowing_naive_local()) + .and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC && dt <= &DateTime::::MAX_UTC) } impl DateTime { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 29de0dc93f..9e41a51096 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1389,6 +1389,54 @@ fn test_min_max_getters() { assert_eq!(beyond_max.nanosecond(), 999_999_999); } +#[test] +fn test_min_max_setters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(beyond_min.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_min.with_month(beyond_min.month()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(3), None); + assert_eq!(beyond_min.with_month0(beyond_min.month0()), Some(beyond_min)); + assert_eq!(beyond_min.with_month0(3), None); + assert_eq!(beyond_min.with_day(beyond_min.day()), Some(beyond_min)); + assert_eq!(beyond_min.with_day(15), None); + assert_eq!(beyond_min.with_day0(beyond_min.day0()), Some(beyond_min)); + assert_eq!(beyond_min.with_day0(15), None); + assert_eq!(beyond_min.with_ordinal(beyond_min.ordinal()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal(200), None); + assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal0(200), None); + assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); + assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(OldDuration::hours(1))); + assert_eq!(beyond_min.with_hour(5), None); + assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); + assert_eq!(beyond_min.with_second(0), Some(beyond_min)); + assert_eq!(beyond_min.with_nanosecond(0), Some(beyond_min)); + + assert_eq!(beyond_max.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_max.with_month(beyond_max.month()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(3), None); + assert_eq!(beyond_max.with_month0(beyond_max.month0()), Some(beyond_max)); + assert_eq!(beyond_max.with_month0(3), None); + assert_eq!(beyond_max.with_day(beyond_max.day()), Some(beyond_max)); + assert_eq!(beyond_max.with_day(15), None); + assert_eq!(beyond_max.with_day0(beyond_max.day0()), Some(beyond_max)); + assert_eq!(beyond_max.with_day0(15), None); + assert_eq!(beyond_max.with_ordinal(beyond_max.ordinal()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal(200), None); + assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal0(200), None); + assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); + assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(OldDuration::hours(1))); + assert_eq!(beyond_max.with_hour(5), None); + assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); + assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); + assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); +} + #[test] #[should_panic] fn test_local_beyond_min_datetime() { From 22846c9df1e79417eed477cfc5831c5d483c4eec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 16:02:04 +0200 Subject: [PATCH 599/999] Inline `write_rfc2822_inner`, don't localize --- src/format/formatting.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 8cb83c7521..f11c680e01 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -395,7 +395,7 @@ fn format_inner( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(w, *d, *t, off, locale)) + Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off)) } else { None } @@ -576,45 +576,35 @@ pub(crate) fn write_rfc2822( dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { - write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) -} - -#[cfg(feature = "alloc")] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -fn write_rfc2822_inner( - w: &mut impl Write, - d: NaiveDate, - t: NaiveTime, - off: FixedOffset, - locale: Locale, -) -> fmt::Result { - let year = d.year(); + let year = dt.year(); // RFC2822 is only defined on years 0 through 9999 if !(0..=9999).contains(&year) { return Err(fmt::Error); } - w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + let english = default_locale(); + + w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?; w.write_str(", ")?; - let day = d.day(); + let day = dt.day(); if day < 10 { w.write_char((b'0' + day as u8) as char)?; } else { write_hundreds(w, day as u8)?; } w.write_char(' ')?; - w.write_str(short_months(locale)[d.month0() as usize])?; + w.write_str(short_months(english)[dt.month0() as usize])?; w.write_char(' ')?; write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; w.write_char(' ')?; - let (hour, min, sec) = t.hms(); + let (hour, min, sec) = dt.time().hms(); write_hundreds(w, hour as u8)?; w.write_char(':')?; write_hundreds(w, min as u8)?; w.write_char(':')?; - let sec = sec + t.nanosecond() / 1_000_000_000; + let sec = sec + dt.nanosecond() / 1_000_000_000; write_hundreds(w, sec as u8)?; w.write_char(' ')?; OffsetFormat { From 295e9c83211aec160800298591039b107a356b4a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:06:55 +0200 Subject: [PATCH 600/999] Tweaks to visibility in main documentation --- src/lib.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7841afb42c..3488b9bea2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -477,6 +477,7 @@ extern crate alloc; mod duration; pub use duration::Duration; #[cfg(feature = "std")] +#[doc(no_inline)] pub use duration::OutOfRangeError; use core::fmt; @@ -508,13 +509,18 @@ pub mod prelude { mod date; #[allow(deprecated)] -pub use date::{Date, MAX_DATE, MIN_DATE}; +pub use date::Date; +#[doc(no_inline)] +#[allow(deprecated)] +pub use date::{MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] pub use datetime::rustc_serialize::TsSeconds; +pub use datetime::DateTime; #[allow(deprecated)] -pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; +#[doc(no_inline)] +pub use datetime::{SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. @@ -523,24 +529,30 @@ pub use format::Locale; pub use format::{ParseError, ParseResult}; pub mod naive; -#[doc(no_inline)] -pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; +#[doc(inline)] +pub use naive::{Days, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] -#[doc(no_inline)] +#[doc(inline)] pub use offset::Local; -#[doc(no_inline)] -pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; +pub use offset::LocalResult; +#[doc(inline)] +pub use offset::{FixedOffset, Offset, TimeZone, Utc}; mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; mod weekday; -pub use weekday::{ParseWeekdayError, Weekday}; +#[doc(no_inline)] +pub use weekday::ParseWeekdayError; +pub use weekday::Weekday; mod month; -pub use month::{Month, Months, ParseMonthError}; +#[doc(no_inline)] +pub use month::ParseMonthError; +pub use month::{Month, Months}; mod traits; pub use traits::{Datelike, Timelike}; From 017b621033b6aae06349ae36021b74f91b892391 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:20 +0200 Subject: [PATCH 601/999] Remove useless `#[doc(no_inline)]` from `prelude` module --- src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3488b9bea2..2af2aef8d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -484,26 +484,17 @@ use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { - #[doc(no_inline)] #[allow(deprecated)] pub use crate::Date; #[cfg(feature = "clock")] - #[doc(no_inline)] pub use crate::Local; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] - #[doc(no_inline)] pub use crate::Locale; - #[doc(no_inline)] pub use crate::SubsecRound; - #[doc(no_inline)] pub use crate::{DateTime, SecondsFormat}; - #[doc(no_inline)] pub use crate::{Datelike, Month, Timelike, Weekday}; - #[doc(no_inline)] pub use crate::{FixedOffset, Utc}; - #[doc(no_inline)] pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; - #[doc(no_inline)] pub use crate::{Offset, TimeZone}; } From 5da23657ccfaecde6af998e728ee51c909096c54 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:32 +0200 Subject: [PATCH 602/999] Hide private macros --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2af2aef8d9..79c098579d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -614,6 +614,7 @@ impl std::error::Error for OutOfRange {} /// Workaround because `?` is not (yet) available in const context. #[macro_export] +#[doc(hidden)] macro_rules! try_opt { ($e:expr) => { match $e { @@ -625,6 +626,7 @@ macro_rules! try_opt { /// Workaround because `.expect()` is not (yet) available in const context. #[macro_export] +#[doc(hidden)] macro_rules! expect { ($e:expr, $m:literal) => { match $e { From 819dea0480dd99d682b11e4f7ac5e2a588d2e202 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:07:08 +0200 Subject: [PATCH 603/999] `Local` does not depend on the `time` crate --- src/offset/local/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index ffd2844d55..e4f4e1631f 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -88,7 +88,7 @@ mod inner { #[cfg(unix)] mod tz_info; -/// The local timescale. This is implemented via the standard `time` crate. +/// The local timescale. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on the Local struct is the preferred way to construct `DateTime` From 5237de6e0a1996ed65d74458b581a63aaeb0623e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:37 +0200 Subject: [PATCH 604/999] Make `round` module public (so we don't need to show it in the main documentation) --- src/lib.rs | 2 +- src/round.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 79c098579d..f7638bc6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -532,7 +532,7 @@ pub use offset::LocalResult; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; -mod round; +pub mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; mod weekday; diff --git a/src/round.rs b/src/round.rs index 795ffb04cd..4de1de60c8 100644 --- a/src/round.rs +++ b/src/round.rs @@ -1,6 +1,8 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. +//! Functionality for rounding or truncating a `DateTime` by a `Duration`. + use crate::datetime::DateTime; use crate::duration::Duration; use crate::NaiveDateTime; From b7857baed26e8011e04421a49173b858759a1665 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:06:55 +0200 Subject: [PATCH 605/999] Move `SecondsFormat` from `datetime` to `format::formatting` --- src/datetime/mod.rs | 34 +-------------------------------- src/datetime/rustc_serialize.rs | 3 ++- src/datetime/serde.rs | 4 ++-- src/format/formatting.rs | 34 +++++++++++++++++++++++++++++++-- src/format/mod.rs | 1 + src/lib.rs | 4 ++-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5c9e888b6e..24a344d9ab 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -22,7 +22,7 @@ use crate::format::{ StrftimeItems, TOO_LONG, }; #[cfg(feature = "alloc")] -use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat}; +use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -44,38 +44,6 @@ pub(super) mod serde; #[cfg(test)] mod tests; -/// Specific formatting options for seconds. This may be extended in the -/// future, so exhaustive matching in external code is not recommended. -/// -/// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[allow(clippy::manual_non_exhaustive)] -pub enum SecondsFormat { - /// Format whole seconds only, with no decimal point nor subseconds. - Secs, - - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). - Millis, - - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). - Micros, - - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). - Nanos, - - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). - AutoSi, - - // Do not match against this. - #[doc(hidden)] - __NonExhaustive, -} - /// ISO 8601 combined date and time with time zone. /// /// There are some constructors implemented here (the `from_*` methods), but diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 51ef5b7230..f704b2f97b 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,4 +1,5 @@ -use super::{DateTime, SecondsFormat}; +use super::DateTime; +use crate::format::SecondsFormat; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 85655c99a6..dcdb36dd0b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1,8 +1,8 @@ use core::fmt; use serde::{de, ser}; -use super::{DateTime, SecondsFormat}; -use crate::format::write_rfc3339; +use super::DateTime; +use crate::format::{write_rfc3339, SecondsFormat}; use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f11c680e01..c0986732a5 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -11,8 +11,6 @@ use core::borrow::Borrow; use core::fmt::Display; use core::fmt::{self, Write}; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] -use crate::datetime::SecondsFormat; #[cfg(feature = "alloc")] use crate::offset::Offset; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] @@ -504,6 +502,38 @@ impl OffsetFormat { } } +/// Specific formatting options for seconds. This may be extended in the +/// future, so exhaustive matching in external code is not recommended. +/// +/// See the `TimeZone::to_rfc3339_opts` function for usage. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[allow(clippy::manual_non_exhaustive)] +pub enum SecondsFormat { + /// Format whole seconds only, with no decimal point nor subseconds. + Secs, + + /// Use fixed 3 subsecond digits. This corresponds to + /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). + Millis, + + /// Use fixed 6 subsecond digits. This corresponds to + /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). + Micros, + + /// Use fixed 9 subsecond digits. This corresponds to + /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). + Nanos, + + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to + /// display all available non-zero sub-second digits. This corresponds to + /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). + AutoSi, + + // Do not match against this. + #[doc(hidden)] + __NonExhaustive, +} + /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] diff --git a/src/format/mod.rs b/src/format/mod.rs index df75b4ffbc..fda4d425b6 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -58,6 +58,7 @@ pub(crate) use formatting::write_hundreds; pub(crate) use formatting::write_rfc2822; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) use formatting::write_rfc3339; +pub use formatting::SecondsFormat; #[cfg(feature = "alloc")] #[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; diff --git a/src/lib.rs b/src/lib.rs index f7638bc6ac..da8e00d17a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -511,13 +511,13 @@ pub use datetime::rustc_serialize::TsSeconds; pub use datetime::DateTime; #[allow(deprecated)] #[doc(no_inline)] -pub use datetime::{SecondsFormat, MAX_DATETIME, MIN_DATETIME}; +pub use datetime::{MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use format::Locale; -pub use format::{ParseError, ParseResult}; +pub use format::{ParseError, ParseResult, SecondsFormat}; pub mod naive; #[doc(inline)] From 604b92a2415177e7513a44fe712b61866758a69b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:59:13 +0200 Subject: [PATCH 606/999] Convert manual documentation links --- src/format/formatting.rs | 14 +++++--------- src/naive/date.rs | 26 ++++++++++++-------------- src/naive/datetime/mod.rs | 20 +++++++++----------- src/naive/time/mod.rs | 12 ++++++------ src/offset/mod.rs | 3 +-- 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index c0986732a5..9b055289ce 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -512,21 +512,17 @@ pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). + /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3]. Millis, - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). + /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6]. Micros, - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). + /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9]. Nanos, - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available + /// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond]. AutoSi, // Do not match against this. diff --git a/src/naive/date.rs b/src/naive/date.rs index 1c57b89d62..379e667a56 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -100,8 +100,7 @@ impl NaiveWeek { } /// Returns a [`RangeInclusive`] representing the whole week bounded by - /// [first_day](./struct.NaiveWeek.html#method.first_day) and - /// [last_day](./struct.NaiveWeek.html#method.last_day) functions. + /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions. /// /// # Panics /// @@ -160,12 +159,12 @@ impl Days { /// /// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). /// If you need a typical BCE/BC and CE/AD notation for year numbers, -/// use the [`Datelike::year_ce`](../trait.Datelike.html#method.year_ce) method. +/// use the [`Datelike::year_ce`] method. /// /// # Week Date /// /// The ISO 8601 **week date** is a triple of year number, week number -/// and [day of the week](../enum.Weekday.html) with the following rules: +/// and [day of the week](Weekday) with the following rules: /// /// * A week consists of Monday through Sunday, and is always numbered within some year. /// The week number ranges from 1 to 52 or 53 depending on the year. @@ -176,10 +175,9 @@ impl Days { /// * The year number in the week date may *not* correspond to the actual Gregorian year. /// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. /// -/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), -/// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and -/// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods -/// can be used to get the corresponding week date. +/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), but +/// [`Datelike::iso_week`] and [`Datelike::weekday`] methods can be used to get the corresponding +/// week date. /// /// # Ordinal Date /// @@ -542,7 +540,7 @@ impl NaiveDate { } /// Parses a string with the specified format string and returns a new `NaiveDate`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -591,7 +589,7 @@ impl NaiveDate { /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with /// the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -1280,7 +1278,7 @@ impl NaiveDate { } /// Formats the date with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -2139,7 +2137,7 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { impl FusedIterator for NaiveDateWeeksIterator {} /// The `Debug` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -2182,7 +2180,7 @@ impl fmt::Debug for NaiveDate { } /// The `Display` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -2210,7 +2208,7 @@ impl fmt::Display for NaiveDate { } /// Parsing a `str` into a `NaiveDate` uses the same format, -/// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`. +/// [`%Y-%m-%d`](crate::format::strftime), as in `Debug` and `Display`. /// /// # Example /// diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 64f52d8d19..e6558de2e4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -53,7 +53,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// /// # Example /// -/// `NaiveDateTime` is commonly created from [`NaiveDate`](./struct.NaiveDate.html). +/// `NaiveDateTime` is commonly created from [`NaiveDate`]. /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime}; @@ -62,8 +62,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// # let _ = dt; /// ``` /// -/// You can use typical [date-like](../trait.Datelike.html) and -/// [time-like](../trait.Timelike.html) methods, +/// You can use typical [date-like](Datelike) and [time-like](Timelike) methods, /// provided that relevant traits are in the scope. /// /// ``` @@ -113,8 +112,7 @@ impl NaiveDateTime { /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// For a non-naive version of this function see - /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). + /// For a non-naive version of this function see [`TimeZone::timestamp`]. /// /// The nanosecond part can exceed 1,000,000,000 in order to represent a /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. @@ -244,7 +242,7 @@ impl NaiveDateTime { } /// Parses a string with the specified format string and returns a new `NaiveDateTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -322,7 +320,7 @@ impl NaiveDateTime { /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a /// slice with the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -958,7 +956,7 @@ impl NaiveDateTime { } /// Formats the combined date and time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -1087,7 +1085,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 11. /// - /// See also the [`NaiveDate::month0`](./struct.NaiveDate.html#method.month0) method. + /// See also the [`NaiveDate::month0`] method. /// /// # Example /// @@ -1125,7 +1123,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 30. (The last day of month differs by months.) /// - /// See also the [`NaiveDate::day0`](./struct.NaiveDate.html#method.day0) method. + /// See also the [`NaiveDate::day0`] method. /// /// # Example /// @@ -1163,7 +1161,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 365. (The last day of year differs by years.) /// - /// See also the [`NaiveDate::ordinal0`](./struct.NaiveDate.html#method.ordinal0) method. + /// See also the [`NaiveDate::ordinal0`] method. /// /// # Example /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8418ffc29c..4882bb69b0 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -474,7 +474,7 @@ impl NaiveTime { } /// Parses a string with the specified format string and returns a new `NaiveTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -542,7 +542,7 @@ impl NaiveTime { /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with /// the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -819,7 +819,7 @@ impl NaiveTime { } /// Formats the time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -1373,7 +1373,7 @@ impl Sub for NaiveTime { } /// The `Debug` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -1429,7 +1429,7 @@ impl fmt::Debug for NaiveTime { } /// The `Display` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -1463,7 +1463,7 @@ impl fmt::Display for NaiveTime { } /// Parsing a `str` into a `NaiveTime` uses the same format, -/// [`%H:%M:%S%.f`](../format/strftime/index.html), as in `Debug` and `Display`. +/// [`%H:%M:%S%.f`](crate::format::strftime), as in `Debug` and `Display`. /// /// # Example /// diff --git a/src/offset/mod.rs b/src/offset/mod.rs index fae7da00d3..99b64b2760 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -211,8 +211,7 @@ pub trait Offset: Sized + Clone + fmt::Debug { /// The time zone. /// -/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and -/// [`DateTime`](../struct.DateTime.html) types. +/// The methods here are the primary constructors for [`Date`] and [`DateTime`] types. pub trait TimeZone: Sized + Clone { /// An associated offset type. /// This type is used to store the actual offset in date and time types. From bb4e9b03583858da8031b83fa535cbaec0055c58 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 28 Sep 2023 07:16:06 +0200 Subject: [PATCH 607/999] Remove unnecessary feature gate --- src/datetime/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 9e41a51096..42b014983d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1341,9 +1341,9 @@ fn test_min_max_getters() { assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_min.to_rfc2822(), ""); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!( beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), "-262144-12-31T22:00:00-02:00" @@ -1366,9 +1366,9 @@ fn test_min_max_getters() { assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_max.to_rfc2822(), ""); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!( beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), "+262143-01-01T01:59:59.999999999+02:00" From 4bbfa9ffe16c95044977b506d967244bb01511ef Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Sep 2023 12:50:40 +0200 Subject: [PATCH 608/999] Fix quotes in docs.rs feature list --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5f2c8cbf3..7d82ba3eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ bincode = { version = "1.3.0" } wasm-bindgen-test = "0.3" [package.metadata.docs.rs] -features = ["arbitrary, rkyv, serde, unstable-locales"] +features = ["arbitrary", "rkyv", "serde", "unstable-locales"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] From 6d3a77dc5a704a84f8332096b30244b185e01c1a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:43:36 +0200 Subject: [PATCH 609/999] Derive `Default` for `Duration`. --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 570c6ce5f3..7673ede27a 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -50,7 +50,7 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// /// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", From 8018c326844ef140a56169ca4d0b92f4e719f601 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 08:49:56 +0200 Subject: [PATCH 610/999] Rename `Duration::nanos_mod_sec` to `subsec_nanos`, make public --- src/duration.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 7673ede27a..b46f1705b6 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -184,9 +184,9 @@ impl Duration { } /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of + /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. - const fn nanos_mod_sec(&self) -> i32 { + pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { @@ -199,7 +199,7 @@ impl Duration { // A proper Duration will not overflow, because MIN and MAX are defined // such that the range is exactly i64 milliseconds. let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; secs_part + nanos_part as i64 } @@ -207,7 +207,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; + let nanos_part = self.subsec_nanos() / NANOS_PER_MICRO; secs_part.checked_add(nanos_part as i64) } @@ -215,7 +215,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); - let nanos_part = self.nanos_mod_sec(); + let nanos_part = self.subsec_nanos(); secs_part.checked_add(nanos_part as i64) } From a2820c4f7db20a77d69bdb62d822394c52db0aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 22 Jan 2023 19:59:13 +0100 Subject: [PATCH 611/999] Add Duration::try_* builders --- src/duration.rs | 56 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index b46f1705b6..c7958f3b36 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -80,8 +80,15 @@ impl Duration { #[inline] #[must_use] pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) + Duration::try_weeks(weeks).expect("Duration::weeks out of bounds") + } + + /// Makes a new `Duration` with given number of weeks. + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_weeks(weeks: i64) -> Option { + weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of days. @@ -90,8 +97,15 @@ impl Duration { #[inline] #[must_use] pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) + Duration::try_days(days).expect("Duration::days out of bounds") + } + + /// Makes a new `Duration` with given number of days. + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_days(days: i64) -> Option { + days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of hours. @@ -100,8 +114,15 @@ impl Duration { #[inline] #[must_use] pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) + Duration::try_hours(hours).expect("Duration::hours ouf of bounds") + } + + /// Makes a new `Duration` with given number of hours. + /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_hours(hours: i64) -> Option { + hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of minutes. @@ -110,8 +131,15 @@ impl Duration { #[inline] #[must_use] pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) + Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") + } + + /// Makes a new `Duration` with given number of minutes. + /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_minutes(minutes: i64) -> Option { + minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of seconds. @@ -120,11 +148,19 @@ impl Duration { #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { + Duration::try_seconds(seconds).expect("Duration::seconds out of bounds") + } + + /// Makes a new `Duration` with given number of seconds. + /// Returns `None` when the duration is more than `i64::MAX` milliseconds + /// or less than `i64::MIN` milliseconds. + #[inline] + pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); + return None; } - d + Some(d) } /// Makes a new `Duration` with given number of milliseconds. From bd6573aebec9cb337a1788cea9c65e441f5d7c36 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:19:38 +0200 Subject: [PATCH 612/999] Use `checked_add` and `checked_sub` in `Add` and `Sub` impls --- src/duration.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index c7958f3b36..add0333872 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -372,13 +372,7 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs, nanos } + self.checked_add(&rhs).expect("`Duration + Duration` overflowed") } } @@ -386,13 +380,7 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs, nanos } + self.checked_sub(&rhs).expect("`Duration - Duration` overflowed") } } From c8e3eb4ce1130dffa7eb35a2f7472d75de86a648 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:41:37 +0200 Subject: [PATCH 613/999] Implement `AddAssign` and `SubAssign` --- src/duration.rs | 21 ++++++++++++++++++++- src/naive/time/mod.rs | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index add0333872..fb64591b80 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -10,7 +10,7 @@ //! Temporal quantification -use core::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration as StdDuration; use core::{fmt, i64}; #[cfg(feature = "std")] @@ -384,6 +384,20 @@ impl Sub for Duration { } } +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + let new = self.checked_add(&rhs).expect("`Duration + Duration` overflowed"); + *self = new; + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + let new = self.checked_sub(&rhs).expect("`Duration - Duration` overflowed"); + *self = new; + } +} + impl Mul for Duration { type Output = Duration; @@ -533,6 +547,11 @@ mod tests { -(Duration::days(3) + Duration::seconds(70)), Duration::days(-4) + Duration::seconds(86_400 - 70) ); + + let mut d = Duration::default(); + d += Duration::minutes(1); + d -= Duration::seconds(30); + assert_eq!(d, Duration::seconds(30)); } #[test] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 4882bb69b0..965834ce95 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -590,11 +590,11 @@ impl NaiveTime { if frac >= 1_000_000_000 { let rfrac = 2_000_000_000 - frac; if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs = rhs - OldDuration::nanoseconds(i64::from(rfrac)); + rhs -= OldDuration::nanoseconds(i64::from(rfrac)); secs += 1; frac = 0; } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs = rhs + OldDuration::nanoseconds(i64::from(frac)); + rhs += OldDuration::nanoseconds(i64::from(frac)); frac = 0; } else { frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; From a0d3800412a989599cdabf748628e2371894b04e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:44:43 +0200 Subject: [PATCH 614/999] (Used in `NaiveDate::timestamp*`) Make methods on `NaiveDateTime` const where possible --- src/naive/date.rs | 23 +++++++++++++++ src/naive/datetime/mod.rs | 59 ++++++++++++++++++++------------------- src/naive/time/mod.rs | 15 ++++++++++ 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 379e667a56..69f413edb7 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1437,6 +1437,13 @@ impl NaiveDate { self.ymdf >> 13 } + /// Returns the day of year starting from 1. + // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. + #[inline] + const fn ordinal(&self) -> u32 { + self.of().ordinal() + } + // This duplicates `Datelike::month()`, because trait methods can't be const yet. #[inline] const fn month(&self) -> u32 { @@ -1455,6 +1462,22 @@ impl NaiveDate { self.of().weekday() } + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. + pub(crate) const fn num_days_from_ce(&self) -> i32 { + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e6558de2e4..6be8c88ce5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -126,9 +126,9 @@ impl NaiveDateTime { #[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")] #[inline] #[must_use] - pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { + pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); - datetime.expect("invalid or out-of-range datetime") + expect!(datetime, "invalid or out-of-range datetime") } /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. @@ -157,7 +157,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_millis(millis: i64) -> Option { + pub const fn from_timestamp_millis(millis: i64) -> Option { let secs = millis.div_euclid(1000); let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; NaiveDateTime::from_timestamp_opt(secs, nsecs) @@ -189,7 +189,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_micros(micros: i64) -> Option { + pub const fn from_timestamp_micros(micros: i64) -> Option { let secs = micros.div_euclid(1_000_000); let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; NaiveDateTime::from_timestamp_opt(secs, nsecs) @@ -227,13 +227,14 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { + pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { let days = secs.div_euclid(86_400); let secs = secs.rem_euclid(86_400); - let date = i32::try_from(days) - .ok() - .and_then(|days| days.checked_add(719_163)) - .and_then(NaiveDate::from_num_days_from_ce_opt); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + let date = + NaiveDate::from_num_days_from_ce_opt(try_opt!((days as i32).checked_add(719_163))); let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); match (date, time) { (Some(date), Some(time)) => Some(NaiveDateTime { date, time }), @@ -397,10 +398,10 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp(&self) -> i64 { + pub const fn timestamp(&self) -> i64 { const UNIX_EPOCH_DAY: i64 = 719_163; - let gregorian_day = i64::from(self.date.num_days_from_ce()); - let seconds_from_midnight = i64::from(self.time.num_seconds_from_midnight()); + let gregorian_day = self.date.num_days_from_ce() as i64; + let seconds_from_midnight = self.time.num_seconds_from_midnight() as i64; (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight } @@ -425,9 +426,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_millis(&self) -> i64 { + pub const fn timestamp_millis(&self) -> i64 { let as_ms = self.timestamp() * 1000; - as_ms + i64::from(self.timestamp_subsec_millis()) + as_ms + self.timestamp_subsec_millis() as i64 } /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. @@ -448,9 +449,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_micros(&self) -> i64 { + pub const fn timestamp_micros(&self) -> i64 { let as_us = self.timestamp() * 1_000_000; - as_us + i64::from(self.timestamp_subsec_micros()) + as_us + self.timestamp_subsec_micros() as i64 } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -547,7 +548,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_millis(&self) -> u32 { + pub const fn timestamp_subsec_millis(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000_000 } @@ -569,7 +570,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_micros(&self) -> u32 { + pub const fn timestamp_subsec_micros(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000 } @@ -591,7 +592,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_nanos(&self) -> u32 { + pub const fn timestamp_subsec_nanos(&self) -> u32 { self.time.nanosecond() } @@ -671,7 +672,7 @@ impl NaiveDateTime { return None; } - let date = self.date.checked_add_signed(OldDuration::seconds(rhs))?; + let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -701,8 +702,8 @@ impl NaiveDateTime { /// ); /// ``` #[must_use] - pub fn checked_add_months(self, rhs: Months) -> Option { - Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) + pub const fn checked_add_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_months(rhs)), time: self.time }) } /// Adds given `FixedOffset` to the current datetime. @@ -854,24 +855,24 @@ impl NaiveDateTime { /// ); /// ``` #[must_use] - pub fn checked_sub_months(self, rhs: Months) -> Option { - Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.time }) + pub const fn checked_sub_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_months(rhs)), time: self.time }) } /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. #[must_use] - pub fn checked_add_days(self, days: Days) -> Option { - Some(Self { date: self.date.checked_add_days(days)?, ..self }) + pub const fn checked_add_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_days(days)), ..self }) } /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. #[must_use] - pub fn checked_sub_days(self, days: Days) -> Option { - Some(Self { date: self.date.checked_sub_days(days)?, ..self }) + pub const fn checked_sub_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_days(days)), ..self }) } /// Subtracts another `NaiveDateTime` from the current date and time. @@ -1430,7 +1431,7 @@ impl Timelike for NaiveDateTime { /// The range from 1,000,000,000 to 1,999,999,999 represents /// the [leap second](./struct.NaiveTime.html#leap-second-handling). /// - /// See also the [`NaiveTime::nanosecond`] method. + /// See also the [`NaiveTime#method.nanosecond`] method. /// /// # Example /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 965834ce95..de29fa465e 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -868,6 +868,21 @@ impl NaiveTime { (hour, min, sec) } + /// Returns the number of non-leap seconds past the last midnight. + // This duplicates `Timelike::num_seconds_from_midnight()`, because trait methods can't be const + // yet. + #[inline] + pub(crate) const fn num_seconds_from_midnight(&self) -> u32 { + self.secs + } + + /// Returns the number of nanoseconds since the whole non-leap second. + // This duplicates `Timelike::nanosecond()`, because trait methods can't be const yet. + #[inline] + pub(crate) const fn nanosecond(&self) -> u32 { + self.frac + } + /// The earliest possible `NaiveTime` pub const MIN: Self = Self { secs: 0, frac: 0 }; pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; From fe4713a4f06728096c4a27a31b5de01c11b9e4e6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 13 Sep 2023 07:26:06 +0200 Subject: [PATCH 615/999] Make `NaiveDateTime::and_utc` const --- src/datetime/mod.rs | 8 ++++++++ src/naive/datetime/mod.rs | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 24a344d9ab..b26296823e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -622,6 +622,14 @@ impl DateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) } + // FIXME: remove when our MSRV is 1.61+ + // This method is used by `NaiveDateTime::and_utc` because `DateTime::from_naive_utc_and_offset` + // can't be made const yet. + // Trait bounds in const function / implementation blocks were not supported until 1.61. + pub(crate) const fn from_naive_utc(datetime: NaiveDateTime) -> Self { + DateTime { datetime, offset: Utc } + } + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 6be8c88ce5..88d780a4c1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1030,8 +1030,9 @@ impl NaiveDateTime { /// assert_eq!(dt.timezone(), Utc); /// ``` #[must_use] - pub fn and_utc(&self) -> DateTime { - Utc.from_utc_datetime(self) + pub const fn and_utc(&self) -> DateTime { + // FIXME: use `DateTime::from_naive_utc_and_offset` when our MSRV is 1.61+. + DateTime::from_naive_utc(*self) } /// The minimum possible `NaiveDateTime`. From 0f19d6be1db7b0e2a8528d57654317e1f9c8084f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:43:38 +0200 Subject: [PATCH 616/999] Adjust `Duration::MIN` by 1 millisecond --- src/duration.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index fb64591b80..1e5eb67512 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -61,10 +61,10 @@ pub struct Duration { nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC } -/// The minimum possible `Duration`: `i64::MIN` milliseconds. +/// The minimum possible `Duration`: `-i64::MAX` milliseconds. pub(crate) const MIN: Duration = Duration { - secs: i64::MIN / MILLIS_PER_SEC - 1, - nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, + secs: -i64::MAX / MILLIS_PER_SEC - 1, + nanos: NANOS_PER_SEC + (-i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; /// The maximum possible `Duration`: `i64::MAX` milliseconds. @@ -144,7 +144,7 @@ impl Duration { /// Makes a new `Duration` with given number of seconds. /// Panics when the duration is more than `i64::MAX` milliseconds - /// or less than `i64::MIN` milliseconds. + /// or less than `-i64::MAX` milliseconds. #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { @@ -153,7 +153,7 @@ impl Duration { /// Makes a new `Duration` with given number of seconds. /// Returns `None` when the duration is more than `i64::MAX` milliseconds - /// or less than `i64::MIN` milliseconds. + /// or less than `-i64::MAX` milliseconds. #[inline] pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; @@ -303,7 +303,7 @@ impl Duration { } } - /// The minimum possible `Duration`: `i64::MIN` milliseconds. + /// The minimum possible `Duration`: `-i64::MAX` milliseconds. #[inline] pub const fn min_value() -> Duration { MIN @@ -507,7 +507,7 @@ const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Duration { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { - const MIN_SECS: i64 = i64::MIN / MILLIS_PER_SEC - 1; + const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; @@ -588,9 +588,9 @@ mod tests { assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN); + assert_eq!(Duration::milliseconds(-i64::MAX).num_milliseconds(), -i64::MAX); assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), i64::MIN); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); } #[test] @@ -603,7 +603,7 @@ mod tests { assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); + assert_eq!(Duration::microseconds(-i64::MAX).num_microseconds(), Some(-i64::MAX)); assert_eq!(MAX.num_microseconds(), None); assert_eq!(MIN.num_microseconds(), None); @@ -614,11 +614,11 @@ mod tests { Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!( - Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), - Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY) + Duration::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(), + Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); + assert_eq!(Duration::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); } #[test] @@ -627,7 +627,7 @@ mod tests { assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); + assert_eq!(Duration::nanoseconds(-i64::MAX).num_nanoseconds(), Some(-i64::MAX)); assert_eq!(MAX.num_nanoseconds(), None); assert_eq!(MIN.num_nanoseconds(), None); @@ -638,11 +638,11 @@ mod tests { Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!( - Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY) + Duration::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); + assert_eq!(Duration::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); } #[test] @@ -656,10 +656,12 @@ mod tests { .is_none()); assert_eq!( - Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)), - Some(Duration::milliseconds(i64::MIN)) + Duration::milliseconds(-i64::MAX).checked_sub(&Duration::milliseconds(0)), + Some(Duration::milliseconds(-i64::MAX)) ); - assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none()); + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::milliseconds(1)) + .is_none()); } #[test] @@ -673,6 +675,7 @@ mod tests { assert_eq!(Duration::milliseconds(-1000).abs(), Duration::milliseconds(1000)); assert_eq!(Duration::milliseconds(-1300).abs(), Duration::milliseconds(1300)); assert_eq!(Duration::milliseconds(-1700).abs(), Duration::milliseconds(1700)); + assert_eq!(Duration::milliseconds(-i64::MAX).abs(), Duration::milliseconds(i64::MAX)); } #[test] From 02bdd1ddc8750760c9e2e89aa3c1d2012c743417 Mon Sep 17 00:00:00 2001 From: Mikhail Katychev Date: Tue, 10 Oct 2023 09:25:06 -0500 Subject: [PATCH 617/999] archive derive of PartialEq for rkyv - 0.4.x (#959) --- .github/workflows/test.yml | 2 +- Cargo.toml | 3 ++- src/date.rs | 2 +- src/datetime/mod.rs | 27 +++++++++++++++++++++++++++ src/duration.rs | 10 ++++++++++ src/format/scan.rs | 2 +- src/month.rs | 10 ++++++++++ src/naive/date.rs | 14 ++++++++++++++ src/naive/datetime/mod.rs | 2 ++ src/naive/datetime/tests.rs | 12 ++++++++++++ src/naive/isoweek.rs | 16 ++++++++++++++++ src/naive/time/mod.rs | 2 ++ src/naive/time/tests.rs | 12 ++++++++++++ src/offset/fixed.rs | 15 ++++++++++++++- src/offset/local/mod.rs | 16 +++++++++++++++- src/offset/utc.rs | 7 ++++++- src/weekday.rs | 16 +++++++++++++++- 17 files changed, 160 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1bdd2a4c2..a096504d97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - run: | - cargo hack check --feature-powerset --optional-deps serde,rkyv \ + cargo hack check --feature-powerset --optional-deps serde \ --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ --all-targets # run using `bash` on all platforms for consistent diff --git a/Cargo.toml b/Cargo.toml index 7d82ba3eca..c6edb2a311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] +rkyv-validation = ["rkyv/validation"] __internal_bench = [] [dependencies] @@ -33,7 +34,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.7", optional = true } -rkyv = { version = "0.7", optional = true } +rkyv = { version = "0.7.41", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] diff --git a/src/date.rs b/src/date.rs index e82a3f0eee..8a8511b528 100644 --- a/src/date.rs +++ b/src/date.rs @@ -479,7 +479,7 @@ impl Eq for Date {} impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { - self.date.partial_cmp(&other.date) + Some(self.cmp(other)) } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b26296823e..ac9ef511d0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -51,6 +51,8 @@ mod tests; /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. #[derive(Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive(compare(PartialEq, PartialOrd)))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct DateTime { datetime: NaiveDateTime, offset: Tz::Offset, @@ -1504,6 +1506,31 @@ impl fmt::Debug for DateTime { } } +// `fmt::Debug` is hand implemented for the `rkyv::Archive` variant of `DateTime` because +// deriving a trait recursively does not propagate trait defined associated types with their own +// constraints: +// In our case `<::Offset as Archive>::Archived` +// cannot be formatted using `{:?}` because it doesn't implement `Debug`. +// See below for further discussion: +// * https://github.com/rust-lang/rust/issues/26925 +// * https://github.com/rkyv/rkyv/issues/333 +// * https://github.com/dtolnay/syn/issues/370 +#[cfg(feature = "rkyv-validation")] +impl fmt::Debug for ArchivedDateTime +where + Tz: Archive, + ::Archived: fmt::Debug, + <::Offset as Archive>::Archived: fmt::Debug, + ::Offset: fmt::Debug + Archive, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ArchivedDateTime") + .field("datetime", &self.datetime) + .field("offset", &self.offset) + .finish() + } +} + impl fmt::Display for DateTime where Tz::Offset: fmt::Display, diff --git a/src/duration.rs b/src/duration.rs index 1e5eb67512..f00b03663e 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -54,8 +54,10 @@ macro_rules! try_opt { #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct Duration { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC @@ -795,4 +797,12 @@ mod tests { Err(OutOfRangeError(())) ); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let duration = Duration::seconds(1); + let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); + } } diff --git a/src/format/scan.rs b/src/format/scan.rs index 98f3673ff1..45b5bcbec0 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -274,7 +274,7 @@ where }; s = match s.len() { len if len >= 2 => &s[2..], - len if len == 0 => s, + 0 => s, _ => return Err(TOO_SHORT), }; diff --git a/src/month.rs b/src/month.rs index a409bb155d..26597bd4b0 100644 --- a/src/month.rs +++ b/src/month.rs @@ -33,8 +33,10 @@ use crate::OutOfRange; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { /// January @@ -417,4 +419,12 @@ mod tests { from_str::(string).unwrap_err(); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let month = Month::January; + let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), month); + } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 69f413edb7..11952e2766 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -192,8 +192,10 @@ impl Days { #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { ymdf: DateImpl, // (year << 13) | of } @@ -3324,6 +3326,18 @@ mod tests { } } + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let date_min = NaiveDate::MIN; + let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); + + let date_max = NaiveDate::MAX; + let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); + } + // MAX_YEAR-12-31 minus 0000-01-01 // = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) // = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 88d780a4c1..2f9b2db6d1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -77,8 +77,10 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 97604db74f..0dbbe7bc5e 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -536,3 +536,15 @@ fn test_and_timezone_min_max_dates() { } } } + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let dt_min = NaiveDateTime::MIN; + let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_min); + + let dt_max = NaiveDateTime::MAX; + let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_max); +} diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 606699659b..50ffcdc169 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -20,8 +20,10 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, @@ -151,6 +153,8 @@ impl fmt::Debug for IsoWeek { #[cfg(test)] mod tests { + #[cfg(feature = "rkyv-validation")] + use super::IsoWeek; use crate::naive::{internals, NaiveDate}; use crate::Datelike; @@ -205,4 +209,16 @@ mod tests { assert!(monday.iso_week() >= friday.iso_week()); assert!(monday.iso_week() <= friday.iso_week()); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let minweek = NaiveDate::MIN.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), minweek); + + let maxweek = NaiveDate::MAX.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), maxweek); + } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index de29fa465e..9a2e5d8f76 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -206,8 +206,10 @@ mod tests; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveTime { secs: u32, frac: u32, diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 30c1af9537..784a8390b8 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -376,3 +376,15 @@ fn test_overflowing_offset() { assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset); assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset); } + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let t_min = NaiveTime::MIN; + let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_min); + + let t_max = NaiveTime::MAX; + let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_max); +} diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 6d3aebdb68..1826ec4ec2 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -21,7 +21,12 @@ use crate::naive::{NaiveDate, NaiveDateTime}; /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct FixedOffset { local_minus_utc: i32, } @@ -222,4 +227,12 @@ mod tests { let offset = FixedOffset::from_str("+06:30").unwrap(); assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let offset = FixedOffset::from_str("-0500").unwrap(); + let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), offset); + } } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index e4f4e1631f..af1c21e7ef 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -105,7 +105,8 @@ mod tz_info; /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr(feature = "rkyv", archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; @@ -258,4 +259,17 @@ mod tests { ); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let local = Local; + // Local is a ZST and serializes to 0 bytes + let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap(); + assert_eq!(bytes.len(), 0); + + // but is deserialized to an archived variant without a + // wrapping object + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), super::ArchivedLocal); + } } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 3b0a85d56a..11e9dad931 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -42,7 +42,12 @@ use crate::{Date, DateTime}; /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; diff --git a/src/weekday.rs b/src/weekday.rs index fb88c3b929..786d516c7b 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -32,7 +32,12 @@ use crate::OutOfRange; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. @@ -381,4 +386,13 @@ mod tests { from_str::(str).unwrap_err(); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let mon = Weekday::Mon; + let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); + + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), mon); + } } From 15c8885365c696cf7a43cd9ead507974a2e92f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 04:27:03 +0000 Subject: [PATCH 618/999] Bump actions/setup-node from 3 to 4 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a096504d97..8deb2a8533 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,7 +135,7 @@ jobs: with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 - uses: jetli/wasm-pack-action@v0.4.0 # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment # with the host system. From fbd67ab02cf4b41b476e8e43bfe501c2b81dc35e Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:29:25 -0800 Subject: [PATCH 619/999] test.yml remove errant `with: node-version` --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8deb2a8533..c6c3ec1261 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -120,8 +120,6 @@ jobs: with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - with: - node-version: "12" - run: cargo build --target ${{ matrix.target }} --color=always test_wasm: From dd3f2f81c4cf6f4d85723f122d7e637bd4bd674d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 30 Oct 2023 09:30:11 +0100 Subject: [PATCH 620/999] Bump MSRV to 1.61 --- .github/workflows/test.yml | 4 ++-- Cargo.toml | 2 +- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6c3ec1261..67e6e8c696 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - run: cargo test --all-features --color=always -- --color=always # later this may be able to be included with the below - # kept separate for now as the following don't compile on 1.57 + # kept separate for now as the following don't compile on 1.60 # * arbitrary (requires 1.63 as of v1.3.0) rust_msrv: strategy: @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.57 + toolchain: "1.61.0" - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests # which are run elsewhere diff --git a/Cargo.toml b/Cargo.toml index c6edb2a311..d960da1f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2021" -rust-version = "1.57.0" +rust-version = "1.61.0" [lib] name = "chrono" diff --git a/README.md b/README.md index c3222ddaa3..f605b11f22 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Optional features: ## Rust version requirements -The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done lightly. diff --git a/src/lib.rs b/src/lib.rs index da8e00d17a..64d2135e6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -359,7 +359,7 @@ //! //! ## Rust version requirements //! -//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. +//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. //! //! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done //! lightly. From c0f418bbbfa83b1c8392099cec9fdf42657c1e51 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sat, 28 Oct 2023 10:42:33 -0600 Subject: [PATCH 621/999] Split clock feature into clock and now --- .github/workflows/test.yml | 1 + Cargo.toml | 3 ++- README.md | 3 ++- src/lib.rs | 17 +++++++++++++---- src/offset/utc.rs | 6 +++--- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67e6e8c696..19232e0e8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,7 @@ jobs: - run: cargo test --no-default-features --features=alloc - run: cargo test --no-default-features --features=unstable-locales - run: cargo test --no-default-features --features=alloc,unstable-locales + - run: cargo test --no-default-features --features=now no_std: strategy: diff --git a/Cargo.toml b/Cargo.toml index d960da1f4e..1cad234834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ alloc = [] libc = [] winapi = ["windows-targets"] std = ["alloc"] -clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] +clock = ["winapi", "iana-time-zone", "android-tzdata", "now"] +now = ["std"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] diff --git a/README.md b/README.md index f605b11f22..c75a50096f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ Default features: * `alloc`: Enable features that depend on allocation (primarily string formatting) * `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` and adds interoperation with standard library types and traits. -* `clock`: Enables reading the system time (`now`) and local timezone (`Local`). +* `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +* `now`: Enables reading the system time (`now`) * `wasmbind`: Interface with the JS Date API for the `wasm32` target. Optional features: diff --git a/src/lib.rs b/src/lib.rs index 64d2135e6c..086dbe450c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,21 +108,28 @@ //! or in the local time zone //! ([`Local::now()`](./offset/struct.Local.html#method.now)). //! +#![cfg_attr(not(feature = "now"), doc = "```ignore")] +#![cfg_attr(feature = "now", doc = "```rust")] +//! use chrono::prelude::*; +//! +//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` +//! # let _ = utc; +//! ``` +//! #![cfg_attr(not(feature = "clock"), doc = "```ignore")] #![cfg_attr(feature = "clock", doc = "```rust")] //! use chrono::prelude::*; //! -//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -//! # let _ = utc; let _ = local; +//! # let _ = local; //! ``` //! //! Alternatively, you can create your own date and time. //! This is a bit verbose due to Rust's lack of function and method overloading, //! but in turn we get a rich combination of initialization methods. //! -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -#![cfg_attr(feature = "std", doc = "```rust")] +#![cfg_attr(not(feature = "now"), doc = "```ignore")] +#![cfg_attr(feature = "now", doc = "```rust")] //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! @@ -146,12 +153,14 @@ //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); //! +//! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. //! let local_dt = Local.from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap()).unwrap(); //! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; +//! # } //! # Some(()) //! # } //! # doctest().unwrap(); diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 11e9dad931..7c23a282bb 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -5,7 +5,7 @@ use core::fmt; #[cfg(all( - feature = "clock", + feature = "now", not(all( target_arch = "wasm32", feature = "wasmbind", @@ -19,7 +19,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] #[allow(deprecated)] use crate::{Date, DateTime}; @@ -51,7 +51,7 @@ use crate::{Date, DateTime}; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] impl Utc { /// Returns a `Date` which corresponds to the current date. #[deprecated( From 039189c4a1360db117898bf3d2bbddce3bc1d4a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 04:17:46 +0000 Subject: [PATCH 622/999] Update windows-targets requirement from 0.48 to 0.52 Updates the requirements on [windows-targets](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/windows-targets-0.48.1...0.52.0) --- updated-dependencies: - dependency-name: windows-targets dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1cad234834..e056373386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for [target.'cfg(windows)'.dependencies] -windows-targets = { version = "0.48", optional = true } +windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] windows-bindgen = { version = "0.51" } From 6f3ab9dd5a5120871ccb524fb6d0eb697645eab1 Mon Sep 17 00:00:00 2001 From: dcechano Date: Sun, 12 Nov 2023 16:23:51 -0500 Subject: [PATCH 623/999] Add From for NaiveDateTime --- src/naive/datetime/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 2f9b2db6d1..51b9988a75 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1048,6 +1048,22 @@ impl NaiveDateTime { expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); } +impl From for NaiveDateTime { + /// Converts a `NaiveDate` to a `NaiveDateTime` of the same date but at midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let nd = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap(); + /// let ndt = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// assert_eq!(ndt, NaiveDateTime::from(nd)); + fn from(date: NaiveDate) -> Self { + date.and_hms_opt(0, 0, 0).unwrap() + } +} + impl Datelike for NaiveDateTime { /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). /// From e1a9494e24b24d4c754aa889a4be6f386ad37d16 Mon Sep 17 00:00:00 2001 From: Ali Mirghasemi Date: Fri, 24 Nov 2023 12:08:25 +0330 Subject: [PATCH 624/999] Add from_timestamp_nanos (#1357) * Add from_timestamp_nanos Add and implement from_timestamp_nanos and add unit test for it * Fix lint check error * Add pub(crate) for NANOS_PER_SEC * Replace number with constant variable --- src/duration.rs | 2 +- src/naive/datetime/mod.rs | 35 ++++++++++++++++++++++++- src/naive/datetime/tests.rs | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index f00b03663e..63fa66fd49 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -24,7 +24,7 @@ const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. const NANOS_PER_MILLI: i32 = 1_000_000; /// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; +pub(crate) const NANOS_PER_SEC: i32 = 1_000_000_000; /// The number of microseconds per second. const MICROS_PER_SEC: i64 = 1_000_000; /// The number of milliseconds per second. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 51b9988a75..764a1743a8 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,7 +13,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::Duration as OldDuration; +use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -197,6 +197,39 @@ impl NaiveDateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs) } + /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDateTime; + /// let timestamp_nanos: i64 = 1662921288_000_000_000; //Sunday, September 11, 2022 6:34:48 PM + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Option { + let secs = nanos.div_euclid(NANOS_PER_SEC as i64); + let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; + + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, /// from the number of non-leap seconds /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 0dbbe7bc5e..6375a9d351 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -78,6 +78,57 @@ fn test_datetime_from_timestamp_micros() { } } +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos).unwrap(); + assert_eq!(timestamp_nanos, naive_datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(naive_datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000"; + let parsed: NaiveDateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Test that the result of `from_timestamp_nanos` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + NaiveDateTime::from_timestamp_nanos(secs * 1_000_000_000), + NaiveDateTime::from_timestamp_opt(secs, 0) + ); + } +} + #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); From d7b4a82027471896caa175b07918fe883a5bef58 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 20 Nov 2023 09:26:05 +0100 Subject: [PATCH 625/999] Silence clippy lint for test code --- src/datetime/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 42b014983d..3df11e78d4 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -412,6 +412,7 @@ fn signed_duration_since_autoref() { let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap(); let diff1 = dt1.signed_duration_since(dt2); // Copy/consume + #[allow(clippy::needless_borrows_for_generic_args)] let diff2 = dt2.signed_duration_since(&dt1); // Take by reference assert_eq!(diff1, -diff2); From 3f2282208674f01fc3cb2fde4ada38eaf91bcdc5 Mon Sep 17 00:00:00 2001 From: Gerd Zellweger Date: Fri, 1 Dec 2023 11:08:21 -0800 Subject: [PATCH 626/999] Expose rkyv features as features for chrono users. rkyv by default serializes usize as u32. This isn't ideal on most modern platforms and unfortunately is configured through a feature flag. If we just set `default-features = false` in the rkyv Cargo dependency, the crate fails to compile because all the size features are mutually exclusive. On the other hand if we want to e.g., change the serialization of usize to 64-bit and we also want to use chrono this currently fails to compile because chrono always enables rkyv/size_32. This re-exports the relevant rkyv features so users can choose which serialization to enable. The approach is similar to what the ordered-float crate does: https://github.com/reem/rust-ordered-float/blob/8111b345372632893af0b8aa12152f3dc7278aba/Cargo.toml#L37 Signed-off-by: Gerd Zellweger --- Cargo.toml | 10 ++++++++-- src/month.rs | 2 +- src/naive/isoweek.rs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e056373386..a56c0c000f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.61.0" name = "chrono" [features] +# Don't forget to adjust `ALL_NON_EXCLUSIVE_FEATURES` in CI scripts when adding a feature or an optional dependency. default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] @@ -27,7 +28,12 @@ now = ["std"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] -rkyv-validation = ["rkyv/validation"] +# Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive. +rkyv-16 = ["rkyv", "rkyv?/size_16"] +rkyv-32 = ["rkyv", "rkyv?/size_32"] +rkyv-64 = ["rkyv", "rkyv?/size_64"] +rkyv-validation = ["rkyv?/validation"] +# Features for internal use only: __internal_bench = [] [dependencies] @@ -35,7 +41,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.7", optional = true } -rkyv = { version = "0.7.41", optional = true } +rkyv = { version = "0.7.43", optional = true, default-features = false } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] diff --git a/src/month.rs b/src/month.rs index 26597bd4b0..2f18f9a10b 100644 --- a/src/month.rs +++ b/src/month.rs @@ -33,7 +33,7 @@ use crate::OutOfRange; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", - archive(compare(PartialEq)), + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 50ffcdc169..8ef982701e 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -20,7 +20,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", - archive(compare(PartialEq)), + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] From 849932b09bdc58c508c0b6794fa474a9558d27ea Mon Sep 17 00:00:00 2001 From: Gerd Zellweger Date: Thu, 14 Dec 2023 14:26:01 -0800 Subject: [PATCH 627/999] Change the CI `--all-features` to an explicit list. This is due to the mutually exclusive features in rkyv which we expose now. `--all-features` will now activate them and the crate will fail to compile rkyv. We work around this by defining an explicit list of all mutually exclusive features to. Unfortunately there isn't an easy way to share env variables among different YAML files (https://github.com/actions/runner/issues/655). There also isn't a good way to specify `--all-features` minus "just a few" (https://github.com/rust-lang/cargo/issues/3126) aside from giving the complete list. Signed-off-by: Gerd Zellweger --- .github/workflows/codecov.yml | 7 ++++++- .github/workflows/lint.yml | 10 +++++++--- .github/workflows/test.yml | 22 +++++++++++++--------- Cargo.toml | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 88634264cf..a819b21a5b 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -1,4 +1,9 @@ name: codecov + +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -18,7 +23,7 @@ jobs: - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo +nightly llvm-cov --all-features --workspace --lcov --doctests --output-path lcov.info + run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87c9f553c4..b954a7ec2c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,9 @@ name: lint +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -19,7 +23,7 @@ jobs: - run: cargo fmt --check --manifest-path fuzz/Cargo.toml - run: cargo fmt --check --manifest-path bench/Cargo.toml - run: | - cargo clippy --all-features --all-targets --color=always \ + cargo clippy ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --all-targets --color=always \ -- -D warnings - run: | cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ @@ -50,8 +54,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - - run: cargo deadlinks -- --all-features - - run: cargo doc --all-features --no-deps + - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} + - run: cargo doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: RUSTDOCFLAGS: -Dwarnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19232e0e8f..34aae224bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,9 @@ name: All Tests and Builds +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-32 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -16,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - run: cargo test --all-features --color=always -- --color=always + - run: cargo test ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always # later this may be able to be included with the below # kept separate for now as the following don't compile on 1.60 @@ -61,8 +65,8 @@ jobs: - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests # which are run elsewhere - - run: cargo test --lib --all-features --color=always -- --color=always - - run: cargo test --doc --all-features --color=always -- --color=always + - run: cargo test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always + - run: cargo test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always features_check: strategy: @@ -76,7 +80,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps serde \ - --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ + --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi,rkyv-16,rkyv-64,rkyv-validation \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks @@ -182,16 +186,16 @@ jobs: - uses: actions/checkout@v4 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - - run: cross test --lib --all-features --target i686-unknown-linux-gnu --color=always - - run: cross test --doc --all-features --target i686-unknown-linux-gnu --color=always - - run: cross test --lib --all-features --target i686-unknown-linux-musl --color=always - - run: cross test --doc --all-features --target i686-unknown-linux-musl --color=always + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always check-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: cargo +nightly doc --all-features --no-deps + - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: RUSTDOCFLAGS: "-D warnings --cfg docsrs" diff --git a/Cargo.toml b/Cargo.toml index a56c0c000f..5fbc14cccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ unstable-locales = ["pure-rust-locales"] rkyv-16 = ["rkyv", "rkyv?/size_16"] rkyv-32 = ["rkyv", "rkyv?/size_32"] rkyv-64 = ["rkyv", "rkyv?/size_64"] -rkyv-validation = ["rkyv?/validation"] +rkyv-validation = ["rkyv", "rkyv?/validation"] # Features for internal use only: __internal_bench = [] From 6033afea6f6e986f8da8b58c1b363894ad2542a7 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Thu, 26 Oct 2023 00:42:49 -0600 Subject: [PATCH 628/999] docs: Improve deprecation of `TimeZone::datetime_from_str` Add recommending using `NaiveDate::parse_from_str` as well --- src/offset/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 99b64b2760..ae3634e549 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -469,7 +469,14 @@ pub trait TimeZone: Sized + Clone { /// /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. - #[deprecated(since = "0.4.29", note = "use `DateTime::parse_from_str` instead")] + /// + /// See also [`NaiveDateTime::parse_from_str`] which gives a [`NaiveDateTime`] without + /// an offset, but can be converted to a [`DateTime`] with [`NaiveDateTime::and_utc`] or + /// [`NaiveDateTime::and_local_timezone`]. + #[deprecated( + since = "0.4.29", + note = "use `DateTime::parse_from_str` or `NaiveDateTime::parse_from_str` with `and_utc()` or `and_local_timezone()` instead" + )] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; From 5536687c0d4dfae66130cc44c04e08d9f2797708 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 9 Jan 2024 12:24:41 +0000 Subject: [PATCH 629/999] Add Months::as_u32() (#1373) --- src/month.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/month.rs b/src/month.rs index 2f18f9a10b..1e1531d716 100644 --- a/src/month.rs +++ b/src/month.rs @@ -233,6 +233,12 @@ impl Months { pub const fn new(num: u32) -> Self { Self(num) } + + /// Returns the total number of months in the `Months` instance. + #[inline] + pub const fn as_u32(&self) -> u32 { + self.0 + } } /// An error resulting from reading `` value with `FromStr`. @@ -302,7 +308,7 @@ mod month_serde { #[cfg(test)] mod tests { use super::Month; - use crate::{Datelike, OutOfRange, TimeZone, Utc}; + use crate::{Datelike, Months, OutOfRange, TimeZone, Utc}; #[test] fn test_month_enum_try_from() { @@ -357,6 +363,13 @@ mod tests { assert!(Month::September > Month::March); } + #[test] + fn test_months_as_u32() { + assert_eq!(Months::new(0).as_u32(), 0); + assert_eq!(Months::new(1).as_u32(), 1); + assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX); + } + #[test] #[cfg(feature = "serde")] fn test_serde_serialize() { From 65f0cc2aa41800a93878dfbeb816b182cc027b1d Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 30 Dec 2023 19:54:40 +0100 Subject: [PATCH 630/999] CI Linting: Fix missing sources checkout in `toml` job. --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b954a7ec2c..18f65d78e2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,6 +39,7 @@ jobs: container: image: tamasfe/taplo:0.8.0 steps: + - uses: actions/checkout@v4 - run: taplo lint - run: taplo fmt --check --diff From 6ec8f97d16ce320a6f948b1cd1494ed3a21b251f Mon Sep 17 00:00:00 2001 From: Makro <4398091+xmakro@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:50:58 +0800 Subject: [PATCH 631/999] Add from_timestamp_millis to DateTime (#1374) Co-authored-by: xmakro --- src/datetime/mod.rs | 28 ++++++++++++++++++++++++++++ src/datetime/tests.rs | 12 ++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ac9ef511d0..9d4e1c3861 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -624,6 +624,34 @@ impl DateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) } + /// Makes a new [`DateTime`] from the number of non-leap milliseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with regard to [`timestamp_millis`](DateTime::timestamp_millis). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of milliseconds, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::{DateTime, Utc}; + /// + /// let dt: DateTime = DateTime::::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); + /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub fn from_timestamp_millis(millis: i64) -> Option { + NaiveDateTime::from_timestamp_millis(millis).as_ref().map(NaiveDateTime::and_utc) + } + // FIXME: remove when our MSRV is 1.61+ // This method is used by `NaiveDateTime::and_utc` because `DateTime::from_naive_utc_and_offset` // can't be made const yet. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3df11e78d4..3e96e227fa 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1253,6 +1253,18 @@ fn test_datetime_from_local() { assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); } +#[test] +fn test_datetime_from_timestamp_millis() { + // 2000-01-12T01:02:03:004Z + let naive_dt = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_milli_opt(1, 2, 3, 4).unwrap(); + let datetime_utc = DateTime::::from_naive_utc_and_offset(naive_dt, Utc); + assert_eq!( + datetime_utc, + DateTime::::from_timestamp_millis(datetime_utc.timestamp_millis()).unwrap() + ); +} + #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { From cee242a6565bffcc2a90c8566136c3b96491a821 Mon Sep 17 00:00:00 2001 From: Tam Pham Date: Thu, 18 Jan 2024 11:14:31 -0600 Subject: [PATCH 632/999] Fix typos in Datelike impl for DateTime --- src/datetime/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 9d4e1c3861..6b3e49f5f4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1045,7 +1045,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_month0(month0)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of month (starting from 1) changed. /// /// See also the [`NaiveDate::with_day`] method. /// @@ -1061,7 +1061,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_day(day)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of month (starting from 0) changed. /// /// See also the [`NaiveDate::with_day0`] method. /// @@ -1077,7 +1077,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_day0(day0)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of year (starting from 1) changed. /// /// See also the [`NaiveDate::with_ordinal`] method. /// @@ -1093,7 +1093,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_ordinal(ordinal)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of year (starting from 0) changed. /// /// See also the [`NaiveDate::with_ordinal0`] method. /// From 7757386368913e8df9a8505f9ef37c5868045a88 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 22 Jan 2024 17:07:25 +0100 Subject: [PATCH 633/999] Prepare 0.4.32 release --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 877b1b48b4..7546aff126 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-09-15 -version: 0.4.31 +date-released: 2024-01-22 +version: 0.4.32 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 5fbc14cccc..0d60e706f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.31" +version = "0.4.32" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From bf704191f2914a2db43abf6827a73cbe05864d57 Mon Sep 17 00:00:00 2001 From: Kenny Kerr Date: Fri, 17 Nov 2023 09:39:30 -0600 Subject: [PATCH 634/999] 52 --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d60e706f0..6211d9f835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.51" } +windows-bindgen = { version = "0.52" } [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 292c951414..592726c732 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,4 +1,4 @@ -// Bindings generated by `windows-bindgen` 0.51.1 +// Bindings generated by `windows-bindgen` 0.52.0 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] ::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); From b5381f8fb5c2ba32260288dc2fc43a711ed2549d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 25 Jan 2024 06:29:21 +0000 Subject: [PATCH 635/999] Fixed typo in Duration::hours() exception --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 63fa66fd49..c4e9ee2d62 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -116,7 +116,7 @@ impl Duration { #[inline] #[must_use] pub fn hours(hours: i64) -> Duration { - Duration::try_hours(hours).expect("Duration::hours ouf of bounds") + Duration::try_hours(hours).expect("Duration::hours out of bounds") } /// Makes a new `Duration` with given number of hours. From a73b54320ac24a770b2e451e60e84eb52a6acbf9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 17:08:37 +0100 Subject: [PATCH 636/999] Don't assume `rkyv-(16|32|64)` implies the `rkyv` feature --- src/datetime/mod.rs | 9 ++++++--- src/duration.rs | 6 +++--- src/lib.rs | 2 +- src/month.rs | 6 +++--- src/naive/date.rs | 6 +++--- src/naive/datetime/mod.rs | 6 +++--- src/naive/isoweek.rs | 6 +++--- src/naive/time/mod.rs | 6 +++--- src/offset/fixed.rs | 6 +++--- src/offset/local/mod.rs | 10 +++++++--- src/offset/utc.rs | 6 +++--- src/weekday.rs | 6 +++--- 12 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 6b3e49f5f4..e12b2db762 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -31,7 +31,7 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::Date; use crate::{Datelike, Months, Timelike, Weekday}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] @@ -50,8 +50,11 @@ mod tests; /// the general-purpose constructors are all via the methods on the /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. #[derive(Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive(compare(PartialEq, PartialOrd)))] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)) +)] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct DateTime { datetime: NaiveDateTime, diff --git a/src/duration.rs b/src/duration.rs index c4e9ee2d62..e4498b9f72 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,7 +16,7 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// The number of nanoseconds in a microsecond. @@ -51,9 +51,9 @@ macro_rules! try_opt { /// /// This also allows for the negative duration; see individual methods for details. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/lib.rs b/src/lib.rs index 086dbe450c..e532d7479f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -578,7 +578,7 @@ pub mod serde { /// Zero-copy serialization/deserialization with rkyv. /// /// This module re-exports the `Archived*` versions of chrono's types. -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] pub mod rkyv { pub use crate::datetime::ArchivedDateTime; pub use crate::duration::ArchivedDuration; diff --git a/src/month.rs b/src/month.rs index 1e1531d716..c3c3579d22 100644 --- a/src/month.rs +++ b/src/month.rs @@ -1,6 +1,6 @@ use core::fmt; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::OutOfRange; @@ -30,9 +30,9 @@ use crate::OutOfRange; // Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/date.rs b/src/naive/date.rs index 11952e2766..c8acb9efc6 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -9,7 +9,7 @@ use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// L10n locales. @@ -189,9 +189,9 @@ impl Days { /// /// [proleptic Gregorian date]: crate::NaiveDate#calendar-date #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 764a1743a8..ece1c80092 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -10,7 +10,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; @@ -74,9 +74,9 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// assert_eq!(dt.num_seconds_from_midnight(), 33011); /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 8ef982701e..4b3d8d9d85 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -7,7 +7,7 @@ use core::fmt; use super::internals::{DateImpl, Of, YearFlags}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 week. @@ -17,9 +17,9 @@ use rkyv::{Archive, Deserialize, Serialize}; /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 9a2e5d8f76..94aee99168 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -9,7 +9,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; @@ -203,9 +203,9 @@ mod tests; /// Since Chrono alone cannot determine any existence of leap seconds, /// **there is absolutely no guarantee that the leap second read has actually happened**. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 1826ec4ec2..387a5cc0ab 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -6,7 +6,7 @@ use core::fmt; use core::str::FromStr; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; @@ -20,9 +20,9 @@ use crate::naive::{NaiveDate, NaiveDateTime}; /// `DateTime` instances. See the [`east_opt`](#method.east_opt) and /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) )] diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index af1c21e7ef..1767763738 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -3,7 +3,7 @@ //! The local (system) time zone. -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; @@ -104,8 +104,12 @@ mod tz_info; /// assert!(dt1 >= dt2); /// ``` #[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, Debug)) +)] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 7c23a282bb..e68dc227f3 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -14,7 +14,7 @@ use core::fmt; ))] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; @@ -41,9 +41,9 @@ use crate::{Date, DateTime}; /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] diff --git a/src/weekday.rs b/src/weekday.rs index 786d516c7b..03a4b5a7fd 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -1,6 +1,6 @@ use core::fmt; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::OutOfRange; @@ -31,9 +31,9 @@ use crate::OutOfRange; /// ``` #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] From a9b37c4c81de09b5b420b617ee6394f30ce491b1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 16:16:10 +0100 Subject: [PATCH 637/999] Make `rkyv` feature default to `size_32` --- Cargo.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6211d9f835..cf434b6a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,11 @@ oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] # Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive. -rkyv-16 = ["rkyv", "rkyv?/size_16"] -rkyv-32 = ["rkyv", "rkyv?/size_32"] -rkyv-64 = ["rkyv", "rkyv?/size_64"] -rkyv-validation = ["rkyv", "rkyv?/validation"] +rkyv = ["dep:rkyv", "rkyv/size_32"] +rkyv-16 = ["dep:rkyv", "rkyv?/size_16"] +rkyv-32 = ["dep:rkyv", "rkyv?/size_32"] +rkyv-64 = ["dep:rkyv", "rkyv?/size_64"] +rkyv-validation = ["rkyv?/validation"] # Features for internal use only: __internal_bench = [] From 7c419a358e8978fa774e649c300196095365eb58 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 21:44:10 +0100 Subject: [PATCH 638/999] Prepare 0.4.33 release --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7546aff126..8ee34c2148 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-01-22 -version: 0.4.32 +date-released: 2024-01-25 +version: 0.4.33 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index cf434b6a86..8c8d8304c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.32" +version = "0.4.33" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 94f9738b03a18c05226526da70191997a532b613 Mon Sep 17 00:00:00 2001 From: Eden Mikitas Date: Wed, 4 Oct 2023 09:54:21 +0300 Subject: [PATCH 639/999] Fix the formatting of `timestamp_micros`'s Example doc --- src/offset/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index ae3634e549..02ad1c7017 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -445,7 +445,8 @@ pub trait TimeZone: Sized + Clone { /// Makes a new `DateTime` from the number of non-leap microseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// #Example + /// # Example + /// /// ``` /// use chrono::{Utc, TimeZone}; /// From 5cbb9a7d9c9841bf79bd59315323b76f5e79ed21 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 26 Jan 2024 13:58:23 +0100 Subject: [PATCH 640/999] Update branch names in pull request template --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 950451f3e0..55d575a99a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ### Thanks for contributing to chrono! -If your feature is semver-compatible, please target the 0.4.x branch; -the main branch will be used for 0.5.0 development going forward. +If your feature is semver-compatible, please target the main branch; +for semver-incompatible changes, please target the `0.5.x` branch. Please consider adding a test to ensure your bug fix/feature will not break in the future. From ec557d28436f13456610fc6ebf2a4249e477f82f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 Jan 2024 11:13:50 +0100 Subject: [PATCH 641/999] Specify branch for GitHub Actions badge and fix link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c75a50096f..0558d88343 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [![Chat][discord-image]][discord] [![codecov.io][codecov-img]][codecov-link] -[gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg -[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest +[gh-image]: https://github.com/chronotope/chrono/actions/workflows/test.yml/badge.svg?branch=main +[gh-checks]: https://github.com/chronotope/chrono/actions/workflows/test.yml?query=branch%3Amain [cratesio-image]: https://img.shields.io/crates/v/chrono.svg [cratesio]: https://crates.io/crates/chrono [docsrs-image]: https://docs.rs/chrono/badge.svg From 76822ae05152e86c3a2d0e4ca99dd357f46a3a6e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 16:57:48 +0200 Subject: [PATCH 642/999] Don't use deprecated method in `impl Arbitrary for DateTime` --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e12b2db762..9caf1fc0b4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1705,7 +1705,7 @@ where fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { let datetime = NaiveDateTime::arbitrary(u)?; let offset = ::Offset::arbitrary(u)?; - Ok(DateTime::from_utc(datetime, offset)) + Ok(DateTime::from_naive_utc_and_offset(datetime, offset)) } } From f3e56c505d01aaa067c75c994e8f5391fd35e388 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 15:57:10 +0200 Subject: [PATCH 643/999] Arbitrary does not (yet) support `no_std` --- src/date.rs | 2 +- src/datetime/mod.rs | 2 +- src/duration.rs | 2 +- src/month.rs | 4 ++-- src/naive/date.rs | 2 +- src/naive/datetime/mod.rs | 2 +- src/offset/fixed.rs | 2 +- src/offset/utc.rs | 2 +- src/weekday.rs | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/date.rs b/src/date.rs index 8a8511b528..be2f05c559 100644 --- a/src/date.rs +++ b/src/date.rs @@ -555,7 +555,7 @@ where // Note that implementation of Arbitrary cannot be automatically derived for Date, due to // the nontrivial bound ::Offset: Arbitrary. -#[cfg(feature = "arbitrary")] +#[cfg(all(feature = "arbitrary", feature = "std"))] impl<'a, Tz> arbitrary::Arbitrary<'a> for Date where Tz: TimeZone, diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 9caf1fc0b4..63554a0fcd 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1696,7 +1696,7 @@ impl From> for js_sys::Date { // Note that implementation of Arbitrary cannot be simply derived for DateTime, due to // the nontrivial bound ::Offset: Arbitrary. -#[cfg(feature = "arbitrary")] +#[cfg(all(feature = "arbitrary", feature = "std"))] impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime where Tz: TimeZone, diff --git a/src/duration.rs b/src/duration.rs index e4498b9f72..6a969b3dfc 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -506,7 +506,7 @@ const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { (this.div_euclid(other), this.rem_euclid(other)) } -#[cfg(feature = "arbitrary")] +#[cfg(all(feature = "arbitrary", feature = "std"))] impl arbitrary::Arbitrary<'_> for Duration { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; diff --git a/src/month.rs b/src/month.rs index c3c3579d22..4353dda042 100644 --- a/src/month.rs +++ b/src/month.rs @@ -37,7 +37,7 @@ use crate::OutOfRange; archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub enum Month { /// January January = 0, @@ -225,7 +225,7 @@ impl num_traits::FromPrimitive for Month { /// A duration in calendar months #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub struct Months(pub(crate) u32); impl Months { diff --git a/src/naive/date.rs b/src/naive/date.rs index c8acb9efc6..ed31729c56 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -207,7 +207,7 @@ pub const MIN_DATE: NaiveDate = NaiveDate::MIN; #[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] pub const MAX_DATE: NaiveDate = NaiveDate::MAX; -#[cfg(feature = "arbitrary")] +#[cfg(all(feature = "arbitrary", feature = "std"))] impl arbitrary::Arbitrary<'_> for NaiveDate { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index ece1c80092..e3daaa65f1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -81,7 +81,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, time: NaiveTime, diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 387a5cc0ab..8f37558988 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -176,7 +176,7 @@ impl fmt::Display for FixedOffset { } } -#[cfg(feature = "arbitrary")] +#[cfg(all(feature = "arbitrary", feature = "std"))] impl arbitrary::Arbitrary<'_> for FixedOffset { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { let secs = u.int_in_range(-86_399..=86_399)?; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index e68dc227f3..29b832a1fa 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -48,7 +48,7 @@ use crate::{Date, DateTime}; archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub struct Utc; #[cfg(feature = "now")] diff --git a/src/weekday.rs b/src/weekday.rs index 03a4b5a7fd..618d5355d6 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -38,7 +38,7 @@ use crate::OutOfRange; archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. Mon = 0, From baa55d084784e4e88b5332efe8e96af794a52e8a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:55:19 +0200 Subject: [PATCH 644/999] CI: test `arbitrary` feature --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34aae224bb..18ae42cd48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - run: | - cargo hack check --feature-powerset --optional-deps serde \ + cargo hack check --feature-powerset --optional-deps arbitrary,serde \ --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi,rkyv-16,rkyv-64,rkyv-validation \ --all-targets # run using `bash` on all platforms for consistent From 02a96eba577ff1c0fa18359c5a327002732aaf67 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 Jan 2024 19:44:41 +0100 Subject: [PATCH 645/999] Remove workaround for Rust < 1.61 --- src/datetime/mod.rs | 13 ++++--------- src/naive/datetime/mod.rs | 3 +-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 63554a0fcd..967f6a5f72 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -93,7 +93,10 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn from_naive_utc_and_offset(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + pub const fn from_naive_utc_and_offset( + datetime: NaiveDateTime, + offset: Tz::Offset, + ) -> DateTime { DateTime { datetime, offset } } @@ -655,14 +658,6 @@ impl DateTime { NaiveDateTime::from_timestamp_millis(millis).as_ref().map(NaiveDateTime::and_utc) } - // FIXME: remove when our MSRV is 1.61+ - // This method is used by `NaiveDateTime::and_utc` because `DateTime::from_naive_utc_and_offset` - // can't be made const yet. - // Trait bounds in const function / implementation blocks were not supported until 1.61. - pub(crate) const fn from_naive_utc(datetime: NaiveDateTime) -> Self { - DateTime { datetime, offset: Utc } - } - /// The Unix Epoch, 1970-01-01 00:00:00 UTC. pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e3daaa65f1..de9976de9e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1066,8 +1066,7 @@ impl NaiveDateTime { /// ``` #[must_use] pub const fn and_utc(&self) -> DateTime { - // FIXME: use `DateTime::from_naive_utc_and_offset` when our MSRV is 1.61+. - DateTime::from_naive_utc(*self) + DateTime::from_naive_utc_and_offset(*self, Utc) } /// The minimum possible `NaiveDateTime`. From 3e6fa4c0dfd750c1e46d827e42488a6c6d736d4d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 Jan 2024 21:37:59 +0100 Subject: [PATCH 646/999] Don't use deprecated method in wasm test --- tests/wasm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wasm.rs b/tests/wasm.rs index b534f0a315..28eaacd0d5 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -22,7 +22,7 @@ fn now() { // Ensure time set by the test script is correct let now = env!("NOW"); - let actual = Utc.datetime_from_str(&now, "%s").unwrap(); + let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc(); let diff = utc - actual; assert!( diff < chrono::Duration::minutes(5), From cf17f7a2390fcb6ea0cbb2f28e57e46d686b6a2c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 28 Jan 2024 21:07:07 +0100 Subject: [PATCH 647/999] Remove deprecated methods from documentation --- src/lib.rs | 8 +------- src/naive/date.rs | 4 ++-- src/offset/mod.rs | 3 +-- src/round.rs | 2 +- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e532d7479f..dae6867295 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,7 +260,7 @@ //! # } //! ``` //! -//! Parsing can be done with three methods: +//! Parsing can be done with two methods: //! //! 1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait //! (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method @@ -278,12 +278,6 @@ //! [`DateTime::parse_from_rfc3339`](./struct.DateTime.html#method.parse_from_rfc3339) //! are similar but for well-known formats. //! -//! 3. [`Offset::datetime_from_str`](./offset/trait.TimeZone.html#method.datetime_from_str) is -//! similar but returns `DateTime` of given offset. -//! When the explicit offset is missing from the input, it simply uses given offset. -//! It issues an error when the input contains an explicit offset different -//! from the current offset. -//! //! More detailed control over the parsing process is available via //! [`format`](./format/index.html) module. //! diff --git a/src/naive/date.rs b/src/naive/date.rs index ed31729c56..ca11efdca1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1556,7 +1556,7 @@ impl Datelike for NaiveDate { /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); /// ``` /// - /// Combined with [`NaiveDate::pred`](#method.pred), + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), /// one can determine the number of days in a particular month. /// (Note that this panics when `year` is out of range.) /// @@ -1613,7 +1613,7 @@ impl Datelike for NaiveDate { /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); /// ``` /// - /// Combined with [`NaiveDate::pred`](#method.pred), + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), /// one can determine the number of days in a particular year. /// (Note that this panics when `year` is out of range.) /// diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 02ad1c7017..03764c38e3 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -423,8 +423,7 @@ pub trait TimeZone: Sized + Clone { /// Makes a new `DateTime` from the number of non-leap nanoseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// Unlike [`timestamp_millis`](#method.timestamp_millis), this never - /// panics. + /// Unlike [`timestamp_millis_opt`](#method.timestamp_millis_opt), this never fails. /// /// # Example /// diff --git a/src/round.rs b/src/round.rs index 4de1de60c8..ab16fbb89a 100644 --- a/src/round.rs +++ b/src/round.rs @@ -97,7 +97,7 @@ const fn span_for_digits(digits: u16) -> u32 { /// /// # Limitations /// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and -/// [`DateTime::timestamp_nanos`]. This means that they will fail if either the +/// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the /// `Duration` or the `DateTime` are too big to represented as nanoseconds. They /// will also fail if the `Duration` is bigger than the timestamp. pub trait DurationRound: Sized { From 83d5aae6aabb9f2ccbe696794d87c0844fb89ffd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 Jan 2024 18:48:15 +0100 Subject: [PATCH 648/999] Remove stray documentation --- src/lib.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dae6867295..795aa1596f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,11 +354,11 @@ //! //! ## Limitations //! -//! Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -//! Date types are limited to about +/- 262,000 years from the common epoch. -//! Time types are limited to nanosecond accuracy. -//! Leap seconds can be represented, but Chrono does not fully support them. -//! See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). +//! * Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +//! * Date types are limited to about +/- 262,000 years from the common epoch. +//! * Time types are limited to nanosecond accuracy. +//! * Leap seconds can be represented, but Chrono does not fully support them. +//! See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). //! //! ## Rust version requirements //! @@ -367,18 +367,6 @@ //! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done //! lightly. //! -//! Chrono inherently does not support an inaccurate or partial date and time representation. -//! Any operation that can be ambiguous will return `None` in such cases. -//! For example, "a month later" of 2014-01-30 is not well-defined -//! and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. -//! -//! Non ISO week handling is not yet supported. -//! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -//! crate ([sources](https://github.com/bcourtine/chrono-ext/)). -//! -//! Advanced time zone handling is not yet supported. -//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. -//! //! ## Relation between chrono and time 0.1 //! //! Rust first had a `time` module added to `std` in its 0.7 release. It later moved to From d87df5de78046f32425f261641d85d4a17ecda18 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 26 Jan 2024 07:18:07 +0000 Subject: [PATCH 649/999] Added additional tests for Duration bounds - Added tests for creating the maximum and minimum allowable values of Durations having a magnitude of seconds, testing the limits plus one value beyond the limits in both directions. These tests all pass. - Expanded the tests for creating the maximum and minimum allowable values of Durations having a magnitude of milliseconds. These tests examine the results in more detail, document what is being tested, and also test one value beyond the limits in both directions. Notably, the test for Duration::milliseconds() construction for i64::MIN currently fails, as it is erroneously allowed. This test is ignored for now, until the fix is applied. - Expanded the tests for creating the maximum and minimum allowable values of Durations having a magnitude of microseconds and nanoseconds. These tests examine the results in more detail, document what is being tested, and also test one value beyond the limits in both directions. They also test the maximum reportable value from .num_*() and the maximum storable value of the Duration separately. - Separated out the tests for MAX and MIN, for clarity. - Added additional tests for addition and subtraction operations on Durations, ensuring that equivalent tests are performed against both operations, such as adding and subtracting zero, adding and subtracting one nanosecond, and others. - Added tests for greater-than and less-than comparison of two Durations, to ensure that internal representation of partial seconds is correctly ordered. --- src/duration.rs | 296 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 284 insertions(+), 12 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 6a969b3dfc..00244ce78d 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -579,6 +579,42 @@ mod tests { assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); } + #[test] + fn test_duration_seconds_max_allowed() { + let duration = Duration::seconds(i64::MAX / 1_000); + assert_eq!(duration.num_seconds(), i64::MAX / 1_000); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + #[test] + fn test_duration_seconds_max_overflow() { + assert!(Duration::try_seconds(i64::MAX / 1_000 + 1).is_none()); + } + #[test] + #[should_panic(expected = "Duration::seconds out of bounds")] + fn test_duration_seconds_max_overflow_panic() { + let _ = Duration::seconds(i64::MAX / 1_000 + 1); + } + #[test] + fn test_duration_seconds_min_allowed() { + let duration = Duration::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + #[test] + fn test_duration_seconds_min_underflow() { + assert!(Duration::try_seconds(-i64::MAX / 1_000 - 1).is_none()); + } + #[test] + #[should_panic(expected = "Duration::seconds out of bounds")] + fn test_duration_seconds_min_underflow_panic() { + let _ = Duration::seconds(-i64::MAX / 1_000 - 1); + } #[test] fn test_duration_num_milliseconds() { @@ -589,10 +625,59 @@ mod tests { assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); - assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(-i64::MAX).num_milliseconds(), -i64::MAX); - assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), -i64::MAX); + } + #[test] + fn test_duration_milliseconds_max_allowed() { + // The maximum number of milliseconds acceptable through the constructor is + // equal to the number that can be stored in a Duration. + let duration = Duration::milliseconds(i64::MAX); + assert_eq!(duration.num_milliseconds(), i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_milliseconds_max_overflow() { + // Here we ensure that trying to add one millisecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(1)).is_none()); + } + #[test] + fn test_duration_milliseconds_min_allowed() { + // The minimum number of milliseconds acceptable through the constructor is + // not equal to the number that can be stored in a Duration - there is a + // difference of one (i64::MIN vs -i64::MAX). + let duration = Duration::milliseconds(-i64::MAX); + assert_eq!(duration.num_milliseconds(), -i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_milliseconds_min_underflow() { + // Here we ensure that trying to subtract one millisecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::milliseconds(1)) + .is_none()); + } + #[test] + #[ignore] + #[should_panic(expected = "Duration::milliseconds out of bounds")] + fn test_duration_milliseconds_min_underflow_panic() { + // Here we ensure that trying to create a value one millisecond below the + // minimum storable value will fail. This test is necessary because the + // storable range is -i64::MAX, but the constructor type of i64 will allow + // i64::MIN, which is one value below. + // WARNING: + // This test currently fails, because Duration::milliseconds() does not + // check against MIN, and hence allows an unsupported value to be created. + // Therefore it is currently marked as "ignore", until the fix is applied. + // This is because the current implementation of milliseconds() cannot + // actually panic. + let _ = Duration::milliseconds(i64::MIN); // Same as -i64::MAX - 1 } #[test] @@ -604,10 +689,6 @@ mod tests { assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); - assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(-i64::MAX).num_microseconds(), Some(-i64::MAX)); - assert_eq!(MAX.num_microseconds(), None); - assert_eq!(MIN.num_microseconds(), None); // overflow checks const MICROS_PER_DAY: i64 = 86_400_000_000; @@ -622,16 +703,86 @@ mod tests { assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); assert_eq!(Duration::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); } + #[test] + fn test_duration_microseconds_max_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a Duration, so this + // is not a particular insightful test. + let duration = Duration::microseconds(i64::MAX); + assert_eq!(duration.num_microseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000 + ); + // Here we create a Duration with the maximum possible number of + // microseconds by creating a Duration with the maximum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = Duration::milliseconds(i64::MAX); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_max_overflow() { + // This test establishes that a Duration can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = Duration::microseconds(i64::MAX) + Duration::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MAX as i128 + 1) * 1_000 + ); + // Here we ensure that trying to add one microsecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::microseconds(1)).is_none()); + } + #[test] + fn test_duration_microseconds_min_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a Duration, so this + // is not a particular insightful test. + let duration = Duration::microseconds(i64::MIN); + assert_eq!(duration.num_microseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 * 1_000 + ); + // Here we create a Duration with the minimum possible number of + // microseconds by creating a Duration with the minimum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = Duration::milliseconds(-i64::MAX); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_min_underflow() { + // This test establishes that a Duration can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = Duration::microseconds(i64::MIN) - Duration::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MIN as i128 - 1) * 1_000 + ); + // Here we ensure that trying to subtract one microsecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::microseconds(1)) + .is_none()); + } #[test] fn test_duration_num_nanoseconds() { assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(-i64::MAX).num_nanoseconds(), Some(-i64::MAX)); - assert_eq!(MAX.num_nanoseconds(), None); - assert_eq!(MIN.num_nanoseconds(), None); // overflow checks const NANOS_PER_DAY: i64 = 86_400_000_000_000; @@ -646,9 +797,124 @@ mod tests { assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); assert_eq!(Duration::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); } + #[test] + fn test_duration_nanoseconds_max_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a Duration, so this is not + // a particular insightful test. + let duration = Duration::nanoseconds(i64::MAX); + assert_eq!(duration.num_nanoseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + ); + // Here we create a Duration with the maximum possible number of nanoseconds + // by creating a Duration with the maximum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = Duration::milliseconds(i64::MAX); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_nanoseconds_max_overflow() { + // This test establishes that a Duration can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = Duration::nanoseconds(i64::MAX) + Duration::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + 1 + ); + // Here we ensure that trying to add one nanosecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); + } + #[test] + fn test_duration_nanoseconds_min_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a Duration, so this is not + // a particular insightful test. + let duration = Duration::nanoseconds(i64::MIN); + assert_eq!(duration.num_nanoseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 + ); + // Here we create a Duration with the minimum possible number of nanoseconds + // by creating a Duration with the minimum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = Duration::milliseconds(-i64::MAX); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_nanoseconds_min_underflow() { + // This test establishes that a Duration can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = Duration::nanoseconds(i64::MIN) - Duration::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 - 1 + ); + // Here we ensure that trying to subtract one nanosecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); + } + + #[test] + fn test_max() { + assert_eq!( + MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MAX, Duration::milliseconds(i64::MAX)); + assert_eq!(MAX.num_milliseconds(), i64::MAX); + assert_eq!(MAX.num_microseconds(), None); + assert_eq!(MAX.num_nanoseconds(), None); + } + #[test] + fn test_min() { + assert_eq!( + MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MIN, Duration::milliseconds(-i64::MAX)); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); + assert_eq!(MIN.num_microseconds(), None); + assert_eq!(MIN.num_nanoseconds(), None); + } + + #[test] + fn test_duration_ord() { + assert!(Duration::milliseconds(1) < Duration::milliseconds(2)); + assert!(Duration::milliseconds(2) > Duration::milliseconds(1)); + assert!(Duration::milliseconds(-1) > Duration::milliseconds(-2)); + assert!(Duration::milliseconds(-2) < Duration::milliseconds(-1)); + assert!(Duration::milliseconds(-1) < Duration::milliseconds(1)); + assert!(Duration::milliseconds(1) > Duration::milliseconds(-1)); + assert!(Duration::milliseconds(0) < Duration::milliseconds(1)); + assert!(Duration::milliseconds(0) > Duration::milliseconds(-1)); + assert!(Duration::milliseconds(1_001) < Duration::milliseconds(1_002)); + assert!(Duration::milliseconds(-1_001) > Duration::milliseconds(-1_002)); + assert!(Duration::nanoseconds(1_234_567_890) < Duration::nanoseconds(1_234_567_891)); + assert!(Duration::nanoseconds(-1_234_567_890) > Duration::nanoseconds(-1_234_567_891)); + assert!(Duration::milliseconds(i64::MAX) > Duration::milliseconds(i64::MAX - 1)); + assert!(Duration::milliseconds(-i64::MAX) < Duration::milliseconds(-i64::MAX + 1)); + } #[test] fn test_duration_checked_ops() { + assert_eq!( + Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(0)), + Some(Duration::milliseconds(i64::MAX)) + ); assert_eq!( Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)) @@ -656,14 +922,20 @@ mod tests { assert!(Duration::milliseconds(i64::MAX) .checked_add(&Duration::microseconds(1000)) .is_none()); + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); assert_eq!( Duration::milliseconds(-i64::MAX).checked_sub(&Duration::milliseconds(0)), Some(Duration::milliseconds(-i64::MAX)) ); + assert_eq!( + Duration::milliseconds(-i64::MAX + 1).checked_sub(&Duration::microseconds(999)), + Some(Duration::milliseconds(-i64::MAX + 2) - Duration::microseconds(1999)) + ); assert!(Duration::milliseconds(-i64::MAX) .checked_sub(&Duration::milliseconds(1)) .is_none()); + assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); } #[test] From e5309f8aa7f25e3058b93ac9caa27f46a3631f5d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 27 Jan 2024 04:43:14 +0000 Subject: [PATCH 650/999] Improved the documentation of Duration constructors - Added Panics and Errors sections where appropriate, as these are generally-expected and help draw attention to the fact that the standard (i.e. non-try) constructors can panic. The Errors section for the try constructors is common practice when returning None for overflow situations as well as for functions actually returning a Result. - Added an further explanation of the behaviour of the seconds() constructor. - Minor additional readability edits. --- src/duration.rs | 87 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 00244ce78d..a8fb742044 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -76,8 +76,13 @@ pub(crate) const MAX: Duration = Duration { }; impl Duration { - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of weeks. + /// + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -85,16 +90,26 @@ impl Duration { Duration::try_weeks(weeks).expect("Duration::weeks out of bounds") } - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of weeks. + /// + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Errors + /// /// Returns `None` when the duration is out of bounds. #[inline] pub fn try_weeks(weeks: i64) -> Option { weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds) } - /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of days. + /// + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -102,16 +117,25 @@ impl Duration { Duration::try_days(days).expect("Duration::days out of bounds") } - /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of days. + /// + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Errors + /// /// Returns `None` when the duration is out of bounds. #[inline] pub fn try_days(days: i64) -> Option { days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds) } - /// Makes a new `Duration` with given number of hours. + /// Makes a new `Duration` with the given number of hours. + /// /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -119,16 +143,24 @@ impl Duration { Duration::try_hours(hours).expect("Duration::hours out of bounds") } - /// Makes a new `Duration` with given number of hours. + /// Makes a new `Duration` with the given number of hours. + /// /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Errors + /// /// Returns `None` when the duration is out of bounds. #[inline] pub fn try_hours(hours: i64) -> Option { hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds) } - /// Makes a new `Duration` with given number of minutes. + /// Makes a new `Duration` with the given number of minutes. + /// /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -136,26 +168,39 @@ impl Duration { Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") } - /// Makes a new `Duration` with given number of minutes. + /// Makes a new `Duration` with the given number of minutes. + /// /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// + /// # Errors + /// /// Returns `None` when the duration is out of bounds. #[inline] pub fn try_minutes(minutes: i64) -> Option { minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds) } - /// Makes a new `Duration` with given number of seconds. - /// Panics when the duration is more than `i64::MAX` milliseconds - /// or less than `-i64::MAX` milliseconds. + /// Makes a new `Duration` with the given number of seconds. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds, i.e. when the value is more + /// than `i64::MAX / 1_000` seconds or less than `-i64::MAX / 1_000` seconds + /// (in this context, this is the same as `i64::MIN / 1_000` due to + /// rounding). #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { Duration::try_seconds(seconds).expect("Duration::seconds out of bounds") } - /// Makes a new `Duration` with given number of seconds. - /// Returns `None` when the duration is more than `i64::MAX` milliseconds - /// or less than `-i64::MAX` milliseconds. + /// Makes a new `Duration` with the given number of seconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is more than `i64::MAX / 1_000` seconds + /// or less than `-i64::MAX / 1_000` seconds (in this context, this is the + /// same as `i64::MIN / 1_000` due to rounding). #[inline] pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; @@ -165,7 +210,7 @@ impl Duration { Some(d) } - /// Makes a new `Duration` with given number of milliseconds. + /// Makes a new `Duration` with the given number of milliseconds. #[inline] pub const fn milliseconds(milliseconds: i64) -> Duration { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); @@ -173,7 +218,7 @@ impl Duration { Duration { secs, nanos } } - /// Makes a new `Duration` with given number of microseconds. + /// Makes a new `Duration` with the given number of microseconds. #[inline] pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); @@ -181,7 +226,7 @@ impl Duration { Duration { secs, nanos } } - /// Makes a new `Duration` with given number of nanoseconds. + /// Makes a new `Duration` with the given number of nanoseconds. #[inline] pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); From ec831e7bc9552f8336995de78168e5410b00c3bf Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 27 Jan 2024 05:19:54 +0000 Subject: [PATCH 651/999] Added Duration::try_milliseconds() - Added a new Duration::try_milliseconds() function, to attempt to create a new milliseconds-based Duration, and return None if it fails, based on checking against MAX and MIN. Currently, failure can only happen for exactly one value, which is i64::MIN. - Updated Duration::milliseconds() to call try_milliseconds() and panic if None is returned. Although panicking in production code and especially library code is bad, this is in keeping with current Chrono behaviour. Note that this function is now no longer const. - Updated the Duration::milliseconds() documentation to make it clear that it now panics. - Added documentation to Duration::microseconds() and nanoseconds() to make it clear that they are infallible. - All tests now pass, including the one previously ignored. --- src/duration.rs | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index a8fb742044..20591438d4 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -211,14 +211,42 @@ impl Duration { } /// Makes a new `Duration` with the given number of milliseconds. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds, i.e. when the duration is + /// more than `i64::MAX` milliseconds or less than `-i64::MAX` milliseconds. + /// Notably, this is not the same as `i64::MIN`. + #[inline] + pub fn milliseconds(milliseconds: i64) -> Duration { + Duration::try_milliseconds(milliseconds).expect("Duration::milliseconds out of bounds") + } + + /// Makes a new `Duration` with the given number of milliseconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is more than `i64::MAX` milliseconds or + /// less than `-i64::MAX` milliseconds. Notably, this is not the same as + /// `i64::MIN`. #[inline] - pub const fn milliseconds(milliseconds: i64) -> Duration { + pub fn try_milliseconds(milliseconds: i64) -> Option { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs, nanos } + let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + // We don't need to compare against MAX, as this function accepts an + // i64, and MAX is aligned to i64::MAX milliseconds. + if d < MIN { + return None; + } + Some(d) } /// Makes a new `Duration` with the given number of microseconds. + /// + /// The number of microseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `Duration`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. #[inline] pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); @@ -227,6 +255,11 @@ impl Duration { } /// Makes a new `Duration` with the given number of nanoseconds. + /// + /// The number of nanoseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `Duration`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. #[inline] pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); @@ -709,19 +742,12 @@ mod tests { .is_none()); } #[test] - #[ignore] #[should_panic(expected = "Duration::milliseconds out of bounds")] fn test_duration_milliseconds_min_underflow_panic() { // Here we ensure that trying to create a value one millisecond below the // minimum storable value will fail. This test is necessary because the // storable range is -i64::MAX, but the constructor type of i64 will allow // i64::MIN, which is one value below. - // WARNING: - // This test currently fails, because Duration::milliseconds() does not - // check against MIN, and hence allows an unsupported value to be created. - // Therefore it is currently marked as "ignore", until the fix is applied. - // This is because the current implementation of milliseconds() cannot - // actually panic. let _ = Duration::milliseconds(i64::MIN); // Same as -i64::MAX - 1 } From e031ffbad50c0e100247eadb6a8f8424fdd21967 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 27 Jan 2024 06:40:55 +0000 Subject: [PATCH 652/999] Improved Duration documentation - Added a description of internal storage and permissable range to the Duration type. - Adjusted formatting of `Duration` in a number of related function Rustdoc blocks, to aid readability. - Clarified comments for Duration::num_milliseconds(). --- src/duration.rs | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 20591438d4..eb985e7f42 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -49,7 +49,13 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// -/// This also allows for the negative duration; see individual methods for details. +/// This also allows for negative durations; see individual methods for details. +/// +/// A `Duration` is represented internally as a complement of seconds and +/// nanoseconds. The range is restricted to that of `i64` milliseconds, with the +/// minimum value notably being set to `-i64::MAX` rather than allowing the full +/// range of `i64::MIN`. This is to allow easy flipping of sign, so that for +/// instance `abs()` can be called without any checks. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr( any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), @@ -266,30 +272,30 @@ impl Duration { Duration { secs, nanos: nanos as i32 } } - /// Returns the total number of whole weeks in the duration. + /// Returns the total number of whole weeks in the `Duration`. #[inline] pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } - /// Returns the total number of whole days in the duration. + /// Returns the total number of whole days in the `Duration`. pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } - /// Returns the total number of whole hours in the duration. + /// Returns the total number of whole hours in the `Duration`. #[inline] pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } - /// Returns the total number of whole minutes in the duration. + /// Returns the total number of whole minutes in the `Duration`. #[inline] pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } - /// Returns the total number of whole seconds in the duration. + /// Returns the total number of whole seconds in the `Duration`. pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. if self.secs < 0 && self.nanos > 0 { @@ -301,7 +307,7 @@ impl Duration { /// Returns the number of nanoseconds such that /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the duration. + /// nanoseconds in the `Duration`. pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC @@ -310,16 +316,17 @@ impl Duration { } } - /// Returns the total number of whole milliseconds in the duration, + /// Returns the total number of whole milliseconds in the `Duration`. pub const fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined - // such that the range is exactly i64 milliseconds. + // A proper Duration will not overflow, because MIN and MAX are defined such + // that the range is within the bounds of an i64, from -i64::MAX through to + // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. let secs_part = self.num_seconds() * MILLIS_PER_SEC; let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; secs_part + nanos_part as i64 } - /// Returns the total number of whole microseconds in the duration, + /// Returns the total number of whole microseconds in the `Duration`, /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); @@ -327,7 +334,7 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Returns the total number of whole nanoseconds in the duration, + /// Returns the total number of whole nanoseconds in the `Duration`, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); @@ -335,7 +342,7 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Add two durations, returning `None` if overflow occurred. + /// Add two `Duration`s, returning `None` if overflow occurred. #[must_use] pub fn checked_add(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); @@ -354,7 +361,7 @@ impl Duration { } } - /// Subtract two durations, returning `None` if overflow occurred. + /// Subtract two `Duration`s, returning `None` if overflow occurred. #[must_use] pub fn checked_sub(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); @@ -373,7 +380,7 @@ impl Duration { } } - /// Returns the duration as an absolute (non-negative) value. + /// Returns the `Duration` as an absolute (non-negative) value. #[inline] pub const fn abs(&self) -> Duration { if self.secs < 0 && self.nanos != 0 { @@ -395,13 +402,13 @@ impl Duration { MAX } - /// A duration where the stored seconds and nanoseconds are equal to zero. + /// A `Duration` where the stored seconds and nanoseconds are equal to zero. #[inline] pub const fn zero() -> Duration { Duration { secs: 0, nanos: 0 } } - /// Returns `true` if the duration equals `Duration::zero()`. + /// Returns `true` if the `Duration` equals `Duration::zero()`. #[inline] pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 From 086a26b0207be42ea1fb240ce7977025e43b8d50 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:21:08 +0200 Subject: [PATCH 653/999] Remove duplicate `try_opt` macro --- src/duration.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index eb985e7f42..7e987e7117 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,6 +16,8 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; +use crate::try_opt; + #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -38,15 +40,6 @@ const SECS_PER_DAY: i64 = 86_400; /// The number of (non-leap) seconds in a week. const SECS_PER_WEEK: i64 = 604_800; -macro_rules! try_opt { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - /// ISO 8601 time duration with nanosecond precision. /// /// This also allows for negative durations; see individual methods for details. From a14b16071f0f9333441056dad02003e05db5cf75 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:43:38 +0200 Subject: [PATCH 654/999] Add `Duration::new` --- src/duration.rs | 17 +++++++++++++++++ src/naive/time/mod.rs | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 7e987e7117..4aa86cd4c8 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -75,6 +75,23 @@ pub(crate) const MAX: Duration = Duration { }; impl Duration { + /// Makes a new `Duration` with given number of seconds and nanoseconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000. + pub(crate) const fn new(secs: i64, nanos: u32) -> Option { + if secs < MIN.secs + || secs > MAX.secs + || nanos > 1_000_000_000 + || (secs == MAX.secs && nanos > MAX.nanos as u32) + || (secs == MIN.secs && nanos < MIN.nanos as u32) + { + return None; + } + Some(Duration { secs, nanos: nanos as i32 }) + } + /// Makes a new `Duration` with the given number of weeks. /// /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 94aee99168..bcbd557809 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1196,7 +1196,7 @@ impl Add for NaiveTime { // overflow during the conversion to `chrono::Duration`. // But we limit to double that just in case `self` is a leap-second. let secs = rhs.as_secs() % (2 * 24 * 60 * 60); - let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap(); self.overflowing_add_signed(d).0 } } @@ -1306,7 +1306,7 @@ impl Sub for NaiveTime { // overflow during the conversion to `chrono::Duration`. // But we limit to double that just in case `self` is a leap-second. let secs = rhs.as_secs() % (2 * 24 * 60 * 60); - let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap(); self.overflowing_sub_signed(d).0 } } From 7c4bf0b5780f3f1391516a5eb12d58fa7ea67f23 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 27 Jul 2023 16:21:20 +0200 Subject: [PATCH 655/999] Make all methods on `Duration` const --- src/duration.rs | 103 +++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 4aa86cd4c8..96ad89e2d0 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,7 +16,7 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; -use crate::try_opt; +use crate::{expect, try_opt}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -102,8 +102,8 @@ impl Duration { /// Panics when the duration is out of bounds. #[inline] #[must_use] - pub fn weeks(weeks: i64) -> Duration { - Duration::try_weeks(weeks).expect("Duration::weeks out of bounds") + pub const fn weeks(weeks: i64) -> Duration { + expect!(Duration::try_weeks(weeks), "Duration::weeks out of bounds") } /// Makes a new `Duration` with the given number of weeks. @@ -115,8 +115,8 @@ impl Duration { /// /// Returns `None` when the duration is out of bounds. #[inline] - pub fn try_weeks(weeks: i64) -> Option { - weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds) + pub const fn try_weeks(weeks: i64) -> Option { + Duration::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) } /// Makes a new `Duration` with the given number of days. @@ -129,8 +129,8 @@ impl Duration { /// Panics when the duration is out of bounds. #[inline] #[must_use] - pub fn days(days: i64) -> Duration { - Duration::try_days(days).expect("Duration::days out of bounds") + pub const fn days(days: i64) -> Duration { + expect!(Duration::try_days(days), "Duration::days out of bounds") } /// Makes a new `Duration` with the given number of days. @@ -142,8 +142,8 @@ impl Duration { /// /// Returns `None` when the duration is out of bounds. #[inline] - pub fn try_days(days: i64) -> Option { - days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds) + pub const fn try_days(days: i64) -> Option { + Duration::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) } /// Makes a new `Duration` with the given number of hours. @@ -155,8 +155,8 @@ impl Duration { /// Panics when the duration is out of bounds. #[inline] #[must_use] - pub fn hours(hours: i64) -> Duration { - Duration::try_hours(hours).expect("Duration::hours out of bounds") + pub const fn hours(hours: i64) -> Duration { + expect!(Duration::try_hours(hours), "Duration::hours out of bounds") } /// Makes a new `Duration` with the given number of hours. @@ -167,8 +167,8 @@ impl Duration { /// /// Returns `None` when the duration is out of bounds. #[inline] - pub fn try_hours(hours: i64) -> Option { - hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds) + pub const fn try_hours(hours: i64) -> Option { + Duration::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) } /// Makes a new `Duration` with the given number of minutes. @@ -180,8 +180,8 @@ impl Duration { /// Panics when the duration is out of bounds. #[inline] #[must_use] - pub fn minutes(minutes: i64) -> Duration { - Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") + pub const fn minutes(minutes: i64) -> Duration { + expect!(Duration::try_minutes(minutes), "Duration::minutes out of bounds") } /// Makes a new `Duration` with the given number of minutes. @@ -192,8 +192,8 @@ impl Duration { /// /// Returns `None` when the duration is out of bounds. #[inline] - pub fn try_minutes(minutes: i64) -> Option { - minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds) + pub const fn try_minutes(minutes: i64) -> Option { + Duration::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) } /// Makes a new `Duration` with the given number of seconds. @@ -206,8 +206,8 @@ impl Duration { /// rounding). #[inline] #[must_use] - pub fn seconds(seconds: i64) -> Duration { - Duration::try_seconds(seconds).expect("Duration::seconds out of bounds") + pub const fn seconds(seconds: i64) -> Duration { + expect!(Duration::try_seconds(seconds), "Duration::seconds out of bounds") } /// Makes a new `Duration` with the given number of seconds. @@ -218,12 +218,8 @@ impl Duration { /// or less than `-i64::MAX / 1_000` seconds (in this context, this is the /// same as `i64::MIN / 1_000` due to rounding). #[inline] - pub fn try_seconds(seconds: i64) -> Option { - let d = Duration { secs: seconds, nanos: 0 }; - if d < MIN || d > MAX { - return None; - } - Some(d) + pub const fn try_seconds(seconds: i64) -> Option { + Duration::new(seconds, 0) } /// Makes a new `Duration` with the given number of milliseconds. @@ -234,8 +230,8 @@ impl Duration { /// more than `i64::MAX` milliseconds or less than `-i64::MAX` milliseconds. /// Notably, this is not the same as `i64::MIN`. #[inline] - pub fn milliseconds(milliseconds: i64) -> Duration { - Duration::try_milliseconds(milliseconds).expect("Duration::milliseconds out of bounds") + pub const fn milliseconds(milliseconds: i64) -> Duration { + expect!(Duration::try_milliseconds(milliseconds), "Duration::milliseconds out of bounds") } /// Makes a new `Duration` with the given number of milliseconds. @@ -246,14 +242,14 @@ impl Duration { /// less than `-i64::MAX` milliseconds. Notably, this is not the same as /// `i64::MIN`. #[inline] - pub fn try_milliseconds(milliseconds: i64) -> Option { - let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + pub const fn try_milliseconds(milliseconds: i64) -> Option { // We don't need to compare against MAX, as this function accepts an // i64, and MAX is aligned to i64::MAX milliseconds. - if d < MIN { + if milliseconds < -i64::MAX { return None; } + let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); + let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; Some(d) } @@ -354,40 +350,30 @@ impl Duration { /// Add two `Duration`s, returning `None` if overflow occurred. #[must_use] - pub fn checked_add(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); + pub const fn checked_add(&self, rhs: &Duration) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `Duration::new`. + let mut secs = self.secs + rhs.secs; let mut nanos = self.nanos + rhs.nanos; if nanos >= NANOS_PER_SEC { nanos -= NANOS_PER_SEC; - secs = try_opt!(secs.checked_add(1)); - } - let d = Duration { secs, nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) + secs += 1; } + Duration::new(secs, nanos as u32) } /// Subtract two `Duration`s, returning `None` if overflow occurred. #[must_use] - pub fn checked_sub(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); + pub const fn checked_sub(&self, rhs: &Duration) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `Duration::new`. + let mut secs = self.secs - rhs.secs; let mut nanos = self.nanos - rhs.nanos; if nanos < 0 { nanos += NANOS_PER_SEC; - secs = try_opt!(secs.checked_sub(1)); - } - let d = Duration { secs, nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) + secs -= 1; } + Duration::new(secs, nanos as u32) } /// Returns the `Duration` as an absolute (non-negative) value. @@ -428,23 +414,22 @@ impl Duration { /// /// This function errors when original duration is larger than the maximum /// value supported for this type. - pub fn from_std(duration: StdDuration) -> Result { + pub const fn from_std(duration: StdDuration) -> Result { // We need to check secs as u64 before coercing to i64 if duration.as_secs() > MAX.secs as u64 { return Err(OutOfRangeError(())); } - let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 }; - if d > MAX { - return Err(OutOfRangeError(())); + match Duration::new(duration.as_secs() as i64, duration.subsec_nanos()) { + Some(d) => Ok(d), + None => Err(OutOfRangeError(())), } - Ok(d) } /// Creates a `std::time::Duration` object from `time::Duration` /// /// This function errors when duration is less than zero. As standard /// library implementation is limited to non-negative values. - pub fn to_std(&self) -> Result { + pub const fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); } From d7afd72da503b5d11217354f04bb2d88825d9ee7 Mon Sep 17 00:00:00 2001 From: Scott Driggers Date: Wed, 22 Dec 2021 14:15:39 -0500 Subject: [PATCH 656/999] Add `test_duration_const()` --- src/duration.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/duration.rs b/src/duration.rs index 96ad89e2d0..be3dc0ceb5 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1143,6 +1143,40 @@ mod tests { ); } + #[test] + fn test_duration_const() { + const ONE_WEEK: Duration = Duration::weeks(1); + const ONE_DAY: Duration = Duration::days(1); + const ONE_HOUR: Duration = Duration::hours(1); + const ONE_MINUTE: Duration = Duration::minutes(1); + const ONE_SECOND: Duration = Duration::seconds(1); + const ONE_MILLI: Duration = Duration::milliseconds(1); + const ONE_MICRO: Duration = Duration::microseconds(1); + const ONE_NANO: Duration = Duration::nanoseconds(1); + let combo: Duration = ONE_WEEK + + ONE_DAY + + ONE_HOUR + + ONE_MINUTE + + ONE_SECOND + + ONE_MILLI + + ONE_MICRO + + ONE_NANO; + + assert!(ONE_WEEK != Duration::zero()); + assert!(ONE_DAY != Duration::zero()); + assert!(ONE_HOUR != Duration::zero()); + assert!(ONE_MINUTE != Duration::zero()); + assert!(ONE_SECOND != Duration::zero()); + assert!(ONE_MILLI != Duration::zero()); + assert!(ONE_MICRO != Duration::zero()); + assert!(ONE_NANO != Duration::zero()); + assert_eq!( + combo, + Duration::seconds(86400 * 7 + 86400 + 3600 + 60 + 1) + + Duration::nanoseconds(1 + 1_000 + 1_000_000) + ); + } + #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { From 7cc4ce89eac2356584826751a4954dec66a5c889 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 21:57:54 +0200 Subject: [PATCH 657/999] Make `DateTime::from_timestamp` const --- src/datetime/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 967f6a5f72..7b0fa842c4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -27,6 +27,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::try_opt; #[allow(deprecated)] use crate::Date; use crate::{Datelike, Months, Timelike, Weekday}; @@ -626,8 +627,11 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp(secs: i64, nsecs: u32) -> Option { - NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) + pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option { + Some(DateTime { + datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)), + offset: Utc, + }) } /// Makes a new [`DateTime`] from the number of non-leap milliseconds From 45785b58d3af3c1ee6273191d443eee14ca461ec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 21:58:30 +0200 Subject: [PATCH 658/999] Make methods on `NaiveWeek` const --- src/naive/date.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index ca11efdca1..0b3fafc2ad 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -61,14 +61,14 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn first_day(&self) -> NaiveDate { + pub const fn first_day(&self) -> NaiveDate { let start = self.start.num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to subtract from `self.date`. // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - self.date.add_days(days).unwrap() + expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`") } /// Returns a date representing the last day of the week. @@ -89,14 +89,14 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn last_day(&self) -> NaiveDate { + pub const fn last_day(&self) -> NaiveDate { let end = self.start.pred().num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to add to `self.date`. // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - self.date.add_days(days).unwrap() + expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`") } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -119,7 +119,7 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn days(&self) -> RangeInclusive { + pub const fn days(&self) -> RangeInclusive { self.first_day()..=self.last_day() } } From 912cc63602edfa0a36623799ef8dd59e94793e50 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:03:46 +0200 Subject: [PATCH 659/999] Make remaining methods on `NaiveDate` const --- src/naive/date.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0b3fafc2ad..c4fe5209d1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1155,9 +1155,12 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let days = i32::try_from(rhs.num_days()).ok()?; - self.add_days(days) + pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { + let days = rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) } /// Subtracts the number of whole days in the given `Duration` from the current date. @@ -1181,9 +1184,12 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let days = i32::try_from(-rhs.num_days()).ok()?; - self.add_days(days) + pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { + let days = -rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) } /// Subtracts another `NaiveDate` from the current date. @@ -1209,7 +1215,7 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { let year1 = self.year(); let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); From 48369534318364eee5fb725aaf9729756aed5ba3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:29:58 +0200 Subject: [PATCH 660/999] Make `NaiveTime::signed_duration_since` const --- src/naive/time/mod.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index bcbd557809..7d87280c3d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -724,7 +724,7 @@ impl NaiveTime { /// Duration::seconds(61)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { // | | :leap| | | | | | | :leap| | // | | : | | | | | | | : | | // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- @@ -735,25 +735,20 @@ impl NaiveTime { // `rhs.frac`|========================================>| // | | | `self - rhs` | | - use core::cmp::Ordering; - - let secs = i64::from(self.secs) - i64::from(rhs.secs); - let frac = i64::from(self.frac) - i64::from(rhs.frac); + let mut secs = self.secs as i64 - rhs.secs as i64; + let frac = self.frac as i64 - rhs.frac as i64; // `secs` may contain a leap second yet to be counted - let adjust = match self.secs.cmp(&rhs.secs) { - Ordering::Greater => i64::from(rhs.frac >= 1_000_000_000), - Ordering::Equal => 0, - Ordering::Less => { - if self.frac >= 1_000_000_000 { - -1 - } else { - 0 - } - } - }; + if self.secs > rhs.secs && rhs.frac >= 1_000_000_000 { + secs += 1; + } else if self.secs < rhs.secs && self.frac >= 1_000_000_000 { + secs -= 1; + } + + let secs_from_frac = frac.div_euclid(1_000_000_000); + let frac = frac.rem_euclid(1_000_000_000) as u32; - OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) + expect!(OldDuration::new(secs + secs_from_frac, frac), "must be in range") } /// Adds given `FixedOffset` to the current time, and returns the number of days that should be From e56f9c5797fce18ec78c7e6437bde21c809fa803 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 2 Oct 2023 07:20:20 +0200 Subject: [PATCH 661/999] Simplify `NaiveTime::overflowing_add_signed` and make const --- src/naive/time/mod.rs | 69 ++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 7d87280c3d..f6861c0385 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -582,46 +582,30 @@ impl NaiveTime { /// (from_hms(20, 4, 5), -86_400)); /// ``` #[must_use] - pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { - let mut secs = self.secs; - let mut frac = self.frac; - - // check if `self` is a leap second and adding `rhs` would escape that leap second. - // if it's the case, update `self` and `rhs` to involve no leap second; - // otherwise the addition immediately finishes. + pub const fn overflowing_add_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + let mut secs = self.secs as i64; + let mut frac = self.frac as i32; + let secs_to_add = rhs.num_seconds(); + let frac_to_add = rhs.subsec_nanos(); + + // Check if `self` is a leap second and adding `rhs` would escape that leap second. + // If that is the case, update `frac` and `secs` to involve no leap second. + // If it stays within the leap second or the second before, and only adds a fractional + // second, just do that and return (this way the rest of the code can ignore leap seconds). if frac >= 1_000_000_000 { - let rfrac = 2_000_000_000 - frac; - if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs -= OldDuration::nanoseconds(i64::from(rfrac)); + // check below is adjusted to not overflow an i32: `frac + frac_to_add >= 2_000_000_000` + if secs_to_add > 0 || (frac_to_add > 0 && frac >= 2_000_000_000 - frac_to_add) { + frac -= 1_000_000_000; + } else if secs_to_add < 0 { + frac -= 1_000_000_000; secs += 1; - frac = 0; - } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs += OldDuration::nanoseconds(i64::from(frac)); - frac = 0; } else { - frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; - debug_assert!(frac < 2_000_000_000); - return (NaiveTime { secs, frac }, 0); + return (NaiveTime { secs: self.secs, frac: (frac + frac_to_add) as u32 }, 0); } } - debug_assert!(secs <= 86_400); - debug_assert!(frac < 1_000_000_000); - - let rhssecs = rhs.num_seconds(); - let rhsfrac = (rhs - OldDuration::seconds(rhssecs)).num_nanoseconds().unwrap(); - debug_assert_eq!(OldDuration::seconds(rhssecs) + OldDuration::nanoseconds(rhsfrac), rhs); - let rhssecsinday = rhssecs % 86_400; - let mut morerhssecs = rhssecs - rhssecsinday; - let rhssecs = rhssecsinday as i32; - let rhsfrac = rhsfrac as i32; - debug_assert!(-86_400 < rhssecs && rhssecs < 86_400); - debug_assert_eq!(morerhssecs % 86_400, 0); - debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); - - let mut secs = secs as i32 + rhssecs; - let mut frac = frac as i32 + rhsfrac; - debug_assert!(-86_400 < secs && secs < 2 * 86_400); - debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); + + let mut secs = secs + secs_to_add; + frac += frac_to_add; if frac < 0 { frac += 1_000_000_000; @@ -630,19 +614,10 @@ impl NaiveTime { frac -= 1_000_000_000; secs += 1; } - debug_assert!((-86_400..2 * 86_400).contains(&secs)); - debug_assert!((0..1_000_000_000).contains(&frac)); - - if secs < 0 { - secs += 86_400; - morerhssecs -= 86_400; - } else if secs >= 86_400 { - secs -= 86_400; - morerhssecs += 86_400; - } - debug_assert!((0..86_400).contains(&secs)); - (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) + let secs_in_day = secs.rem_euclid(86_400); + let remaining = secs - secs_in_day; + (NaiveTime { secs: secs_in_day as u32, frac: frac as u32 }, remaining) } /// Subtracts given `Duration` from the current time, and also returns the number of *seconds* From f78eb20ef1e9b92de430b78c5a828ff0f2694ce1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 2 Oct 2023 13:17:55 +0200 Subject: [PATCH 662/999] Make `NaiveTime::overflowing_sub_signed` const --- src/duration.rs | 19 ++++++++++++++----- src/naive/time/mod.rs | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index be3dc0ceb5..7c371b1740 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -435,6 +435,15 @@ impl Duration { } Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) } + + /// This duplicates `Neg::neg` because trait methods can't be const yet. + pub(crate) const fn neg(self) -> Duration { + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + Duration { secs: -self.secs - secs_diff, nanos } + } } impl Neg for Duration { @@ -442,11 +451,11 @@ impl Neg for Duration { #[inline] fn neg(self) -> Duration { - if self.nanos == 0 { - Duration { secs: -self.secs, nanos: 0 } - } else { - Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } - } + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + Duration { secs: -self.secs - secs_diff, nanos } } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f6861c0385..b47639ce0d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -639,8 +639,8 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { - let (time, rhs) = self.overflowing_add_signed(-rhs); + pub const fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + let (time, rhs) = self.overflowing_add_signed(rhs.neg()); (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) } From fd6679199640af82f73f7fe61b702ed5396bb85c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:15:59 +0200 Subject: [PATCH 663/999] Make remaining methods on `NaiveDateTime` const --- src/naive/datetime/mod.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index de9976de9e..69fa52a824 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -504,9 +504,11 @@ impl NaiveDateTime { #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.timestamp_nanos_opt() - .expect("value can not be represented in a timestamp with nanosecond precision.") + pub const fn timestamp_nanos(&self) -> i64 { + expect!( + self.timestamp_nanos_opt(), + "value can not be represented in a timestamp with nanosecond precision." + ) } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -542,9 +544,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_nanos_opt(&self) -> Option { + pub const fn timestamp_nanos_opt(&self) -> Option { let mut timestamp = self.timestamp(); - let mut timestamp_subsec_nanos = i64::from(self.timestamp_subsec_nanos()); + let mut timestamp_subsec_nanos = self.timestamp_subsec_nanos() as i64; // subsec nanos are always non-negative, however the timestamp itself (both in seconds and in nanos) can be // negative. Now i64::MIN is NOT dividable by 1_000_000_000, so @@ -562,7 +564,7 @@ impl NaiveDateTime { timestamp += 1; } - timestamp.checked_mul(1_000_000_000).and_then(|ns| ns.checked_add(timestamp_subsec_nanos)) + try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(timestamp_subsec_nanos) } /// Returns the number of milliseconds since the last whole non-leap second. @@ -699,7 +701,7 @@ impl NaiveDateTime { /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); // early checking to avoid overflow in OldDuration::seconds @@ -852,7 +854,7 @@ impl NaiveDateTime { /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); // early checking to avoid overflow in OldDuration::seconds @@ -860,7 +862,7 @@ impl NaiveDateTime { return None; } - let date = self.date.checked_sub_signed(OldDuration::seconds(rhs))?; + let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -949,8 +951,13 @@ impl NaiveDateTime { /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { - self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) + pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { + expect!( + self.date + .signed_duration_since(rhs.date) + .checked_add(&self.time.signed_duration_since(rhs.time)), + "always in range" + ) } /// Formats the combined date and time with the specified formatting items. From 6f06e1ab0eada3b17169ce870a1c7182d59a4da3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 Jan 2024 20:21:49 +0100 Subject: [PATCH 664/999] Make methods on `DateTime` const where possible --- src/datetime/mod.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 7b0fa842c4..6d79239582 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -200,7 +200,7 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn timestamp(&self) -> i64 { + pub const fn timestamp(&self) -> i64 { self.datetime.timestamp() } @@ -219,7 +219,7 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_millis(&self) -> i64 { + pub const fn timestamp_millis(&self) -> i64 { self.datetime.timestamp_millis() } @@ -238,7 +238,7 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_micros(&self) -> i64 { + pub const fn timestamp_micros(&self) -> i64 { self.datetime.timestamp_micros() } @@ -254,9 +254,9 @@ impl DateTime { #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.timestamp_nanos_opt() - .expect("value can not be represented in a timestamp with nanosecond precision.") + #[allow(deprecated)] + pub const fn timestamp_nanos(&self) -> i64 { + self.datetime.timestamp_nanos() } /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. @@ -294,7 +294,7 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_nanos_opt(&self) -> Option { + pub const fn timestamp_nanos_opt(&self) -> Option { self.datetime.timestamp_nanos_opt() } @@ -303,7 +303,7 @@ impl DateTime { /// In event of a leap second this may exceed 999. #[inline] #[must_use] - pub fn timestamp_subsec_millis(&self) -> u32 { + pub const fn timestamp_subsec_millis(&self) -> u32 { self.datetime.timestamp_subsec_millis() } @@ -312,7 +312,7 @@ impl DateTime { /// In event of a leap second this may exceed 999,999. #[inline] #[must_use] - pub fn timestamp_subsec_micros(&self) -> u32 { + pub const fn timestamp_subsec_micros(&self) -> u32 { self.datetime.timestamp_subsec_micros() } @@ -321,14 +321,14 @@ impl DateTime { /// In event of a leap second this may exceed 999,999,999. #[inline] #[must_use] - pub fn timestamp_subsec_nanos(&self) -> u32 { + pub const fn timestamp_subsec_nanos(&self) -> u32 { self.datetime.timestamp_subsec_nanos() } /// Retrieves an associated offset from UTC. #[inline] #[must_use] - pub fn offset(&self) -> &Tz::Offset { + pub const fn offset(&self) -> &Tz::Offset { &self.offset } @@ -360,7 +360,7 @@ impl DateTime { /// information. #[inline] #[must_use] - pub fn to_utc(&self) -> DateTime { + pub const fn to_utc(&self) -> DateTime { DateTime { datetime: self.datetime, offset: Utc } } @@ -476,7 +476,7 @@ impl DateTime { /// Returns a view to the naive UTC datetime. #[inline] #[must_use] - pub fn naive_utc(&self) -> NaiveDateTime { + pub const fn naive_utc(&self) -> NaiveDateTime { self.datetime } @@ -658,8 +658,8 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_millis(millis: i64) -> Option { - NaiveDateTime::from_timestamp_millis(millis).as_ref().map(NaiveDateTime::and_utc) + pub const fn from_timestamp_millis(millis: i64) -> Option { + Some(try_opt!(NaiveDateTime::from_timestamp_millis(millis)).and_utc()) } /// The Unix Epoch, 1970-01-01 00:00:00 UTC. From b92b736f5261537cd6c08f050453f643a644fa92 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 13:50:02 +0100 Subject: [PATCH 665/999] Set main branch as dependabot target --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5271c4ecf..764cd26a9c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,14 +4,14 @@ updates: directory: "/" schedule: interval: "weekly" - target-branch: "0.4.x" + target-branch: "main" - package-ecosystem: "cargo" directory: "/fuzz/" schedule: interval: "weekly" - target-branch: "0.4.x" + target-branch: "main" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - target-branch: "0.4.x" + target-branch: "main" From f8488762482f1ef01c5bedf20ffaa7f8666bf09e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 16:39:12 +0100 Subject: [PATCH 666/999] Make `Duration::new` public --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 7c371b1740..6b1fb6d940 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -80,7 +80,7 @@ impl Duration { /// # Errors /// /// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000. - pub(crate) const fn new(secs: i64, nanos: u32) -> Option { + pub const fn new(secs: i64, nanos: u32) -> Option { if secs < MIN.secs || secs > MAX.secs || nanos > 1_000_000_000 From ae695a412ce8682639b461b3a521579729379e4e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 16:40:09 +0100 Subject: [PATCH 667/999] Fix check for `nanos` in `Duration::new` --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 6b1fb6d940..c7d6ee9b9a 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -83,7 +83,7 @@ impl Duration { pub const fn new(secs: i64, nanos: u32) -> Option { if secs < MIN.secs || secs > MAX.secs - || nanos > 1_000_000_000 + || nanos >= 1_000_000_000 || (secs == MAX.secs && nanos > MAX.nanos as u32) || (secs == MIN.secs && nanos < MIN.nanos as u32) { From f4b47013ed9e87b84d6e635999f514ad9800334e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:10:59 +0000 Subject: [PATCH 668/999] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index a819b21a5b..520a39ed2f 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -25,7 +25,7 @@ jobs: - name: Generate code coverage run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: From 6659f7b9ebc95ae90f268968bcd43b77052f017f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:39:21 +0100 Subject: [PATCH 669/999] Add `TimeDelta` as type alias of `Duration` --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 795aa1596f..950ca46ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -471,6 +471,9 @@ pub use duration::Duration; #[doc(no_inline)] pub use duration::OutOfRangeError; +/// Alias of [`Duration`]. +pub type TimeDelta = Duration; + use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). From d079c8c0323bb4fd9f27b8d97add8fc7c4e3aee3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:06:55 +0100 Subject: [PATCH 670/999] Rename `Duration` to `TimeDelta` in `datetime` module --- src/datetime/mod.rs | 75 +++++++++++++++++++++---------------------- src/datetime/tests.rs | 45 +++++++++++++------------- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 6d79239582..e4e460b280 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -14,7 +14,6 @@ use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; -use crate::duration::Duration as OldDuration; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] use crate::format::Locale; use crate::format::{ @@ -30,7 +29,7 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::try_opt; #[allow(deprecated)] use crate::Date; -use crate::{Datelike, Months, Timelike, Weekday}; +use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -364,14 +363,14 @@ impl DateTime { DateTime { datetime: self.datetime, offset: Utc } } - /// Adds given `Duration` to the current date and time. + /// Adds given `TimeDelta` to the current date and time. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. #[inline] #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) @@ -397,14 +396,14 @@ impl DateTime { .single() } - /// Subtracts given `Duration` from the current date and time. + /// Subtracts given `TimeDelta` from the current date and time. /// /// # Errors /// /// Returns `None` if the resulting date would be out of range. #[inline] #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) @@ -469,7 +468,7 @@ impl DateTime { pub fn signed_duration_since( self, rhs: impl Borrow>, - ) -> OldDuration { + ) -> TimeDelta { self.datetime.signed_duration_since(rhs.borrow().datetime) } @@ -1239,7 +1238,7 @@ impl hash::Hash for DateTime { } } -/// Add `chrono::Duration` to `DateTime`. +/// Add `TimeDelta` to `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1249,12 +1248,12 @@ impl hash::Hash for DateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. -impl Add for DateTime { +impl Add for DateTime { type Output = DateTime; #[inline] - fn add(self, rhs: OldDuration) -> DateTime { - self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> DateTime { + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") } } @@ -1273,9 +1272,9 @@ impl Add for DateTime { #[inline] fn add(self, rhs: Duration) -> DateTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") } } @@ -1289,11 +1288,11 @@ impl Add for DateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. -impl AddAssign for DateTime { +impl AddAssign for DateTime { #[inline] - fn add_assign(&mut self, rhs: OldDuration) { + fn add_assign(&mut self, rhs: TimeDelta) { let datetime = - self.datetime.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed"); + self.datetime.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed"); let tz = self.timezone(); *self = tz.from_utc_datetime(&datetime); } @@ -1312,8 +1311,8 @@ impl AddAssign for DateTime { impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); *self += rhs; } } @@ -1355,9 +1354,9 @@ impl Add for DateTime { } } -/// Subtract `chrono::Duration` from `DateTime`. +/// Subtract `TimeDelta` from `DateTime`. /// -/// This is the same as the addition with a negated `Duration`. +/// This is the same as the addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap /// second ever**, except when the `DateTime` itself represents a leap second in which case @@ -1367,12 +1366,12 @@ impl Add for DateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. -impl Sub for DateTime { +impl Sub for DateTime { type Output = DateTime; #[inline] - fn sub(self, rhs: OldDuration) -> DateTime { - self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> DateTime { + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") } } @@ -1391,15 +1390,15 @@ impl Sub for DateTime { #[inline] fn sub(self, rhs: Duration) -> DateTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") } } -/// Subtract-assign `chrono::Duration` from `DateTime`. +/// Subtract-assign `TimeDelta` from `DateTime`. /// -/// This is the same as the addition with a negated `Duration`. +/// This is the same as the addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `DateTime` itself represents a leap second in which case @@ -1409,11 +1408,11 @@ impl Sub for DateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. -impl SubAssign for DateTime { +impl SubAssign for DateTime { #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { + fn sub_assign(&mut self, rhs: TimeDelta) { let datetime = - self.datetime.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed"); + self.datetime.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed"); let tz = self.timezone(); *self = tz.from_utc_datetime(&datetime) } @@ -1432,8 +1431,8 @@ impl SubAssign for DateTime { impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); *self -= rhs; } } @@ -1476,19 +1475,19 @@ impl Sub for DateTime { } impl Sub> for DateTime { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: DateTime) -> OldDuration { + fn sub(self, rhs: DateTime) -> TimeDelta { self.signed_duration_since(rhs) } } impl Sub<&DateTime> for DateTime { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: &DateTime) -> OldDuration { + fn sub(self, rhs: &DateTime) -> TimeDelta { self.signed_duration_since(rhs) } } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3e96e227fa..88fcd80dec 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,10 +1,9 @@ use super::DateTime; -use crate::duration::Duration as OldDuration; use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike, Weekday}; +use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; #[derive(Clone)] struct DstTester; @@ -54,7 +53,7 @@ impl TimeZone for DstTester { DstTester::TO_WINTER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() - OldDuration::hours(1)); + .and_time(DstTester::transition_start_local() - TimeDelta::hours(1)); let local_to_summer_transition_start = NaiveDate::from_ymd_opt( local.year(), @@ -70,7 +69,7 @@ impl TimeZone for DstTester { DstTester::TO_SUMMER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() + OldDuration::hours(1)); + .and_time(DstTester::transition_start_local() + TimeDelta::hours(1)); if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { LocalResult::Single(DstTester::summer_offset()) @@ -393,12 +392,12 @@ fn test_datetime_offset() { let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( - dt + OldDuration::seconds(3600 + 60 + 1), + dt + TimeDelta::seconds(3600 + 60 + 1), Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() ); assert_eq!( dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), - OldDuration::seconds(-7 * 3600 - 3 * 60 - 3) + TimeDelta::seconds(-7 * 3600 - 3 * 60 - 3) ); assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); @@ -1272,16 +1271,16 @@ fn test_years_elapsed() { // This is always at least one year because 1 year = 52.1775 weeks. let one_year_ago = - Utc::now().date_naive() - OldDuration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. let two_year_ago = - Utc::now().date_naive() - OldDuration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::now().date_naive() + OldDuration::weeks(12); + let future = Utc::now().date_naive() + TimeDelta::weeks(12); assert_eq!(Utc::now().date_naive().years_since(future), None); } @@ -1291,20 +1290,20 @@ fn test_datetime_add_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_add = datetime; - datetime_add += OldDuration::seconds(60); - assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); + datetime_add += TimeDelta::seconds(60); + assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); + assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); + assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); } #[test] @@ -1317,8 +1316,8 @@ fn test_datetime_add_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_add += OldDuration::days(1); - assert_eq!(datetime_add, datetime + OldDuration::days(i)) + datetime_add += TimeDelta::days(1); + assert_eq!(datetime_add, datetime + TimeDelta::days(i)) } } @@ -1328,20 +1327,20 @@ fn test_datetime_sub_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_sub = datetime; - datetime_sub -= OldDuration::minutes(90); - assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); + datetime_sub -= TimeDelta::minutes(90); + assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); + assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); + assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); } #[test] @@ -1423,7 +1422,7 @@ fn test_min_max_setters() { assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); assert_eq!(beyond_min.with_ordinal0(200), None); assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); - assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(OldDuration::hours(1))); + assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(TimeDelta::hours(1))); assert_eq!(beyond_min.with_hour(5), None); assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); assert_eq!(beyond_min.with_second(0), Some(beyond_min)); @@ -1443,7 +1442,7 @@ fn test_min_max_setters() { assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); assert_eq!(beyond_max.with_ordinal0(200), None); assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); - assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(OldDuration::hours(1))); + assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(TimeDelta::hours(1))); assert_eq!(beyond_max.with_hour(5), None); assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); @@ -1474,8 +1473,8 @@ fn test_datetime_sub_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_sub -= OldDuration::days(1); - assert_eq!(datetime_sub, datetime - OldDuration::days(i)) + datetime_sub -= TimeDelta::days(1); + assert_eq!(datetime_sub, datetime - TimeDelta::days(i)) } } From b28c59c401e5a32fd1d09c21e8710e7ed25dc6e7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:29:56 +0100 Subject: [PATCH 671/999] Rename `Duration` to `TimeDelta` in `naive::datetime` module --- src/naive/datetime/mod.rs | 224 ++++++++++++++++++------------------ src/naive/datetime/tests.rs | 58 ++++------ 2 files changed, 137 insertions(+), 145 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 69fa52a824..300c808e1e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,7 +13,7 @@ use core::{fmt, str}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; +use crate::duration::NANOS_PER_SEC; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -21,8 +21,8 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; use crate::{ - expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeZone, Timelike, - Weekday, + expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone, + Timelike, Weekday, }; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -34,10 +34,10 @@ pub(crate) mod serde; #[cfg(test)] mod tests; -/// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` +/// The tight upper bound guarantees that a time delta with `|TimeDelta| >= 2^MAX_SECS_BITS` /// will always overflow the addition with any date and time type. /// -/// So why is this needed? `Duration::seconds(rhs)` may overflow, and we don't have +/// So why is this needed? `TimeDelta::seconds(rhs)` may overflow, and we don't have /// an alternative returning `Option` or `Result`. Thus we need some early bound to avoid /// touching that call when we are already sure that it WILL overflow... const MAX_SECS_BITS: usize = 44; @@ -633,7 +633,7 @@ impl NaiveDateTime { self.time.nanosecond() } - /// Adds given `Duration` to the current date and time. + /// Adds given `TimeDelta` to the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), /// the addition assumes that **there is no leap second ever**, @@ -647,69 +647,69 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::zero()), + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(1)), + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(1)), /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(-1)), + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1)), /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(3600 + 60)), + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60)), /// Some(hms(4, 6, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(86_400)), + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400)), /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap())); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); - /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(Duration::milliseconds(450)), + /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::milliseconds(450)), /// Some(hmsm(3, 5, 8, 430))); /// ``` /// /// Overflow returns `None`. /// /// ``` - /// # use chrono::{Duration, NaiveDate}; + /// # use chrono::{TimeDelta, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::days(1_000_000_000)), None); /// ``` /// /// Leap seconds are handled, /// but the addition assumes that it is the only leap second happened. /// /// ``` - /// # use chrono::{Duration, NaiveDate}; + /// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_add_signed(Duration::zero()), + /// assert_eq!(leap.checked_add_signed(TimeDelta::zero()), /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(-500)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(-500)), /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(500)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(500)), /// Some(hmsm(3, 5, 59, 1_800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(800)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(800)), /// Some(hmsm(3, 6, 0, 100))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(10)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(10)), /// Some(hmsm(3, 6, 9, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(-10)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(-10)), /// Some(hmsm(3, 5, 50, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::days(1)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::days(1)), /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` #[must_use] - pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); - // early checking to avoid overflow in OldDuration::seconds + // early checking to avoid overflow in TimeDelta::seconds if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { return None; } - let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); + let date = try_opt!(self.date.checked_add_signed(TimeDelta::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -790,7 +790,7 @@ impl NaiveDateTime { NaiveDateTime { date, time } } - /// Subtracts given `Duration` from the current date and time. + /// Subtracts given `TimeDelta` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), /// the subtraction assumes that **there is no leap second ever**, @@ -804,65 +804,65 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::zero()), + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(1)), + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(1)), /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(-1)), + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1)), /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(3600 + 60)), + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60)), /// Some(hms(2, 4, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(86_400)), + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400)), /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap())); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); - /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(Duration::milliseconds(670)), + /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::milliseconds(670)), /// Some(hmsm(3, 5, 6, 780))); /// ``` /// /// Overflow returns `None`. /// /// ``` - /// # use chrono::{Duration, NaiveDate}; + /// # use chrono::{TimeDelta, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::days(1_000_000_000)), None); /// ``` /// /// Leap seconds are handled, /// but the subtraction assumes that it is the only leap second happened. /// /// ``` - /// # use chrono::{Duration, NaiveDate}; + /// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_sub_signed(Duration::zero()), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()), /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(200)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(200)), /// Some(hmsm(3, 5, 59, 1_100))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(500)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(500)), /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_sub_signed(Duration::seconds(60)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::seconds(60)), /// Some(hmsm(3, 5, 0, 300))); - /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::days(1)), /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` #[must_use] - pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); - // early checking to avoid overflow in OldDuration::seconds + // early checking to avoid overflow in TimeDelta::seconds if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { return None; } - let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); + let date = try_opt!(self.date.checked_sub_signed(TimeDelta::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -924,34 +924,34 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), - /// Duration::seconds(3600 + 60 + 1)); + /// TimeDelta::seconds(3600 + 60 + 1)); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap().signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), - /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); + /// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` - /// # use chrono::{Duration, NaiveDate}; + /// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), - /// Duration::seconds(3600) + Duration::milliseconds(500)); + /// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), - /// Duration::seconds(3600) - Duration::milliseconds(500)); + /// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500)); /// ``` #[must_use] - pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { expect!( self.date .signed_duration_since(rhs.date) @@ -1606,7 +1606,7 @@ impl Timelike for NaiveDateTime { } } -/// Add `chrono::Duration` to `NaiveDateTime`. +/// Add `TimeDelta` to `NaiveDateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1620,50 +1620,50 @@ impl Timelike for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); -/// assert_eq!(hms(3, 5, 7) + Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(-1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(3600 + 60), hms(4, 6, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(86_400), +/// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(-1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(3600 + 60), hms(4, 6, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(86_400), /// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()); -/// assert_eq!(hms(3, 5, 7) + Duration::days(365), +/// assert_eq!(hms(3, 5, 7) + TimeDelta::days(365), /// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap()); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); -/// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); +/// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::milliseconds(450), hmsm(3, 5, 8, 430)); /// ``` /// /// Leap seconds are handled, /// but the addition assumes that it is the only leap second happened. /// /// ``` -/// # use chrono::{Duration, NaiveDate}; +/// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), +/// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::milliseconds(-500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::milliseconds(500), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::seconds(10), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::seconds(-10), hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::days(1), /// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling -impl Add for NaiveDateTime { +impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] - fn add(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") } } @@ -1682,13 +1682,13 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: Duration) -> NaiveDateTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") } } -/// Add-assign `chrono::Duration` to `NaiveDateTime`. +/// Add-assign `TimeDelta` to `NaiveDateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1698,9 +1698,9 @@ impl Add for NaiveDateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. -impl AddAssign for NaiveDateTime { +impl AddAssign for NaiveDateTime { #[inline] - fn add_assign(&mut self, rhs: OldDuration) { + fn add_assign(&mut self, rhs: TimeDelta) { *self = self.add(rhs); } } @@ -1785,9 +1785,9 @@ impl Add for NaiveDateTime { } } -/// Subtract `chrono::Duration` from `NaiveDateTime`. +/// Subtract `TimeDelta` from `NaiveDateTime`. /// -/// This is the same as the addition with a negated `Duration`. +/// This is the same as the addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1801,48 +1801,48 @@ impl Add for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); -/// assert_eq!(hms(3, 5, 7) - Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(-1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(3600 + 60), hms(2, 4, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(86_400), +/// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(-1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(3600 + 60), hms(2, 4, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(86_400), /// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()); -/// assert_eq!(hms(3, 5, 7) - Duration::days(365), +/// assert_eq!(hms(3, 5, 7) - TimeDelta::days(365), /// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap()); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); -/// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); +/// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::milliseconds(670), hmsm(3, 5, 6, 780)); /// ``` /// /// Leap seconds are handled, /// but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # use chrono::{Duration, NaiveDate}; +/// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), +/// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::milliseconds(200), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::milliseconds(500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::seconds(60), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::days(1), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling -impl Sub for NaiveDateTime { +impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") } } @@ -1861,15 +1861,15 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: Duration) -> NaiveDateTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") } } -/// Subtract-assign `chrono::Duration` from `NaiveDateTime`. +/// Subtract-assign `TimeDelta` from `NaiveDateTime`. /// -/// This is the same as the addition with a negated `Duration`. +/// This is the same as the addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case @@ -1879,9 +1879,9 @@ impl Sub for NaiveDateTime { /// /// Panics if the resulting date would be out of range. /// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. -impl SubAssign for NaiveDateTime { +impl SubAssign for NaiveDateTime { #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { + fn sub_assign(&mut self, rhs: TimeDelta) { *self = self.sub(rhs); } } @@ -1968,36 +1968,36 @@ impl Sub for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); -/// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), Duration::seconds(3600 + 60 + 1)); +/// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), TimeDelta::seconds(3600 + 60 + 1)); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), -/// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); +/// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that no other leap /// seconds happened. /// /// ``` -/// # use chrono::{Duration, NaiveDate}; +/// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), -/// Duration::seconds(3600) + Duration::milliseconds(500)); +/// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500)); /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, -/// Duration::seconds(3600) - Duration::milliseconds(500)); +/// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500)); /// ``` impl Sub for NaiveDateTime { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: NaiveDateTime) -> OldDuration { + fn sub(self, rhs: NaiveDateTime) -> TimeDelta { self.signed_duration_since(rhs) } } diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 6375a9d351..4d36208c39 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,6 +1,5 @@ use super::NaiveDateTime; -use crate::duration::Duration as OldDuration; -use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, Utc}; +use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc}; #[test] fn test_datetime_from_timestamp_millis() { @@ -147,7 +146,7 @@ fn test_datetime_from_timestamp() { fn test_datetime_add() { fn check( (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: OldDuration, + rhs: TimeDelta, result: Option<(i32, u32, u32, u32, u32, u32)>, ) { let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); @@ -158,16 +157,12 @@ fn test_datetime_add() { assert_eq!(lhs.checked_sub_signed(-rhs), sum); } - check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); - check( - (2014, 5, 6, 7, 8, 9), - OldDuration::seconds(-(3600 + 60 + 1)), - Some((2014, 5, 6, 6, 7, 8)), - ); - check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. @@ -177,17 +172,17 @@ fn test_datetime_add() { check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + OldDuration::seconds(86399), + max_days_from_year_0 + TimeDelta::seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + OldDuration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), OldDuration::max_value(), None); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + TimeDelta::seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), None); let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - OldDuration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), OldDuration::min_value(), None); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - TimeDelta::seconds(1), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), None); } #[test] @@ -195,25 +190,22 @@ fn test_datetime_sub() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let since = NaiveDateTime::signed_duration_since; - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), - OldDuration::zero() - ); + assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero()); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - OldDuration::seconds(1) + TimeDelta::seconds(1) ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - OldDuration::seconds(-1) + TimeDelta::seconds(-1) ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - OldDuration::seconds(86399) + TimeDelta::seconds(86399) ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - OldDuration::seconds(999_999_999) + TimeDelta::seconds(999_999_999) ); } @@ -222,9 +214,9 @@ fn test_datetime_addassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += OldDuration::minutes(10_000_000); + date += TimeDelta::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += OldDuration::days(10); + date += TimeDelta::days(10); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); } @@ -233,9 +225,9 @@ fn test_datetime_subassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= OldDuration::minutes(10_000_000); + date -= TimeDelta::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= OldDuration::days(10); + date -= TimeDelta::days(10); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } @@ -427,7 +419,7 @@ fn test_datetime_add_sub_invariant() { // issue #37 let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let t = -946684799990000; - let time = base + OldDuration::microseconds(t); + let time = base + TimeDelta::microseconds(t); assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); } @@ -453,13 +445,13 @@ fn test_nanosecond_range() { // Just beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + OldDuration::milliseconds(300); + let beyond_max = parsed + TimeDelta::milliseconds(300); assert!(beyond_max.timestamp_nanos_opt().is_none()); // Far beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + OldDuration::days(365); + let beyond_max = parsed + TimeDelta::days(365); assert!(beyond_max.timestamp_nanos_opt().is_none()); } From 04050b85f1af7ed429b8eba1846226c5a396b681 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:33:11 +0100 Subject: [PATCH 672/999] Rename `Duration` to `TimeDelta` in `naive::time` module --- src/naive/time/mod.rs | 221 ++++++++++++++++++++-------------------- src/naive/time/tests.rs | 69 +++++++------ 2 files changed, 144 insertions(+), 146 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index b47639ce0d..0bdb76522c 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -12,7 +12,6 @@ use core::{fmt, str}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::Duration as OldDuration; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ @@ -20,7 +19,7 @@ use crate::format::{ Parsed, StrftimeItems, }; use crate::{expect, try_opt}; -use crate::{FixedOffset, Timelike}; +use crate::{FixedOffset, TimeDelta, Timelike}; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; @@ -98,7 +97,7 @@ mod tests; /// In reality, of course, leap seconds are separated by at least 6 months. /// We will also use some intuitive concise notations for the explanation. /// -/// `Time + Duration` +/// `Time + TimeDelta` /// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): /// /// - `03:00:00 + 1s = 03:00:01`. @@ -111,7 +110,7 @@ mod tests; /// - `03:00:60 + 61s = 03:02:00`. /// - `03:00:60.1 + 0.8s = 03:00:60.9`. /// -/// `Time - Duration` +/// `Time - TimeDelta` /// (short for [`NaiveTime::overflowing_sub_signed`](#method.overflowing_sub_signed)): /// /// - `03:00:00 - 1s = 02:59:59`. @@ -140,21 +139,21 @@ mod tests; /// /// In general, /// -/// - `Time + Duration` unconditionally equals to `Duration + Time`. +/// - `Time + TimeDelta` unconditionally equals to `TimeDelta + Time`. /// -/// - `Time - Duration` unconditionally equals to `Time + (-Duration)`. +/// - `Time - TimeDelta` unconditionally equals to `Time + (-TimeDelta)`. /// /// - `Time1 - Time2` unconditionally equals to `-(Time2 - Time1)`. /// /// - Associativity does not generally hold, because -/// `(Time + Duration1) - Duration2` no longer equals to `Time + (Duration1 - Duration2)` +/// `(Time + TimeDelta1) - TimeDelta2` no longer equals to `Time + (TimeDelta1 - TimeDelta2)` /// for two positive durations. /// -/// - As a special case, `(Time + Duration) - Duration` also does not equal to `Time`. +/// - As a special case, `(Time + TimeDelta) - TimeDelta` also does not equal to `Time`. /// /// - If you can assume that all durations have the same sign, however, /// then the associativity holds: -/// `(Time + Duration1) + Duration2` equals to `Time + (Duration1 + Duration2)` +/// `(Time + TimeDelta1) + TimeDelta2` equals to `Time + (TimeDelta1 + TimeDelta2)` /// for two positive durations. /// /// ## Reading And Writing Leap Seconds @@ -564,25 +563,25 @@ impl NaiveTime { parsed.to_naive_time().map(|t| (t, remainder)) } - /// Adds given `Duration` 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. /// /// # Example /// /// ``` - /// use chrono::{Duration, NaiveTime}; + /// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(11)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(11)), /// (from_hms(14, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(23)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(23)), /// (from_hms(2, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)), /// (from_hms(20, 4, 5), -86_400)); /// ``` #[must_use] - pub const fn overflowing_add_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + pub const fn overflowing_add_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { let mut secs = self.secs as i64; let mut frac = self.frac as i32; let secs_to_add = rhs.num_seconds(); @@ -620,32 +619,32 @@ impl NaiveTime { (NaiveTime { secs: secs_in_day as u32, frac: frac as u32 }, remaining) } - /// Subtracts given `Duration` 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. /// /// # Example /// /// ``` - /// use chrono::{Duration, NaiveTime}; + /// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(2)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(2)), /// (from_hms(1, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(17)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(17)), /// (from_hms(10, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(-22)), + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(-22)), /// (from_hms(1, 4, 5), -86_400)); /// ``` #[inline] #[must_use] - pub const fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + pub const fn overflowing_sub_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { let (time, rhs) = self.overflowing_add_signed(rhs.neg()); (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) } /// Subtracts another `NaiveTime` from the current time. - /// Returns a `Duration` within +/- 1 day. + /// Returns a `TimeDelta` within +/- 1 day. /// This does not overflow or underflow at all. /// /// As a part of Chrono's [leap second handling](#leap-second-handling), @@ -657,49 +656,49 @@ impl NaiveTime { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveTime}; + /// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let since = NaiveTime::signed_duration_since; /// /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), - /// Duration::zero()); + /// TimeDelta::zero()); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), - /// Duration::milliseconds(25)); + /// TimeDelta::milliseconds(25)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), - /// Duration::milliseconds(975)); + /// TimeDelta::milliseconds(975)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), - /// Duration::seconds(7)); + /// TimeDelta::seconds(7)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), - /// Duration::seconds(5 * 60)); + /// TimeDelta::seconds(5 * 60)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), - /// Duration::seconds(3 * 3600)); + /// TimeDelta::seconds(3 * 3600)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), - /// Duration::seconds(-3600)); + /// TimeDelta::seconds(-3600)); /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); + /// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` - /// # use chrono::{Duration, NaiveTime}; + /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// # let since = NaiveTime::signed_duration_since; /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), - /// Duration::seconds(1)); + /// TimeDelta::seconds(1)); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), - /// Duration::milliseconds(1500)); + /// TimeDelta::milliseconds(1500)); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), - /// Duration::seconds(60)); + /// TimeDelta::seconds(60)); /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(1)); + /// TimeDelta::seconds(1)); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(61)); + /// TimeDelta::seconds(61)); /// ``` #[must_use] - pub const fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveTime) -> TimeDelta { // | | :leap| | | | | | | :leap| | // | | : | | | | | | | : | | // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- @@ -723,7 +722,7 @@ impl NaiveTime { let secs_from_frac = frac.div_euclid(1_000_000_000); let frac = frac.rem_euclid(1_000_000_000) as u32; - expect!(OldDuration::new(secs + secs_from_frac, frac), "must be in range") + expect!(TimeDelta::new(secs + secs_from_frac, frac), "must be in range") } /// Adds given `FixedOffset` to the current time, and returns the number of days that should be @@ -1081,7 +1080,7 @@ impl Timelike for NaiveTime { } } -/// Add `chrono::Duration` to `NaiveTime`. +/// Add `TimeDelta` to `NaiveTime`. /// /// This wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. @@ -1093,62 +1092,62 @@ impl Timelike for NaiveTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveTime}; +/// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(1), from_hmsm(3, 5, 8, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::milliseconds(80), from_hmsm(3, 5, 7, 80)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(280), from_hmsm(3, 5, 8, 230)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(-980), from_hmsm(3, 5, 6, 970)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(1), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-1), from_hmsm(3, 5, 6, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(280), from_hmsm(3, 5, 8, 230)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(-980), from_hmsm(3, 5, 6, 970)); /// ``` /// /// The addition wraps around. /// /// ``` -/// # use chrono::{Duration, NaiveTime}; +/// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::days(800), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::days(800), from_hmsm(3, 5, 7, 0)); /// ``` /// /// Leap seconds are handled, but the addition assumes that it is the only leap second happened. /// /// ``` -/// # use chrono::{Duration, NaiveTime}; +/// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), from_hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), from_hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), from_hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), from_hmsm(3, 5, 59, 300)); +/// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::milliseconds(-500), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::seconds(10), from_hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::seconds(-10), from_hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::days(1), from_hmsm(3, 5, 59, 300)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling -impl Add for NaiveTime { +impl Add for NaiveTime { type Output = NaiveTime; #[inline] - fn add(self, rhs: OldDuration) -> NaiveTime { + fn add(self, rhs: TimeDelta) -> NaiveTime { self.overflowing_add_signed(rhs).0 } } -/// Add-assign `chrono::Duration` to `NaiveTime`. +/// Add-assign `TimeDelta` to `NaiveTime`. /// /// This wraps around and never overflows or underflows. /// In particular the addition ignores integral number of days. -impl AddAssign for NaiveTime { +impl AddAssign for NaiveTime { #[inline] - fn add_assign(&mut self, rhs: OldDuration) { + fn add_assign(&mut self, rhs: TimeDelta) { *self = self.add(rhs); } } @@ -1163,10 +1162,10 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: Duration) -> NaiveTime { // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid - // overflow during the conversion to `chrono::Duration`. + // overflow during the conversion to `TimeDelta`. // But we limit to double that just in case `self` is a leap-second. let secs = rhs.as_secs() % (2 * 24 * 60 * 60); - let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap(); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); self.overflowing_add_signed(d).0 } } @@ -1195,11 +1194,11 @@ impl Add for NaiveTime { } } -/// Subtract `chrono::Duration` from `NaiveTime`. +/// Subtract `TimeDelta` from `NaiveTime`. /// /// This wraps around and never overflows or underflows. /// In particular the subtraction ignores integral number of days. -/// This is the same as addition with a negated `Duration`. +/// This is the same as addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap /// second ever**, except when the `NaiveTime` itself represents a leap second in which case the @@ -1208,57 +1207,57 @@ impl Add for NaiveTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveTime}; +/// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::milliseconds(80), from_hmsm(3, 5, 6, 920)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) - Duration::milliseconds(280), from_hmsm(3, 5, 7, 670)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(1), from_hmsm(3, 5, 6, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) - TimeDelta::milliseconds(280), from_hmsm(3, 5, 7, 670)); /// ``` /// /// The subtraction wraps around. /// /// ``` -/// # use chrono::{Duration, NaiveTime}; +/// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::days(800), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::days(800), from_hmsm(3, 5, 7, 0)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. /// /// ``` -/// # use chrono::{Duration, NaiveTime}; +/// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), from_hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), from_hmsm(3, 6, 0, 300)); +/// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::seconds(60), from_hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::days(1), from_hmsm(3, 6, 0, 300)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling -impl Sub for NaiveTime { +impl Sub for NaiveTime { type Output = NaiveTime; #[inline] - fn sub(self, rhs: OldDuration) -> NaiveTime { + fn sub(self, rhs: TimeDelta) -> NaiveTime { self.overflowing_sub_signed(rhs).0 } } -/// Subtract-assign `chrono::Duration` from `NaiveTime`. +/// Subtract-assign `TimeDelta` from `NaiveTime`. /// /// This wraps around and never overflows or underflows. /// In particular the subtraction ignores integral number of days. -impl SubAssign for NaiveTime { +impl SubAssign for NaiveTime { #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { + fn sub_assign(&mut self, rhs: TimeDelta) { *self = self.sub(rhs); } } @@ -1273,10 +1272,10 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: Duration) -> NaiveTime { // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid - // overflow during the conversion to `chrono::Duration`. + // overflow during the conversion to `TimeDelta`. // But we limit to double that just in case `self` is a leap-second. let secs = rhs.as_secs() % (2 * 24 * 60 * 60); - let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap(); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); self.overflowing_sub_signed(d).0 } } @@ -1306,7 +1305,7 @@ impl Sub for NaiveTime { } /// Subtracts another `NaiveTime` from the current time. -/// Returns a `Duration` within +/- 1 day. +/// Returns a `TimeDelta` within +/- 1 day. /// This does not overflow or underflow at all. /// /// As a part of Chrono's [leap second handling](#leap-second-handling), @@ -1321,40 +1320,40 @@ impl Sub for NaiveTime { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveTime}; +/// use chrono::{TimeDelta, NaiveTime}; /// /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), Duration::zero()); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), Duration::milliseconds(25)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), Duration::milliseconds(975)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), Duration::seconds(7)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), Duration::seconds(5 * 60)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), Duration::seconds(3 * 3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), Duration::seconds(-3600)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), TimeDelta::milliseconds(25)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), TimeDelta::milliseconds(975)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), TimeDelta::seconds(7)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), TimeDelta::seconds(5 * 60)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), TimeDelta::seconds(3 * 3600)); +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), TimeDelta::seconds(-3600)); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); +/// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that /// there were no other leap seconds happened. /// /// ``` -/// # use chrono::{Duration, NaiveTime}; +/// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), -/// Duration::milliseconds(1500)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), Duration::seconds(60)); -/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), Duration::seconds(1)); +/// TimeDelta::milliseconds(1500)); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::seconds(60)); +/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::seconds(1)); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), -/// Duration::seconds(61)); +/// TimeDelta::seconds(61)); /// ``` impl Sub for NaiveTime { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: NaiveTime) -> OldDuration { + fn sub(self, rhs: NaiveTime) -> TimeDelta { self.signed_duration_since(rhs) } } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 784a8390b8..a991117a7f 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,6 +1,5 @@ use super::NaiveTime; -use crate::duration::Duration as OldDuration; -use crate::{FixedOffset, Timelike}; +use crate::{FixedOffset, TimeDelta, Timelike}; #[test] fn test_time_from_hms_milli() { @@ -94,23 +93,23 @@ fn test_time_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 59, 900), OldDuration::zero(), hmsm(3, 5, 59, 900)); - check!(hmsm(3, 5, 59, 900), OldDuration::milliseconds(100), hmsm(3, 6, 0, 0)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 58, 500)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 59, 500)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 59, 1_200)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 59, 1_400)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(800), hmsm(3, 6, 0, 100)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(1800), hmsm(3, 6, 1, 100)); - check!(hmsm(3, 5, 59, 900), OldDuration::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap - check!(hmsm(3, 5, 59, 900), OldDuration::seconds(-86399), hmsm(3, 6, 0, 900)); - check!(hmsm(3, 5, 59, 900), OldDuration::days(12345), hmsm(3, 5, 59, 900)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::days(1), hmsm(3, 5, 59, 300)); - check!(hmsm(3, 5, 59, 1_300), OldDuration::days(-1), hmsm(3, 6, 0, 300)); + check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::milliseconds(100), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-1800), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-800), hmsm(3, 5, 59, 500)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-100), hmsm(3, 5, 59, 1_200)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(100), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(1800), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(-86399), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::days(12345), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(1), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(-1), hmsm(3, 6, 0, 300)); // regression tests for #37 - check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-9990), hmsm(23, 59, 50, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-990), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-9990), hmsm(23, 59, 50, 10)); } #[test] @@ -118,25 +117,25 @@ fn test_time_overflowing_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(11)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(11)), (hmsm(14, 4, 5, 678), 0) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(23)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(23)), (hmsm(2, 4, 5, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(OldDuration::hours(-7)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(-7)), (hmsm(20, 4, 5, 678), -86_400) ); // overflowing_add_signed with leap seconds may be counter-intuitive assert_eq!( - hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(1)), + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::days(1)), (hmsm(3, 4, 59, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(-1)), + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::days(-1)), (hmsm(3, 5, 0, 678), -86_400) ); } @@ -145,9 +144,9 @@ fn test_time_overflowing_add() { fn test_time_addassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time += OldDuration::hours(10); + time += TimeDelta::hours(10); assert_eq!(time, hms(22, 12, 12)); - time += OldDuration::hours(10); + time += TimeDelta::hours(10); assert_eq!(time, hms(8, 12, 12)); } @@ -155,9 +154,9 @@ fn test_time_addassignment() { fn test_time_subassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time -= OldDuration::hours(10); + time -= TimeDelta::hours(10); assert_eq!(time, hms(2, 12, 12)); - time -= OldDuration::hours(10); + time -= TimeDelta::hours(10); assert_eq!(time, hms(16, 12, 12)); } @@ -173,25 +172,25 @@ fn test_time_sub() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), OldDuration::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), OldDuration::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), OldDuration::seconds(3600 + 60 + 1)); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero()); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::milliseconds(300)); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::seconds(3600 + 60 + 1)); check!( hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 300), - OldDuration::seconds(3600 + 60) + OldDuration::milliseconds(900) + TimeDelta::seconds(3600 + 60) + TimeDelta::milliseconds(900) ); // treats the leap second as if it coincides with the prior non-leap second, // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), OldDuration::milliseconds(400)); - //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); - //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::milliseconds(400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::milliseconds(1400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::milliseconds(1400)); // additional equality: `time1 + duration = time2` is equivalent to // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); - //assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); + assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::milliseconds(400), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::milliseconds(400), hmsm(3, 5, 7, 200)); } #[test] From 20e9a73aa365bf42587a28775ef45bb3f2ad71b0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:36:05 +0100 Subject: [PATCH 673/999] Rename `Duration` to `TimeDelta` in `naive::date` module --- src/naive/date.rs | 244 +++++++++++++++++++++++----------------------- 1 file changed, 120 insertions(+), 124 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index c4fe5209d1..190a6bac60 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -16,7 +16,6 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] use pure_rust_locales::Locale; -use crate::duration::Duration as OldDuration; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ @@ -26,7 +25,7 @@ use crate::format::{ use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; -use crate::{Datelike, Weekday}; +use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -126,10 +125,10 @@ impl NaiveWeek { /// A duration in calendar days. /// -/// This is useful because when using `Duration` it is possible -/// that adding `Duration::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 `Duration::days(n)` and `Days::new(n)` are equivalent. +/// This is useful because when using `TimeDelta` it is possible 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, Ord)] pub struct Days(pub(crate) u64); @@ -1134,7 +1133,7 @@ impl NaiveDate { } } - /// Adds the number of whole days in the given `Duration` to the current date. + /// Adds the number of whole days in the given `TimeDelta` to the current date. /// /// # Errors /// @@ -1143,19 +1142,19 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(d.checked_add_signed(Duration::days(40)), + /// assert_eq!(d.checked_add_signed(TimeDelta::days(40)), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); - /// assert_eq!(d.checked_add_signed(Duration::days(-40)), + /// assert_eq!(d.checked_add_signed(TimeDelta::days(-40)), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); - /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::days(1_000_000_000)), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::days(-1_000_000_000)), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::days(1)), None); /// ``` #[must_use] - pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { let days = rhs.num_days(); if days < i32::MIN as i64 || days > i32::MAX as i64 { return None; @@ -1163,7 +1162,7 @@ impl NaiveDate { self.add_days(days as i32) } - /// Subtracts the number of whole days in the given `Duration` from the current date. + /// Subtracts the number of whole days in the given `TimeDelta` from the current date. /// /// # Errors /// @@ -1172,19 +1171,19 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(d.checked_sub_signed(Duration::days(40)), + /// assert_eq!(d.checked_sub_signed(TimeDelta::days(40)), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); - /// assert_eq!(d.checked_sub_signed(Duration::days(-40)), + /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-40)), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); - /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::days(1_000_000_000)), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-1_000_000_000)), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::days(1)), None); /// ``` #[must_use] - pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { let days = -rhs.num_days(); if days < i32::MIN as i64 || days > i32::MAX as i64 { return None; @@ -1193,38 +1192,36 @@ impl NaiveDate { } /// Subtracts another `NaiveDate` from the current date. - /// Returns a `Duration` of integral numbers. + /// Returns a `TimeDelta` of integral numbers. /// /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. + /// as all possible output fits in the range of `TimeDelta`. /// /// # Example /// /// ``` - /// use chrono::{Duration, NaiveDate}; + /// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let since = NaiveDate::signed_duration_since; /// - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), Duration::zero()); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), Duration::days(1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), Duration::days(-1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), Duration::days(100)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), Duration::days(365)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), Duration::days(365*4 + 1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), TimeDelta::days(1)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::days(-1)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), TimeDelta::days(100)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), TimeDelta::days(365)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), TimeDelta::days(365*4 + 1)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), TimeDelta::days(365*400 + 97)); /// ``` #[must_use] - pub const fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { let year1 = self.year(); let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal()) as i64; let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal()) as i64; - OldDuration::days( - (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2), - ) + TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2)) } /// Returns the number of whole years from the given `base` until `self`. @@ -1862,10 +1859,10 @@ impl Datelike for NaiveDate { } } -/// Add `chrono::Duration` to `NaiveDate`. +/// Add `TimeDelta` to `NaiveDate`. /// -/// This discards the fractional days in `Duration`, rounding to the closest integral number of days -/// towards `Duration::zero()`. +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. /// /// # Panics /// @@ -1875,42 +1872,42 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(-1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(364), from_ymd(2014, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*4 + 1), from_ymd(2018, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*400 + 97), from_ymd(2414, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(1), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(-1), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(364), from_ymd(2014, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*4 + 1), from_ymd(2018, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*400 + 97), from_ymd(2414, 1, 1)); /// ``` /// /// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed -impl Add for NaiveDate { +impl Add for NaiveDate { type Output = NaiveDate; #[inline] - fn add(self, rhs: OldDuration) -> NaiveDate { - self.checked_add_signed(rhs).expect("`NaiveDate + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> NaiveDate { + self.checked_add_signed(rhs).expect("`NaiveDate + TimeDelta` overflowed") } } -/// Add-assign of `chrono::Duration` to `NaiveDate`. +/// Add-assign of `TimeDelta` to `NaiveDate`. /// -/// This discards the fractional days in `Duration`, rounding to the closest integral number of days -/// towards `Duration::zero()`. +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of days +/// towards `TimeDelta::zero()`. /// /// # Panics /// /// Panics if the resulting date would be out of range. /// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. -impl AddAssign for NaiveDate { +impl AddAssign for NaiveDate { #[inline] - fn add_assign(&mut self, rhs: OldDuration) { + fn add_assign(&mut self, rhs: TimeDelta) { *self = self.add(rhs); } } @@ -2004,11 +2001,11 @@ impl Sub for NaiveDate { } } -/// Subtract `chrono::Duration` from `NaiveDate`. +/// Subtract `TimeDelta` from `NaiveDate`. /// -/// This discards the fractional days in `Duration`, rounding to the closest integral number of days -/// towards `Duration::zero()`. -/// It is the same as the addition with a negated `Duration`. +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. /// /// # Panics /// @@ -2018,52 +2015,52 @@ impl Sub for NaiveDate { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(-1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(364), from_ymd(2013, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*4 + 1), from_ymd(2010, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*400 + 97), from_ymd(1614, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(1), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(-1), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(364), from_ymd(2013, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*4 + 1), from_ymd(2010, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*400 + 97), from_ymd(1614, 1, 1)); /// ``` /// /// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed -impl Sub for NaiveDate { +impl Sub for NaiveDate { type Output = NaiveDate; #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDate { - self.checked_sub_signed(rhs).expect("`NaiveDate - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> NaiveDate { + self.checked_sub_signed(rhs).expect("`NaiveDate - TimeDelta` overflowed") } } -/// Subtract-assign `chrono::Duration` from `NaiveDate`. +/// Subtract-assign `TimeDelta` from `NaiveDate`. /// -/// This discards the fractional days in `Duration`, rounding to the closest integral number of days -/// towards `Duration::zero()`. -/// It is the same as the addition with a negated `Duration`. +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. /// /// # Panics /// /// Panics if the resulting date would be out of range. /// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. -impl SubAssign for NaiveDate { +impl SubAssign for NaiveDate { #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { + fn sub_assign(&mut self, rhs: TimeDelta) { *self = self.sub(rhs); } } /// Subtracts another `NaiveDate` from the current date. -/// Returns a `Duration` of integral numbers. +/// Returns a `TimeDelta` of integral numbers. /// /// This does not overflow or underflow at all, -/// as all possible output fits in the range of `Duration`. +/// as all possible output fits in the range of `TimeDelta`. /// /// The implementation is a wrapper around /// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). @@ -2071,23 +2068,23 @@ impl SubAssign for NaiveDate { /// # Example /// /// ``` -/// use chrono::{Duration, NaiveDate}; +/// use chrono::{TimeDelta, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), Duration::zero()); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), Duration::days(1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), Duration::days(-1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), Duration::days(100)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), Duration::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), Duration::days(365*4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), Duration::days(365*400 + 97)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), TimeDelta::zero()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), TimeDelta::days(1)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::days(-1)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::days(100)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::days(365)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), TimeDelta::days(365*4 + 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), TimeDelta::days(365*400 + 97)); /// ``` impl Sub for NaiveDate { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: NaiveDate) -> OldDuration { + fn sub(self, rhs: NaiveDate) -> TimeDelta { self.signed_duration_since(rhs) } } @@ -2145,7 +2142,7 @@ impl Iterator for NaiveDateWeeksIterator { fn next(&mut self) -> Option { let current = self.value; - self.value = current.checked_add_signed(OldDuration::weeks(1))?; + self.value = current.checked_add_signed(TimeDelta::weeks(1))?; Some(current) } @@ -2160,7 +2157,7 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { let current = self.value; - self.value = current.checked_sub_signed(OldDuration::weeks(1))?; + self.value = current.checked_sub_signed(TimeDelta::weeks(1))?; Some(current) } } @@ -2479,9 +2476,8 @@ mod serde { #[cfg(test)] mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; - use crate::duration::Duration; use crate::naive::internals::YearFlags; - use crate::{Datelike, Weekday}; + use crate::{Datelike, TimeDelta, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, // we use a separate run-time test. @@ -2502,7 +2498,7 @@ mod tests { ); // let's also check that the entire range do not exceed 2^44 seconds - // (sometimes used for bounding `Duration` against overflow) + // (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!( @@ -2965,52 +2961,52 @@ mod tests { #[test] fn test_date_add() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: Duration, ymd: Option<(i32, u32, u32)>) { + fn check((y1, m1, d1): (i32, u32, u32), rhs: TimeDelta, ymd: Option<(i32, u32, u32)>) { let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); assert_eq!(lhs.checked_add_signed(rhs), sum); assert_eq!(lhs.checked_sub_signed(-rhs), sum); } - check((2014, 1, 1), Duration::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::seconds(86399), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::seconds(86399), Some((2014, 1, 1))); // always round towards zero - check((2014, 1, 1), Duration::seconds(-86399), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::days(1), Some((2014, 1, 2))); - check((2014, 1, 1), Duration::days(-1), Some((2013, 12, 31))); - check((2014, 1, 1), Duration::days(364), Some((2014, 12, 31))); - check((2014, 1, 1), Duration::days(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Duration::days(365 * 400 + 97), Some((2414, 1, 1))); + check((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::days(1), Some((2014, 1, 2))); + check((2014, 1, 1), TimeDelta::days(-1), Some((2013, 12, 31))); + check((2014, 1, 1), TimeDelta::days(364), Some((2014, 12, 31))); + check((2014, 1, 1), TimeDelta::days(365 * 4 + 1), Some((2018, 1, 1))); + check((2014, 1, 1), TimeDelta::days(365 * 400 + 97), Some((2414, 1, 1))); - check((-7, 1, 1), Duration::days(365 * 12 + 3), Some((5, 1, 1))); + check((-7, 1, 1), TimeDelta::days(365 * 12 + 3), Some((5, 1, 1))); // overflow check - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); - check((0, 1, 1), Duration::max_value(), None); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); - check((0, 1, 1), Duration::min_value(), None); + check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); + check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); + check((0, 1, 1), TimeDelta::max_value(), None); + check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); + check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); + check((0, 1, 1), TimeDelta::min_value(), None); } #[test] fn test_date_sub() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Duration) { + fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: TimeDelta) { let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); assert_eq!(lhs.signed_duration_since(rhs), diff); assert_eq!(rhs.signed_duration_since(lhs), -diff); } - check((2014, 1, 1), (2014, 1, 1), Duration::zero()); - check((2014, 1, 2), (2014, 1, 1), Duration::days(1)); - check((2014, 12, 31), (2014, 1, 1), Duration::days(364)); - check((2015, 1, 3), (2014, 1, 1), Duration::days(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Duration::days(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Duration::days(365 * 400 + 97)); + check((2014, 1, 1), (2014, 1, 1), TimeDelta::zero()); + check((2014, 1, 2), (2014, 1, 1), TimeDelta::days(1)); + check((2014, 12, 31), (2014, 1, 1), TimeDelta::days(364)); + check((2015, 1, 3), (2014, 1, 1), TimeDelta::days(365 + 2)); + check((2018, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 4 + 1)); + check((2414, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 400 + 97)); - check((MAX_YEAR, 12, 31), (0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64)); - check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64)); + check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64)); + check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64)); } #[test] @@ -3062,9 +3058,9 @@ mod tests { fn test_date_addassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 1); - date += Duration::days(10); + date += TimeDelta::days(10); assert_eq!(date, ymd(2016, 10, 11)); - date += Duration::days(30); + date += TimeDelta::days(30); assert_eq!(date, ymd(2016, 11, 10)); } @@ -3072,9 +3068,9 @@ mod tests { fn test_date_subassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 11); - date -= Duration::days(10); + date -= TimeDelta::days(10); assert_eq!(date, ymd(2016, 10, 1)); - date -= Duration::days(2); + date -= TimeDelta::days(2); assert_eq!(date, ymd(2016, 9, 29)); } From 3bd9a90e914527c8b4c7666a9f8ab4d7ed138a76 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:55:18 +0100 Subject: [PATCH 674/999] Rename `Duration` to `TimeDelta` in `round` module --- src/round.rs | 174 +++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 89 deletions(-) diff --git a/src/round.rs b/src/round.rs index ab16fbb89a..281816330e 100644 --- a/src/round.rs +++ b/src/round.rs @@ -1,13 +1,9 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. -//! Functionality for rounding or truncating a `DateTime` by a `Duration`. +//! Functionality for rounding or truncating a `DateTime` by a `TimeDelta`. -use crate::datetime::DateTime; -use crate::duration::Duration; -use crate::NaiveDateTime; -use crate::TimeZone; -use crate::Timelike; +use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike}; use core::cmp::Ordering; use core::fmt; use core::marker::Sized; @@ -48,7 +44,7 @@ pub trait SubsecRound { impl SubsecRound for T where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { fn round_subsecs(self, digits: u16) -> T { let span = span_for_digits(digits); @@ -56,9 +52,9 @@ where if delta_down > 0 { let delta_up = span - delta_down; if delta_up <= delta_down { - self + Duration::nanoseconds(delta_up.into()) + self + TimeDelta::nanoseconds(delta_up.into()) } else { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } } else { self // unchanged @@ -69,7 +65,7 @@ where let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } else { self // unchanged } @@ -93,13 +89,13 @@ const fn span_for_digits(digits: u16) -> u32 { } } -/// Extension trait for rounding or truncating a DateTime by a Duration. +/// Extension trait for rounding or truncating a DateTime by a TimeDelta. /// /// # Limitations -/// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and +/// Both rounding and truncating are done via [`TimeDelta::num_nanoseconds`] and /// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the -/// `Duration` or the `DateTime` are too big to represented as nanoseconds. They -/// will also fail if the `Duration` is bigger than the timestamp. +/// `TimeDelta` or the `DateTime` are too big to represented as nanoseconds. They +/// will also fail if the `TimeDelta` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating #[cfg(feature = "std")] @@ -109,49 +105,49 @@ pub trait DurationRound: Sized { #[cfg(not(feature = "std"))] type Err: fmt::Debug + fmt::Display; - /// Return a copy rounded by Duration. + /// Return a copy rounded by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( - /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_round(Duration::days(1)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` - fn duration_round(self, duration: Duration) -> Result; + fn duration_round(self, duration: TimeDelta) -> Result; - /// Return a copy truncated by Duration. + /// Return a copy truncated by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( - /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` - fn duration_trunc(self, duration: Duration) -> Result; + fn duration_trunc(self, duration: TimeDelta) -> Result; } impl DurationRound for DateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: TimeDelta) -> Result { duration_round(self.naive_local(), self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self.naive_local(), self, duration) } } @@ -159,11 +155,11 @@ impl DurationRound for DateTime { impl DurationRound for NaiveDateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: TimeDelta) -> Result { duration_round(self, self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self, self, duration) } } @@ -171,10 +167,10 @@ impl DurationRound for NaiveDateTime { fn duration_round( naive: NaiveDateTime, original: T, - duration: Duration, + duration: TimeDelta, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { if span < 0 { @@ -197,9 +193,9 @@ where (span - delta_down, delta_down) }; if delta_up <= delta_down { - Ok(original + Duration::nanoseconds(delta_up)) + Ok(original + TimeDelta::nanoseconds(delta_up)) } else { - Ok(original - Duration::nanoseconds(delta_down)) + Ok(original - TimeDelta::nanoseconds(delta_down)) } } } else { @@ -210,10 +206,10 @@ where fn duration_trunc( naive: NaiveDateTime, original: T, - duration: Duration, + duration: TimeDelta, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { if span < 0 { @@ -226,40 +222,40 @@ where let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), - Ordering::Greater => Ok(original - Duration::nanoseconds(delta_down)), - Ordering::Less => Ok(original - Duration::nanoseconds(span - delta_down.abs())), + Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)), + Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())), } } else { Err(RoundingError::DurationExceedsLimit) } } -/// An error from rounding by `Duration` +/// An error from rounding by `TimeDelta` /// /// See: [`DurationRound`] #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum RoundingError { - /// Error when the Duration exceeds the Duration from or until the Unix epoch. + /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch. /// /// ``` rust - /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!( - /// dt.duration_round(Duration::days(365)), + /// dt.duration_round(TimeDelta::days(365)), /// Err(RoundingError::DurationExceedsTimestamp), /// ); /// ``` DurationExceedsTimestamp, - /// Error when `Duration.num_nanoseconds` exceeds the limit. + /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DurationRound, Duration, RoundingError, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, RoundingError, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap(); /// /// assert_eq!( - /// dt.duration_round(Duration::days(300 * 365)), + /// dt.duration_round(TimeDelta::days(300 * 365)), /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` @@ -268,10 +264,10 @@ pub enum RoundingError { /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust - /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// - /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); + /// assert_eq!(dt.duration_round(TimeDelta::days(1)), Err(RoundingError::TimestampExceedsLimit),); /// ``` TimestampExceedsLimit, } @@ -302,7 +298,7 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { - use super::{Duration, DurationRound, RoundingError, SubsecRound}; + use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Timelike; use crate::{NaiveDate, NaiveDateTime}; @@ -447,12 +443,12 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_round(Duration::zero()).unwrap().to_string(), + dt.duration_round(TimeDelta::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500 UTC" ); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); @@ -466,7 +462,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down @@ -479,24 +475,24 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(30)).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), "2012-12-13 00:00:00 UTC" ); @@ -504,11 +500,11 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::weeks(1)).unwrap().to_string(), "2020-10-29 00:00:00 +01:00" ); @@ -516,11 +512,11 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::weeks(1)).unwrap().to_string(), "2020-10-29 00:00:00 -01:00" ); } @@ -538,12 +534,12 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_round(Duration::zero()).unwrap().to_string(), + dt.duration_round(TimeDelta::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500" ); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180" ); @@ -558,7 +554,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00" ); // round down @@ -572,24 +568,24 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(30)).unwrap().to_string(), "2012-12-12 18:30:00" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), "2012-12-13 00:00:00" ); } @@ -598,7 +594,7 @@ mod tests { fn test_duration_round_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } @@ -615,7 +611,7 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" ); @@ -629,7 +625,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down @@ -642,23 +638,23 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(30)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), "2012-12-12 00:00:00 UTC" ); @@ -666,11 +662,11 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(), "2020-10-22 00:00:00 +01:00" ); @@ -678,11 +674,11 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(), "2020-10-22 00:00:00 -01:00" ); } @@ -700,7 +696,7 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.170" ); @@ -715,7 +711,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); // would round down @@ -729,23 +725,23 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(30)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), "2012-12-12 00:00:00" ); } @@ -754,7 +750,7 @@ mod tests { fn test_duration_trunc_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } @@ -762,15 +758,15 @@ mod tests { #[test] fn issue1010() { let dt = NaiveDateTime::from_timestamp_opt(-4_227_854_320, 678_774_288).unwrap(); - let span = Duration::microseconds(-7_019_067_213_869_040); + let span = TimeDelta::microseconds(-7_019_067_213_869_040); assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); let dt = NaiveDateTime::from_timestamp_opt(320_041_586, 920_103_021).unwrap(); - let span = Duration::nanoseconds(-8_923_838_508_697_114_584); + let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); let dt = NaiveDateTime::from_timestamp_opt(-2_621_440, 0).unwrap(); - let span = Duration::nanoseconds(-9_223_372_036_854_771_421); + let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } } From 3a9d8a3a3cfbac214842a3928464ad7ab1ad4a87 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:49:10 +0100 Subject: [PATCH 675/999] Rename `Duration` to `TimeDelta` in `date` module --- src/date.rs | 81 ++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/date.rs b/src/date.rs index be2f05c559..a66882cecc 100644 --- a/src/date.rs +++ b/src/date.rs @@ -13,15 +13,13 @@ use core::{fmt, hash}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::Duration as OldDuration; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] use crate::format::Locale; #[cfg(feature = "alloc")] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; -use crate::DateTime; -use crate::{Datelike, Weekday}; +use crate::{DateTime, Datelike, TimeDelta, Weekday}; /// ISO 8601 calendar date with time zone. /// @@ -53,7 +51,7 @@ use crate::{Datelike, Weekday}; /// /// - The date is timezone-agnostic up to one day (i.e. practically always), /// so the local date and UTC date should be equal for most cases -/// even though the raw calculation between `NaiveDate` and `Duration` may not. +/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not. #[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime` instead")] #[derive(Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] @@ -259,34 +257,34 @@ impl Date { tz.from_utc_date(&self.date) } - /// Adds given `Duration` to the current date. + /// Adds given `TimeDelta` to the current date. /// /// Returns `None` when it will result in overflow. #[inline] #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { let date = self.date.checked_add_signed(rhs)?; Some(Date { date, offset: self.offset }) } - /// Subtracts given `Duration` from the current date. + /// Subtracts given `TimeDelta` from the current date. /// /// Returns `None` when it will result in overflow. #[inline] #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { let date = self.date.checked_sub_signed(rhs)?; Some(Date { date, offset: self.offset }) } /// Subtracts another `Date` from the current date. - /// Returns a `Duration` of integral numbers. + /// Returns a `TimeDelta` of integral numbers. /// /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. + /// as all possible output fits in the range of `TimeDelta`. #[inline] #[must_use] - pub fn signed_duration_since(self, rhs: Date) -> OldDuration { + pub fn signed_duration_since(self, rhs: Date) -> TimeDelta { self.date.signed_duration_since(rhs.date) } @@ -495,43 +493,43 @@ impl hash::Hash for Date { } } -impl Add for Date { +impl Add for Date { type Output = Date; #[inline] - fn add(self, rhs: OldDuration) -> Date { - self.checked_add_signed(rhs).expect("`Date + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> Date { + self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed") } } -impl AddAssign for Date { +impl AddAssign for Date { #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed"); + fn add_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed"); } } -impl Sub for Date { +impl Sub for Date { type Output = Date; #[inline] - fn sub(self, rhs: OldDuration) -> Date { - self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> Date { + self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed") } } -impl SubAssign for Date { +impl SubAssign for Date { #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed"); + fn sub_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed"); } } impl Sub> for Date { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: Date) -> OldDuration { + fn sub(self, rhs: Date) -> TimeDelta { self.signed_duration_since(rhs) } } @@ -572,8 +570,7 @@ where mod tests { use super::Date; - use crate::duration::Duration; - use crate::{FixedOffset, NaiveDate, Utc}; + use crate::{FixedOffset, NaiveDate, TimeDelta, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, TimeZone}; @@ -584,15 +581,15 @@ mod tests { const WEEKS_PER_YEAR: f32 = 52.1775; // This is always at least one year because 1 year = 52.1775 weeks. - let one_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + let one_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); // A bit more than 2 years. - let two_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + let two_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::today() + Duration::weeks(12); + let future = Utc::today() + TimeDelta::weeks(12); assert_eq!(Utc::today().years_since(future), None); } @@ -602,20 +599,20 @@ mod tests { let date = Date::::from_utc(naivedate, Utc); let mut date_add = date; - date_add += Duration::days(5); - assert_eq!(date_add, date + Duration::days(5)); + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_add = date_add.with_timezone(&timezone); - assert_eq!(date_add, date + Duration::days(5)); + assert_eq!(date_add, date + TimeDelta::days(5)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_add = date_add.with_timezone(&timezone); - assert_eq!(date_add, date + Duration::days(5)); + assert_eq!(date_add, date + TimeDelta::days(5)); } #[test] @@ -626,8 +623,8 @@ mod tests { let date = Local.from_utc_date(&naivedate); let mut date_add = date; - date_add += Duration::days(5); - assert_eq!(date_add, date + Duration::days(5)); + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); } #[test] @@ -636,20 +633,20 @@ mod tests { let date = Date::::from_utc(naivedate, Utc); let mut date_sub = date; - date_sub -= Duration::days(5); - assert_eq!(date_sub, date - Duration::days(5)); + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_sub = date_sub.with_timezone(&timezone); - assert_eq!(date_sub, date - Duration::days(5)); + assert_eq!(date_sub, date - TimeDelta::days(5)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let date = date.with_timezone(&timezone); let date_sub = date_sub.with_timezone(&timezone); - assert_eq!(date_sub, date - Duration::days(5)); + assert_eq!(date_sub, date - TimeDelta::days(5)); } #[test] @@ -660,7 +657,7 @@ mod tests { let date = Local.from_utc_date(&naivedate); let mut date_sub = date; - date_sub -= Duration::days(5); - assert_eq!(date_sub, date - Duration::days(5)); + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); } } From 6da68c8d04604e99cb784765741e117ad387099d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 19:25:53 +0100 Subject: [PATCH 676/999] Rename remaining uses of `Duration` to `TimeDelta` --- bench/benches/chrono.rs | 4 ++-- src/format/parsed.rs | 9 ++++----- src/lib.rs | 10 +++++----- src/offset/local/mod.rs | 8 ++++---- src/traits.rs | 4 ++-- tests/dateutils.rs | 4 ++-- tests/wasm.rs | 2 +- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 38f8513d41..ab6a324453 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -6,7 +6,7 @@ use chrono::format::StrftimeItems; use chrono::prelude::*; #[cfg(feature = "unstable-locales")] use chrono::Locale; -use chrono::{DateTime, Duration, FixedOffset, Local, Utc, __BenchYearFlags}; +use chrono::{DateTime, FixedOffset, Local, TimeDelta, Utc, __BenchYearFlags}; fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { c.bench_function("bench_datetime_parse_from_rfc2822", |b| { @@ -198,7 +198,7 @@ fn bench_format_manual(c: &mut Criterion) { fn bench_naivedate_add_signed(c: &mut Criterion) { let date = NaiveDate::from_ymd_opt(2023, 7, 29).unwrap(); - let extra = Duration::days(25); + let extra = TimeDelta::days(25); c.bench_function("bench_naivedate_add_signed", |b| { b.iter(|| black_box(date).checked_add_signed(extra).unwrap()) }); diff --git a/src/format/parsed.rs b/src/format/parsed.rs index fb9d113442..19f7f81615 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -5,10 +5,9 @@ //! They can be constructed incrementally while being checked for consistency. use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; -use crate::duration::Duration as OldDuration; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use crate::{DateTime, Datelike, Timelike, Weekday}; +use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// Parsed parts of date and time. There are two classes of methods: /// @@ -430,7 +429,7 @@ impl Parsed { + (week_from_sun as i32 - 1) * 7 + weekday.num_days_from_sunday() as i32; let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) + .checked_add_signed(TimeDelta::days(i64::from(ndays))) .ok_or(OUT_OF_RANGE)?; if date.year() != year { return Err(OUT_OF_RANGE); @@ -464,7 +463,7 @@ impl Parsed { + (week_from_mon as i32 - 1) * 7 + weekday.num_days_from_monday() as i32; let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) + .checked_add_signed(TimeDelta::days(i64::from(ndays))) .ok_or(OUT_OF_RANGE)?; if date.year() != year { return Err(OUT_OF_RANGE); @@ -587,7 +586,7 @@ impl Parsed { 59 => {} // `datetime` is known to be off by one second. 0 => { - datetime -= OldDuration::seconds(1); + datetime -= TimeDelta::seconds(1); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), diff --git a/src/lib.rs b/src/lib.rs index 950ca46ec7..985bb1c805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,7 +174,7 @@ //! //! ```rust //! use chrono::prelude::*; -//! use chrono::Duration; +//! use chrono::TimeDelta; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: //! let dt = FixedOffset::east_opt(9*3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap()).unwrap(); @@ -201,11 +201,11 @@ //! // arithmetic operations //! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); //! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); -//! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -//! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + Duration::seconds(1_000_000_000), +//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::seconds(-2 * 3600 + 2)); +//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2)); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000), //! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - Duration::seconds(1_000_000_000), +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::seconds(1_000_000_000), //! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); //! ``` //! diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 1767763738..724f3597ad 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -187,7 +187,7 @@ impl TimeZone for Local { mod tests { use super::Local; use crate::offset::TimeZone; - use crate::{Datelike, Duration, Utc}; + use crate::{Datelike, TimeDelta, Utc}; #[test] fn verify_correct_offsets() { @@ -204,8 +204,8 @@ mod tests { #[test] fn verify_correct_offsets_distant_past() { - // let distant_past = Local::now() - Duration::days(365 * 100); - let distant_past = Local::now() - Duration::days(250 * 31); + // let distant_past = Local::now() - TimeDelta::days(365 * 100); + let distant_past = Local::now() - TimeDelta::days(250 * 31); let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); @@ -218,7 +218,7 @@ mod tests { #[test] fn verify_correct_offsets_distant_future() { - let distant_future = Local::now() + Duration::days(250 * 31); + let distant_future = Local::now() + TimeDelta::days(250 * 31); let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); diff --git a/src/traits.rs b/src/traits.rs index 1094bd822b..0018a7d9bd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -330,7 +330,7 @@ pub trait Timelike: Sized { #[cfg(test)] mod tests { use super::Datelike; - use crate::{Duration, NaiveDate}; + use crate::{NaiveDate, TimeDelta}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. /// @@ -377,7 +377,7 @@ mod tests { "on {:?}", jan1_year ); - let mid_year = jan1_year + Duration::days(133); + let mid_year = jan1_year + TimeDelta::days(133); assert_eq!( mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 8d3ce9c102..cf3d908a4b 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -94,7 +94,7 @@ fn try_verify_against_date_command() { let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); while date <= end { verify_against_date_command_local(DATE_PATH, date); - date += chrono::Duration::hours(1); + date += chrono::TimeDelta::hours(1); } })); } @@ -157,6 +157,6 @@ fn try_verify_against_date_command_format() { let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); while date.year() < 2008 { verify_against_date_command_format_local(DATE_PATH, date); - date += chrono::Duration::days(55); + date += chrono::TimeDelta::days(55); } } diff --git a/tests/wasm.rs b/tests/wasm.rs index 28eaacd0d5..6937da9f71 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -25,7 +25,7 @@ fn now() { let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc(); let diff = utc - actual; assert!( - diff < chrono::Duration::minutes(5), + diff < chrono::TimeDelta::minutes(5), "expected {} - {} == {} < 5m (env var: {})", utc, actual, From ac383d5303f732a3ab27e6c2d94fd5ee9895f52c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 20:02:24 +0100 Subject: [PATCH 677/999] Rename `Duration` to `TimeDelta` in `duration` module, flip type alias --- src/duration.rs | 821 ++++++++++++++++++++++++------------------------ src/lib.rs | 11 +- 2 files changed, 424 insertions(+), 408 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index c7d6ee9b9a..f2b3492d74 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -11,7 +11,7 @@ //! Temporal quantification use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; -use core::time::Duration as StdDuration; +use core::time::Duration; use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; @@ -44,7 +44,7 @@ const SECS_PER_WEEK: i64 = 604_800; /// /// This also allows for negative durations; see individual methods for details. /// -/// A `Duration` is represented internally as a complement of seconds and +/// A `TimeDelta` is represented internally as a complement of seconds and /// nanoseconds. The range is restricted to that of `i64` milliseconds, with the /// minimum value notably being set to `-i64::MAX` rather than allowing the full /// range of `i64::MIN`. This is to allow easy flipping of sign, so that for @@ -57,30 +57,30 @@ const SECS_PER_WEEK: i64 = 604_800; archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] -pub struct Duration { +pub struct TimeDelta { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC } -/// The minimum possible `Duration`: `-i64::MAX` milliseconds. -pub(crate) const MIN: Duration = Duration { +/// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. +pub(crate) const MIN: TimeDelta = TimeDelta { secs: -i64::MAX / MILLIS_PER_SEC - 1, nanos: NANOS_PER_SEC + (-i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; -/// The maximum possible `Duration`: `i64::MAX` milliseconds. -pub(crate) const MAX: Duration = Duration { +/// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. +pub(crate) const MAX: TimeDelta = TimeDelta { secs: i64::MAX / MILLIS_PER_SEC, nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; -impl Duration { - /// Makes a new `Duration` with given number of seconds and nanoseconds. +impl TimeDelta { + /// Makes a new `TimeDelta` with given number of seconds and nanoseconds. /// /// # Errors /// /// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000. - pub const fn new(secs: i64, nanos: u32) -> Option { + pub const fn new(secs: i64, nanos: u32) -> Option { if secs < MIN.secs || secs > MAX.secs || nanos >= 1_000_000_000 @@ -89,12 +89,12 @@ impl Duration { { return None; } - Some(Duration { secs, nanos: nanos as i32 }) + Some(TimeDelta { secs, nanos: nanos as i32 }) } - /// Makes a new `Duration` with the given number of weeks. + /// Makes a new `TimeDelta` with the given number of weeks. /// - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// Equivalent to `TimeDelta::seconds(weeks * 7 * 24 * 60 * 60)` with /// overflow checks. /// /// # Panics @@ -102,206 +102,202 @@ impl Duration { /// Panics when the duration is out of bounds. #[inline] #[must_use] - pub const fn weeks(weeks: i64) -> Duration { - expect!(Duration::try_weeks(weeks), "Duration::weeks out of bounds") + pub const fn weeks(weeks: i64) -> TimeDelta { + expect!(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") } - /// Makes a new `Duration` with the given number of weeks. + /// Makes a new `TimeDelta` with the given number of weeks. /// - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// Equivalent to `TimeDelta::seconds(weeks * 7 * 24 * 60 * 60)` with /// overflow checks. /// /// # Errors /// - /// Returns `None` when the duration is out of bounds. + /// Returns `None` when the `TimeDelta` would be out of bounds. #[inline] - pub const fn try_weeks(weeks: i64) -> Option { - Duration::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) + pub const fn try_weeks(weeks: i64) -> Option { + TimeDelta::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) } - /// Makes a new `Duration` with the given number of days. + /// Makes a new `TimeDelta` with the given number of days. /// - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// Equivalent to `TimeDelta::seconds(days * 24 * 60 * 60)` with overflow /// checks. /// /// # Panics /// - /// Panics when the duration is out of bounds. + /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - pub const fn days(days: i64) -> Duration { - expect!(Duration::try_days(days), "Duration::days out of bounds") + pub const fn days(days: i64) -> TimeDelta { + expect!(TimeDelta::try_days(days), "TimeDelta::days out of bounds") } - /// Makes a new `Duration` with the given number of days. + /// Makes a new `TimeDelta` with the given number of days. /// - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// Equivalent to `TimeDelta::seconds(days * 24 * 60 * 60)` with overflow /// checks. /// /// # Errors /// - /// Returns `None` when the duration is out of bounds. + /// Returns `None` when the `TimeDelta` would be out of bounds. #[inline] - pub const fn try_days(days: i64) -> Option { - Duration::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) + pub const fn try_days(days: i64) -> Option { + TimeDelta::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) } - /// Makes a new `Duration` with the given number of hours. + /// Makes a new `TimeDelta` with the given number of hours. /// - /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Equivalent to `TimeDelta::seconds(hours * 60 * 60)` with overflow checks. /// /// # Panics /// - /// Panics when the duration is out of bounds. + /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - pub const fn hours(hours: i64) -> Duration { - expect!(Duration::try_hours(hours), "Duration::hours out of bounds") + pub const fn hours(hours: i64) -> TimeDelta { + expect!(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") } - /// Makes a new `Duration` with the given number of hours. + /// Makes a new `TimeDelta` with the given number of hours. /// - /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Equivalent to `TimeDelta::seconds(hours * 60 * 60)` with overflow checks. /// /// # Errors /// - /// Returns `None` when the duration is out of bounds. + /// Returns `None` when the `TimeDelta` would be out of bounds. #[inline] - pub const fn try_hours(hours: i64) -> Option { - Duration::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) + pub const fn try_hours(hours: i64) -> Option { + TimeDelta::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) } - /// Makes a new `Duration` with the given number of minutes. + /// Makes a new `TimeDelta` with the given number of minutes. /// - /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Equivalent to `TimeDelta::seconds(minutes * 60)` with overflow checks. /// /// # Panics /// - /// Panics when the duration is out of bounds. + /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - pub const fn minutes(minutes: i64) -> Duration { - expect!(Duration::try_minutes(minutes), "Duration::minutes out of bounds") + pub const fn minutes(minutes: i64) -> TimeDelta { + expect!(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") } - /// Makes a new `Duration` with the given number of minutes. + /// Makes a new `TimeDelta` with the given number of minutes. /// - /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Equivalent to `TimeDelta::seconds(minutes * 60)` with overflow checks. /// /// # Errors /// - /// Returns `None` when the duration is out of bounds. + /// Returns `None` when the `TimeDelta` would be out of bounds. #[inline] - pub const fn try_minutes(minutes: i64) -> Option { - Duration::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) + pub const fn try_minutes(minutes: i64) -> Option { + TimeDelta::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) } - /// Makes a new `Duration` with the given number of seconds. + /// Makes a new `TimeDelta` with the given number of seconds. /// /// # Panics /// - /// Panics when the duration is out of bounds, i.e. when the value is more - /// than `i64::MAX / 1_000` seconds or less than `-i64::MAX / 1_000` seconds - /// (in this context, this is the same as `i64::MIN / 1_000` due to - /// rounding). + /// Panics when `seconds` is more than `i64::MAX / 1_000` or less than `-i64::MAX / 1_000` + /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). #[inline] #[must_use] - pub const fn seconds(seconds: i64) -> Duration { - expect!(Duration::try_seconds(seconds), "Duration::seconds out of bounds") + pub const fn seconds(seconds: i64) -> TimeDelta { + expect!(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") } - /// Makes a new `Duration` with the given number of seconds. + /// Makes a new `TimeDelta` with the given number of seconds. /// /// # Errors /// - /// Returns `None` when the duration is more than `i64::MAX / 1_000` seconds - /// or less than `-i64::MAX / 1_000` seconds (in this context, this is the - /// same as `i64::MIN / 1_000` due to rounding). + /// Returns `None` when `seconds` is more than `i64::MAX / 1_000` or less than + /// `-i64::MAX / 1_000` (in this context, this is the same as `i64::MIN / 1_000` due to + /// rounding). #[inline] - pub const fn try_seconds(seconds: i64) -> Option { - Duration::new(seconds, 0) + pub const fn try_seconds(seconds: i64) -> Option { + TimeDelta::new(seconds, 0) } - /// Makes a new `Duration` with the given number of milliseconds. + /// Makes a new `TimeDelta` with the given number of milliseconds. /// /// # Panics /// - /// Panics when the duration is out of bounds, i.e. when the duration is - /// more than `i64::MAX` milliseconds or less than `-i64::MAX` milliseconds. - /// Notably, this is not the same as `i64::MIN`. + /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than + /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] - pub const fn milliseconds(milliseconds: i64) -> Duration { - expect!(Duration::try_milliseconds(milliseconds), "Duration::milliseconds out of bounds") + pub const fn milliseconds(milliseconds: i64) -> TimeDelta { + expect!(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } - /// Makes a new `Duration` with the given number of milliseconds. + /// Makes a new `TimeDelta` with the given number of milliseconds. /// /// # Errors /// - /// Returns `None` when the duration is more than `i64::MAX` milliseconds or - /// less than `-i64::MAX` milliseconds. Notably, this is not the same as - /// `i64::MIN`. + /// Returns `None` the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more + /// than `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] - pub const fn try_milliseconds(milliseconds: i64) -> Option { + pub const fn try_milliseconds(milliseconds: i64) -> Option { // We don't need to compare against MAX, as this function accepts an // i64, and MAX is aligned to i64::MAX milliseconds. if milliseconds < -i64::MAX { return None; } let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + let d = TimeDelta { secs, nanos: millis as i32 * NANOS_PER_MILLI }; Some(d) } - /// Makes a new `Duration` with the given number of microseconds. + /// Makes a new `TimeDelta` with the given number of microseconds. /// /// The number of microseconds acceptable by this constructor is less than - /// the total number that can actually be stored in a `Duration`, so it is + /// the total number that can actually be stored in a `TimeDelta`, so it is /// not possible to specify a value that would be out of bounds. This /// function is therefore infallible. #[inline] - pub const fn microseconds(microseconds: i64) -> Duration { + pub const fn microseconds(microseconds: i64) -> TimeDelta { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); let nanos = micros as i32 * NANOS_PER_MICRO; - Duration { secs, nanos } + TimeDelta { secs, nanos } } - /// Makes a new `Duration` with the given number of nanoseconds. + /// Makes a new `TimeDelta` with the given number of nanoseconds. /// /// The number of nanoseconds acceptable by this constructor is less than - /// the total number that can actually be stored in a `Duration`, so it is + /// the total number that can actually be stored in a `TimeDelta`, so it is /// not possible to specify a value that would be out of bounds. This /// function is therefore infallible. #[inline] - pub const fn nanoseconds(nanos: i64) -> Duration { + pub const fn nanoseconds(nanos: i64) -> TimeDelta { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); - Duration { secs, nanos: nanos as i32 } + TimeDelta { secs, nanos: nanos as i32 } } - /// Returns the total number of whole weeks in the `Duration`. + /// Returns the total number of whole weeks in the `TimeDelta`. #[inline] pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } - /// Returns the total number of whole days in the `Duration`. + /// Returns the total number of whole days in the `TimeDelta`. pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } - /// Returns the total number of whole hours in the `Duration`. + /// Returns the total number of whole hours in the `TimeDelta`. #[inline] pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } - /// Returns the total number of whole minutes in the `Duration`. + /// Returns the total number of whole minutes in the `TimeDelta`. #[inline] pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } - /// Returns the total number of whole seconds in the `Duration`. + /// Returns the total number of whole seconds in the `TimeDelta`. pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. if self.secs < 0 && self.nanos > 0 { @@ -313,7 +309,7 @@ impl Duration { /// Returns the number of nanoseconds such that /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the `Duration`. + /// nanoseconds in the `TimeDelta`. pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC @@ -322,9 +318,9 @@ impl Duration { } } - /// Returns the total number of whole milliseconds in the `Duration`. + /// Returns the total number of whole milliseconds in the `TimeDelta`. pub const fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined such + // A proper TimeDelta will not overflow, because MIN and MAX are defined such // that the range is within the bounds of an i64, from -i64::MAX through to // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. let secs_part = self.num_seconds() * MILLIS_PER_SEC; @@ -332,7 +328,7 @@ impl Duration { secs_part + nanos_part as i64 } - /// Returns the total number of whole microseconds in the `Duration`, + /// Returns the total number of whole microseconds in the `TimeDelta`, /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); @@ -340,7 +336,7 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Returns the total number of whole nanoseconds in the `Duration`, + /// Returns the total number of whole nanoseconds in the `TimeDelta`, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); @@ -348,163 +344,163 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Add two `Duration`s, returning `None` if overflow occurred. + /// Add two `TimeDelta`s, returning `None` if overflow occurred. #[must_use] - pub const fn checked_add(&self, rhs: &Duration) -> Option { + pub const fn checked_add(&self, rhs: &TimeDelta) -> Option { // No overflow checks here because we stay comfortably within the range of an `i64`. - // Range checks happen in `Duration::new`. + // Range checks happen in `TimeDelta::new`. let mut secs = self.secs + rhs.secs; let mut nanos = self.nanos + rhs.nanos; if nanos >= NANOS_PER_SEC { nanos -= NANOS_PER_SEC; secs += 1; } - Duration::new(secs, nanos as u32) + TimeDelta::new(secs, nanos as u32) } - /// Subtract two `Duration`s, returning `None` if overflow occurred. + /// Subtract two `TimeDelta`s, returning `None` if overflow occurred. #[must_use] - pub const fn checked_sub(&self, rhs: &Duration) -> Option { + pub const fn checked_sub(&self, rhs: &TimeDelta) -> Option { // No overflow checks here because we stay comfortably within the range of an `i64`. - // Range checks happen in `Duration::new`. + // Range checks happen in `TimeDelta::new`. let mut secs = self.secs - rhs.secs; let mut nanos = self.nanos - rhs.nanos; if nanos < 0 { nanos += NANOS_PER_SEC; secs -= 1; } - Duration::new(secs, nanos as u32) + TimeDelta::new(secs, nanos as u32) } - /// Returns the `Duration` as an absolute (non-negative) value. + /// Returns the `TimeDelta` as an absolute (non-negative) value. #[inline] - pub const fn abs(&self) -> Duration { + pub const fn abs(&self) -> TimeDelta { if self.secs < 0 && self.nanos != 0 { - Duration { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } + TimeDelta { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } } else { - Duration { secs: self.secs.abs(), nanos: self.nanos } + TimeDelta { secs: self.secs.abs(), nanos: self.nanos } } } - /// The minimum possible `Duration`: `-i64::MAX` milliseconds. + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. #[inline] - pub const fn min_value() -> Duration { + pub const fn min_value() -> TimeDelta { MIN } - /// The maximum possible `Duration`: `i64::MAX` milliseconds. + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. #[inline] - pub const fn max_value() -> Duration { + pub const fn max_value() -> TimeDelta { MAX } - /// A `Duration` where the stored seconds and nanoseconds are equal to zero. + /// A `TimeDelta` where the stored seconds and nanoseconds are equal to zero. #[inline] - pub const fn zero() -> Duration { - Duration { secs: 0, nanos: 0 } + pub const fn zero() -> TimeDelta { + TimeDelta { secs: 0, nanos: 0 } } - /// Returns `true` if the `Duration` equals `Duration::zero()`. + /// Returns `true` if the `TimeDelta` equals `TimeDelta::zero()`. #[inline] pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 } - /// Creates a `time::Duration` object from `std::time::Duration` + /// Creates a `TimeDelta` object from `std::time::Duration` /// /// This function errors when original duration is larger than the maximum /// value supported for this type. - pub const fn from_std(duration: StdDuration) -> Result { + pub const fn from_std(duration: Duration) -> Result { // We need to check secs as u64 before coercing to i64 if duration.as_secs() > MAX.secs as u64 { return Err(OutOfRangeError(())); } - match Duration::new(duration.as_secs() as i64, duration.subsec_nanos()) { + match TimeDelta::new(duration.as_secs() as i64, duration.subsec_nanos()) { Some(d) => Ok(d), None => Err(OutOfRangeError(())), } } - /// Creates a `std::time::Duration` object from `time::Duration` + /// Creates a `std::time::Duration` object from a `TimeDelta`. /// /// This function errors when duration is less than zero. As standard /// library implementation is limited to non-negative values. - pub const fn to_std(&self) -> Result { + pub const fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); } - Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) + Ok(Duration::new(self.secs as u64, self.nanos as u32)) } /// This duplicates `Neg::neg` because trait methods can't be const yet. - pub(crate) const fn neg(self) -> Duration { + pub(crate) const fn neg(self) -> TimeDelta { let (secs_diff, nanos) = match self.nanos { 0 => (0, 0), nanos => (1, NANOS_PER_SEC - nanos), }; - Duration { secs: -self.secs - secs_diff, nanos } + TimeDelta { secs: -self.secs - secs_diff, nanos } } } -impl Neg for Duration { - type Output = Duration; +impl Neg for TimeDelta { + type Output = TimeDelta; #[inline] - fn neg(self) -> Duration { + fn neg(self) -> TimeDelta { let (secs_diff, nanos) = match self.nanos { 0 => (0, 0), nanos => (1, NANOS_PER_SEC - nanos), }; - Duration { secs: -self.secs - secs_diff, nanos } + TimeDelta { secs: -self.secs - secs_diff, nanos } } } -impl Add for Duration { - type Output = Duration; +impl Add for TimeDelta { + type Output = TimeDelta; - fn add(self, rhs: Duration) -> Duration { - self.checked_add(&rhs).expect("`Duration + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> TimeDelta { + self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed") } } -impl Sub for Duration { - type Output = Duration; +impl Sub for TimeDelta { + type Output = TimeDelta; - fn sub(self, rhs: Duration) -> Duration { - self.checked_sub(&rhs).expect("`Duration - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> TimeDelta { + self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed") } } -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: Duration) { - let new = self.checked_add(&rhs).expect("`Duration + Duration` overflowed"); +impl AddAssign for TimeDelta { + fn add_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed"); *self = new; } } -impl SubAssign for Duration { - fn sub_assign(&mut self, rhs: Duration) { - let new = self.checked_sub(&rhs).expect("`Duration - Duration` overflowed"); +impl SubAssign for TimeDelta { + fn sub_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed"); *self = new; } } -impl Mul for Duration { - type Output = Duration; +impl Mul for TimeDelta { + type Output = TimeDelta; - fn mul(self, rhs: i32) -> Duration { + fn mul(self, rhs: i32) -> TimeDelta { // Multiply nanoseconds as i64, because it cannot overflow that way. let total_nanos = self.nanos as i64 * rhs as i64; let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); let secs = self.secs * rhs as i64 + extra_secs; - Duration { secs, nanos: nanos as i32 } + TimeDelta { secs, nanos: nanos as i32 } } } -impl Div for Duration { - type Output = Duration; +impl Div for TimeDelta { + type Output = TimeDelta; - fn div(self, rhs: i32) -> Duration { + fn div(self, rhs: i32) -> TimeDelta { let mut secs = self.secs / rhs as i64; let carry = self.secs - secs * rhs as i64; let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; @@ -517,24 +513,24 @@ impl Div for Duration { nanos += NANOS_PER_SEC; secs -= 1; } - Duration { secs, nanos } + TimeDelta { secs, nanos } } } -impl<'a> core::iter::Sum<&'a Duration> for Duration { - fn sum>(iter: I) -> Duration { - iter.fold(Duration::zero(), |acc, x| acc + *x) +impl<'a> core::iter::Sum<&'a TimeDelta> for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + *x) } } -impl core::iter::Sum for Duration { - fn sum>(iter: I) -> Duration { - iter.fold(Duration::zero(), |acc, x| acc + x) +impl core::iter::Sum for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + x) } } -impl fmt::Display for Duration { - /// Format a duration using the [ISO 8601] format +impl fmt::Display for TimeDelta { + /// Format a `TimeDelta` using the [ISO 8601] format /// /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -567,7 +563,7 @@ impl fmt::Display for Duration { } } -/// Represents error when converting `Duration` to/from a standard library +/// Represents error when converting `TimeDelta` to/from a standard library /// implementation /// /// The `std::time::Duration` supports a range from zero to `u64::MAX` @@ -596,14 +592,14 @@ const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { } #[cfg(all(feature = "arbitrary", feature = "std"))] -impl arbitrary::Arbitrary<'_> for Duration { - fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { +impl arbitrary::Arbitrary<'_> for TimeDelta { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; let nanos: i32 = u.int_in_range(0..=(NANOS_PER_SEC - 1))?; - let duration = Duration { secs, nanos }; + let duration = TimeDelta { secs, nanos }; if duration < MIN || duration > MAX { Err(arbitrary::Error::IncorrectFormat) @@ -616,61 +612,64 @@ impl arbitrary::Arbitrary<'_> for Duration { #[cfg(test)] mod tests { use super::OutOfRangeError; - use super::{Duration, MAX, MIN}; - use core::time::Duration as StdDuration; + use super::{TimeDelta, MAX, MIN}; + use core::time::Duration; #[test] fn test_duration() { - assert!(Duration::seconds(1) != Duration::zero()); - assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); + assert!(TimeDelta::seconds(1) != TimeDelta::zero()); + assert_eq!(TimeDelta::seconds(1) + TimeDelta::seconds(2), TimeDelta::seconds(3)); assert_eq!( - Duration::seconds(86_399) + Duration::seconds(4), - Duration::days(1) + Duration::seconds(3) + TimeDelta::seconds(86_399) + TimeDelta::seconds(4), + TimeDelta::days(1) + TimeDelta::seconds(3) ); - assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863_000)); - assert_eq!(Duration::days(10) - Duration::seconds(1_000_000), Duration::seconds(-136_000)); + assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000)); assert_eq!( - Duration::days(2) + Duration::seconds(86_399) + Duration::nanoseconds(1_234_567_890), - Duration::days(3) + Duration::nanoseconds(234_567_890) + TimeDelta::days(10) - TimeDelta::seconds(1_000_000), + TimeDelta::seconds(-136_000) ); - assert_eq!(-Duration::days(3), Duration::days(-3)); assert_eq!( - -(Duration::days(3) + Duration::seconds(70)), - Duration::days(-4) + Duration::seconds(86_400 - 70) + TimeDelta::days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), + TimeDelta::days(3) + TimeDelta::nanoseconds(234_567_890) + ); + assert_eq!(-TimeDelta::days(3), TimeDelta::days(-3)); + assert_eq!( + -(TimeDelta::days(3) + TimeDelta::seconds(70)), + TimeDelta::days(-4) + TimeDelta::seconds(86_400 - 70) ); - let mut d = Duration::default(); - d += Duration::minutes(1); - d -= Duration::seconds(30); - assert_eq!(d, Duration::seconds(30)); + let mut d = TimeDelta::default(); + d += TimeDelta::minutes(1); + d -= TimeDelta::seconds(30); + assert_eq!(d, TimeDelta::seconds(30)); } #[test] fn test_duration_num_days() { - assert_eq!(Duration::zero().num_days(), 0); - assert_eq!(Duration::days(1).num_days(), 1); - assert_eq!(Duration::days(-1).num_days(), -1); - assert_eq!(Duration::seconds(86_399).num_days(), 0); - assert_eq!(Duration::seconds(86_401).num_days(), 1); - assert_eq!(Duration::seconds(-86_399).num_days(), 0); - assert_eq!(Duration::seconds(-86_401).num_days(), -1); - assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); - assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); + assert_eq!(TimeDelta::zero().num_days(), 0); + assert_eq!(TimeDelta::days(1).num_days(), 1); + assert_eq!(TimeDelta::days(-1).num_days(), -1); + assert_eq!(TimeDelta::seconds(86_399).num_days(), 0); + assert_eq!(TimeDelta::seconds(86_401).num_days(), 1); + assert_eq!(TimeDelta::seconds(-86_399).num_days(), 0); + assert_eq!(TimeDelta::seconds(-86_401).num_days(), -1); + assert_eq!(TimeDelta::days(i32::MAX as i64).num_days(), i32::MAX as i64); + assert_eq!(TimeDelta::days(i32::MIN as i64).num_days(), i32::MIN as i64); } #[test] fn test_duration_num_seconds() { - assert_eq!(Duration::zero().num_seconds(), 0); - assert_eq!(Duration::seconds(1).num_seconds(), 1); - assert_eq!(Duration::seconds(-1).num_seconds(), -1); - assert_eq!(Duration::milliseconds(999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(1001).num_seconds(), 1); - assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); + assert_eq!(TimeDelta::zero().num_seconds(), 0); + assert_eq!(TimeDelta::seconds(1).num_seconds(), 1); + assert_eq!(TimeDelta::seconds(-1).num_seconds(), -1); + assert_eq!(TimeDelta::milliseconds(999).num_seconds(), 0); + assert_eq!(TimeDelta::milliseconds(1001).num_seconds(), 1); + assert_eq!(TimeDelta::milliseconds(-999).num_seconds(), 0); + assert_eq!(TimeDelta::milliseconds(-1001).num_seconds(), -1); } #[test] fn test_duration_seconds_max_allowed() { - let duration = Duration::seconds(i64::MAX / 1_000); + let duration = TimeDelta::seconds(i64::MAX / 1_000); assert_eq!(duration.num_seconds(), i64::MAX / 1_000); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -679,16 +678,16 @@ mod tests { } #[test] fn test_duration_seconds_max_overflow() { - assert!(Duration::try_seconds(i64::MAX / 1_000 + 1).is_none()); + assert!(TimeDelta::try_seconds(i64::MAX / 1_000 + 1).is_none()); } #[test] - #[should_panic(expected = "Duration::seconds out of bounds")] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_max_overflow_panic() { - let _ = Duration::seconds(i64::MAX / 1_000 + 1); + let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); } #[test] fn test_duration_seconds_min_allowed() { - let duration = Duration::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + let duration = TimeDelta::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -697,29 +696,29 @@ mod tests { } #[test] fn test_duration_seconds_min_underflow() { - assert!(Duration::try_seconds(-i64::MAX / 1_000 - 1).is_none()); + assert!(TimeDelta::try_seconds(-i64::MAX / 1_000 - 1).is_none()); } #[test] - #[should_panic(expected = "Duration::seconds out of bounds")] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_min_underflow_panic() { - let _ = Duration::seconds(-i64::MAX / 1_000 - 1); + let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); } #[test] fn test_duration_num_milliseconds() { - assert_eq!(Duration::zero().num_milliseconds(), 0); - assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1); - assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1); - assert_eq!(Duration::microseconds(999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); - assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); + assert_eq!(TimeDelta::zero().num_milliseconds(), 0); + assert_eq!(TimeDelta::milliseconds(1).num_milliseconds(), 1); + assert_eq!(TimeDelta::milliseconds(-1).num_milliseconds(), -1); + assert_eq!(TimeDelta::microseconds(999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(1001).num_milliseconds(), 1); + assert_eq!(TimeDelta::microseconds(-999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(-1001).num_milliseconds(), -1); } #[test] fn test_duration_milliseconds_max_allowed() { // The maximum number of milliseconds acceptable through the constructor is - // equal to the number that can be stored in a Duration. - let duration = Duration::milliseconds(i64::MAX); + // equal to the number that can be stored in a TimeDelta. + let duration = TimeDelta::milliseconds(i64::MAX); assert_eq!(duration.num_milliseconds(), i64::MAX); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -730,14 +729,16 @@ mod tests { fn test_duration_milliseconds_max_overflow() { // Here we ensure that trying to add one millisecond to the maximum storable // value will fail. - assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(1)).is_none()); + assert!(TimeDelta::milliseconds(i64::MAX) + .checked_add(&TimeDelta::milliseconds(1)) + .is_none()); } #[test] fn test_duration_milliseconds_min_allowed() { // The minimum number of milliseconds acceptable through the constructor is - // not equal to the number that can be stored in a Duration - there is a + // not equal to the number that can be stored in a TimeDelta - there is a // difference of one (i64::MIN vs -i64::MAX). - let duration = Duration::milliseconds(-i64::MAX); + let duration = TimeDelta::milliseconds(-i64::MAX); assert_eq!(duration.num_milliseconds(), -i64::MAX); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -748,59 +749,59 @@ mod tests { fn test_duration_milliseconds_min_underflow() { // Here we ensure that trying to subtract one millisecond from the minimum // storable value will fail. - assert!(Duration::milliseconds(-i64::MAX) - .checked_sub(&Duration::milliseconds(1)) + assert!(TimeDelta::milliseconds(-i64::MAX) + .checked_sub(&TimeDelta::milliseconds(1)) .is_none()); } #[test] - #[should_panic(expected = "Duration::milliseconds out of bounds")] + #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] fn test_duration_milliseconds_min_underflow_panic() { // Here we ensure that trying to create a value one millisecond below the // minimum storable value will fail. This test is necessary because the // storable range is -i64::MAX, but the constructor type of i64 will allow // i64::MIN, which is one value below. - let _ = Duration::milliseconds(i64::MIN); // Same as -i64::MAX - 1 + let _ = TimeDelta::milliseconds(i64::MIN); // Same as -i64::MAX - 1 } #[test] fn test_duration_num_microseconds() { - assert_eq!(Duration::zero().num_microseconds(), Some(0)); - assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1)); - assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); + assert_eq!(TimeDelta::zero().num_microseconds(), Some(0)); + assert_eq!(TimeDelta::microseconds(1).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::microseconds(-1).num_microseconds(), Some(-1)); + assert_eq!(TimeDelta::nanoseconds(999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1001).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(-1001).num_microseconds(), Some(-1)); // overflow checks const MICROS_PER_DAY: i64 = 86_400_000_000; assert_eq!( - Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), + TimeDelta::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!( - Duration::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(), + TimeDelta::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(), Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); - assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(Duration::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); + assert_eq!(TimeDelta::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); + assert_eq!(TimeDelta::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); } #[test] fn test_duration_microseconds_max_allowed() { // The number of microseconds acceptable through the constructor is far - // fewer than the number that can actually be stored in a Duration, so this + // fewer than the number that can actually be stored in a TimeDelta, so this // is not a particular insightful test. - let duration = Duration::microseconds(i64::MAX); + let duration = TimeDelta::microseconds(i64::MAX); assert_eq!(duration.num_microseconds(), Some(i64::MAX)); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, i64::MAX as i128 * 1_000 ); - // Here we create a Duration with the maximum possible number of - // microseconds by creating a Duration with the maximum number of + // Here we create a TimeDelta with the maximum possible number of + // microseconds by creating a TimeDelta with the maximum number of // milliseconds and then checking that the number of microseconds matches // the storage limit. - let duration = Duration::milliseconds(i64::MAX); + let duration = TimeDelta::milliseconds(i64::MAX); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -809,9 +810,9 @@ mod tests { } #[test] fn test_duration_microseconds_max_overflow() { - // This test establishes that a Duration can store more microseconds than + // This test establishes that a TimeDelta can store more microseconds than // are representable through the return of duration.num_microseconds(). - let duration = Duration::microseconds(i64::MAX) + Duration::microseconds(1); + let duration = TimeDelta::microseconds(i64::MAX) + TimeDelta::microseconds(1); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -819,24 +820,26 @@ mod tests { ); // Here we ensure that trying to add one microsecond to the maximum storable // value will fail. - assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::microseconds(1)).is_none()); + assert!(TimeDelta::milliseconds(i64::MAX) + .checked_add(&TimeDelta::microseconds(1)) + .is_none()); } #[test] fn test_duration_microseconds_min_allowed() { // The number of microseconds acceptable through the constructor is far - // fewer than the number that can actually be stored in a Duration, so this + // fewer than the number that can actually be stored in a TimeDelta, so this // is not a particular insightful test. - let duration = Duration::microseconds(i64::MIN); + let duration = TimeDelta::microseconds(i64::MIN); assert_eq!(duration.num_microseconds(), Some(i64::MIN)); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, i64::MIN as i128 * 1_000 ); - // Here we create a Duration with the minimum possible number of - // microseconds by creating a Duration with the minimum number of + // Here we create a TimeDelta with the minimum possible number of + // microseconds by creating a TimeDelta with the minimum number of // milliseconds and then checking that the number of microseconds matches // the storage limit. - let duration = Duration::milliseconds(-i64::MAX); + let duration = TimeDelta::milliseconds(-i64::MAX); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -845,9 +848,9 @@ mod tests { } #[test] fn test_duration_microseconds_min_underflow() { - // This test establishes that a Duration can store more microseconds than + // This test establishes that a TimeDelta can store more microseconds than // are representable through the return of duration.num_microseconds(). - let duration = Duration::microseconds(i64::MIN) - Duration::microseconds(1); + let duration = TimeDelta::microseconds(i64::MIN) - TimeDelta::microseconds(1); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -855,45 +858,45 @@ mod tests { ); // Here we ensure that trying to subtract one microsecond from the minimum // storable value will fail. - assert!(Duration::milliseconds(-i64::MAX) - .checked_sub(&Duration::microseconds(1)) + assert!(TimeDelta::milliseconds(-i64::MAX) + .checked_sub(&TimeDelta::microseconds(1)) .is_none()); } #[test] fn test_duration_num_nanoseconds() { - assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); + assert_eq!(TimeDelta::zero().num_nanoseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1).num_nanoseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-1).num_nanoseconds(), Some(-1)); // overflow checks const NANOS_PER_DAY: i64 = 86_400_000_000_000; assert_eq!( - Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + TimeDelta::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!( - Duration::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + TimeDelta::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(), Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); - assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(Duration::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); + assert_eq!(TimeDelta::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); + assert_eq!(TimeDelta::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); } #[test] fn test_duration_nanoseconds_max_allowed() { // The number of nanoseconds acceptable through the constructor is far fewer - // than the number that can actually be stored in a Duration, so this is not + // than the number that can actually be stored in a TimeDelta, so this is not // a particular insightful test. - let duration = Duration::nanoseconds(i64::MAX); + let duration = TimeDelta::nanoseconds(i64::MAX); assert_eq!(duration.num_nanoseconds(), Some(i64::MAX)); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, i64::MAX as i128 ); - // Here we create a Duration with the maximum possible number of nanoseconds - // by creating a Duration with the maximum number of milliseconds and then + // Here we create a TimeDelta with the maximum possible number of nanoseconds + // by creating a TimeDelta with the maximum number of milliseconds and then // checking that the number of nanoseconds matches the storage limit. - let duration = Duration::milliseconds(i64::MAX); + let duration = TimeDelta::milliseconds(i64::MAX); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -902,9 +905,9 @@ mod tests { } #[test] fn test_duration_nanoseconds_max_overflow() { - // This test establishes that a Duration can store more nanoseconds than are + // This test establishes that a TimeDelta can store more nanoseconds than are // representable through the return of duration.num_nanoseconds(). - let duration = Duration::nanoseconds(i64::MAX) + Duration::nanoseconds(1); + let duration = TimeDelta::nanoseconds(i64::MAX) + TimeDelta::nanoseconds(1); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -912,23 +915,25 @@ mod tests { ); // Here we ensure that trying to add one nanosecond to the maximum storable // value will fail. - assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); + assert!(TimeDelta::milliseconds(i64::MAX) + .checked_add(&TimeDelta::nanoseconds(1)) + .is_none()); } #[test] fn test_duration_nanoseconds_min_allowed() { // The number of nanoseconds acceptable through the constructor is far fewer - // than the number that can actually be stored in a Duration, so this is not + // than the number that can actually be stored in a TimeDelta, so this is not // a particular insightful test. - let duration = Duration::nanoseconds(i64::MIN); + let duration = TimeDelta::nanoseconds(i64::MIN); assert_eq!(duration.num_nanoseconds(), Some(i64::MIN)); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, i64::MIN as i128 ); - // Here we create a Duration with the minimum possible number of nanoseconds - // by creating a Duration with the minimum number of milliseconds and then + // Here we create a TimeDelta with the minimum possible number of nanoseconds + // by creating a TimeDelta with the minimum number of milliseconds and then // checking that the number of nanoseconds matches the storage limit. - let duration = Duration::milliseconds(-i64::MAX); + let duration = TimeDelta::milliseconds(-i64::MAX); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -937,9 +942,9 @@ mod tests { } #[test] fn test_duration_nanoseconds_min_underflow() { - // This test establishes that a Duration can store more nanoseconds than are + // This test establishes that a TimeDelta can store more nanoseconds than are // representable through the return of duration.num_nanoseconds(). - let duration = Duration::nanoseconds(i64::MIN) - Duration::nanoseconds(1); + let duration = TimeDelta::nanoseconds(i64::MIN) - TimeDelta::nanoseconds(1); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -947,7 +952,9 @@ mod tests { ); // Here we ensure that trying to subtract one nanosecond from the minimum // storable value will fail. - assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); + assert!(TimeDelta::milliseconds(-i64::MAX) + .checked_sub(&TimeDelta::nanoseconds(1)) + .is_none()); } #[test] @@ -956,7 +963,7 @@ mod tests { MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, i64::MAX as i128 * 1_000_000 ); - assert_eq!(MAX, Duration::milliseconds(i64::MAX)); + assert_eq!(MAX, TimeDelta::milliseconds(i64::MAX)); assert_eq!(MAX.num_milliseconds(), i64::MAX); assert_eq!(MAX.num_microseconds(), None); assert_eq!(MAX.num_nanoseconds(), None); @@ -967,7 +974,7 @@ mod tests { MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, -i64::MAX as i128 * 1_000_000 ); - assert_eq!(MIN, Duration::milliseconds(-i64::MAX)); + assert_eq!(MIN, TimeDelta::milliseconds(-i64::MAX)); assert_eq!(MIN.num_milliseconds(), -i64::MAX); assert_eq!(MIN.num_microseconds(), None); assert_eq!(MIN.num_nanoseconds(), None); @@ -975,194 +982,200 @@ mod tests { #[test] fn test_duration_ord() { - assert!(Duration::milliseconds(1) < Duration::milliseconds(2)); - assert!(Duration::milliseconds(2) > Duration::milliseconds(1)); - assert!(Duration::milliseconds(-1) > Duration::milliseconds(-2)); - assert!(Duration::milliseconds(-2) < Duration::milliseconds(-1)); - assert!(Duration::milliseconds(-1) < Duration::milliseconds(1)); - assert!(Duration::milliseconds(1) > Duration::milliseconds(-1)); - assert!(Duration::milliseconds(0) < Duration::milliseconds(1)); - assert!(Duration::milliseconds(0) > Duration::milliseconds(-1)); - assert!(Duration::milliseconds(1_001) < Duration::milliseconds(1_002)); - assert!(Duration::milliseconds(-1_001) > Duration::milliseconds(-1_002)); - assert!(Duration::nanoseconds(1_234_567_890) < Duration::nanoseconds(1_234_567_891)); - assert!(Duration::nanoseconds(-1_234_567_890) > Duration::nanoseconds(-1_234_567_891)); - assert!(Duration::milliseconds(i64::MAX) > Duration::milliseconds(i64::MAX - 1)); - assert!(Duration::milliseconds(-i64::MAX) < Duration::milliseconds(-i64::MAX + 1)); + assert!(TimeDelta::milliseconds(1) < TimeDelta::milliseconds(2)); + assert!(TimeDelta::milliseconds(2) > TimeDelta::milliseconds(1)); + assert!(TimeDelta::milliseconds(-1) > TimeDelta::milliseconds(-2)); + assert!(TimeDelta::milliseconds(-2) < TimeDelta::milliseconds(-1)); + assert!(TimeDelta::milliseconds(-1) < TimeDelta::milliseconds(1)); + assert!(TimeDelta::milliseconds(1) > TimeDelta::milliseconds(-1)); + assert!(TimeDelta::milliseconds(0) < TimeDelta::milliseconds(1)); + assert!(TimeDelta::milliseconds(0) > TimeDelta::milliseconds(-1)); + assert!(TimeDelta::milliseconds(1_001) < TimeDelta::milliseconds(1_002)); + assert!(TimeDelta::milliseconds(-1_001) > TimeDelta::milliseconds(-1_002)); + assert!(TimeDelta::nanoseconds(1_234_567_890) < TimeDelta::nanoseconds(1_234_567_891)); + assert!(TimeDelta::nanoseconds(-1_234_567_890) > TimeDelta::nanoseconds(-1_234_567_891)); + assert!(TimeDelta::milliseconds(i64::MAX) > TimeDelta::milliseconds(i64::MAX - 1)); + assert!(TimeDelta::milliseconds(-i64::MAX) < TimeDelta::milliseconds(-i64::MAX + 1)); } #[test] fn test_duration_checked_ops() { assert_eq!( - Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(0)), - Some(Duration::milliseconds(i64::MAX)) + TimeDelta::milliseconds(i64::MAX).checked_add(&TimeDelta::milliseconds(0)), + Some(TimeDelta::milliseconds(i64::MAX)) ); assert_eq!( - Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), - Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)) + TimeDelta::milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)), + Some(TimeDelta::milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999)) ); - assert!(Duration::milliseconds(i64::MAX) - .checked_add(&Duration::microseconds(1000)) + assert!(TimeDelta::milliseconds(i64::MAX) + .checked_add(&TimeDelta::microseconds(1000)) + .is_none()); + assert!(TimeDelta::milliseconds(i64::MAX) + .checked_add(&TimeDelta::nanoseconds(1)) .is_none()); - assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); assert_eq!( - Duration::milliseconds(-i64::MAX).checked_sub(&Duration::milliseconds(0)), - Some(Duration::milliseconds(-i64::MAX)) + TimeDelta::milliseconds(-i64::MAX).checked_sub(&TimeDelta::milliseconds(0)), + Some(TimeDelta::milliseconds(-i64::MAX)) ); assert_eq!( - Duration::milliseconds(-i64::MAX + 1).checked_sub(&Duration::microseconds(999)), - Some(Duration::milliseconds(-i64::MAX + 2) - Duration::microseconds(1999)) + TimeDelta::milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)), + Some(TimeDelta::milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999)) ); - assert!(Duration::milliseconds(-i64::MAX) - .checked_sub(&Duration::milliseconds(1)) + assert!(TimeDelta::milliseconds(-i64::MAX) + .checked_sub(&TimeDelta::milliseconds(1)) + .is_none()); + assert!(TimeDelta::milliseconds(-i64::MAX) + .checked_sub(&TimeDelta::nanoseconds(1)) .is_none()); - assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); } #[test] fn test_duration_abs() { - assert_eq!(Duration::milliseconds(1300).abs(), Duration::milliseconds(1300)); - assert_eq!(Duration::milliseconds(1000).abs(), Duration::milliseconds(1000)); - assert_eq!(Duration::milliseconds(300).abs(), Duration::milliseconds(300)); - assert_eq!(Duration::milliseconds(0).abs(), Duration::milliseconds(0)); - assert_eq!(Duration::milliseconds(-300).abs(), Duration::milliseconds(300)); - assert_eq!(Duration::milliseconds(-700).abs(), Duration::milliseconds(700)); - assert_eq!(Duration::milliseconds(-1000).abs(), Duration::milliseconds(1000)); - assert_eq!(Duration::milliseconds(-1300).abs(), Duration::milliseconds(1300)); - assert_eq!(Duration::milliseconds(-1700).abs(), Duration::milliseconds(1700)); - assert_eq!(Duration::milliseconds(-i64::MAX).abs(), Duration::milliseconds(i64::MAX)); + assert_eq!(TimeDelta::milliseconds(1300).abs(), TimeDelta::milliseconds(1300)); + assert_eq!(TimeDelta::milliseconds(1000).abs(), TimeDelta::milliseconds(1000)); + assert_eq!(TimeDelta::milliseconds(300).abs(), TimeDelta::milliseconds(300)); + assert_eq!(TimeDelta::milliseconds(0).abs(), TimeDelta::milliseconds(0)); + assert_eq!(TimeDelta::milliseconds(-300).abs(), TimeDelta::milliseconds(300)); + assert_eq!(TimeDelta::milliseconds(-700).abs(), TimeDelta::milliseconds(700)); + assert_eq!(TimeDelta::milliseconds(-1000).abs(), TimeDelta::milliseconds(1000)); + assert_eq!(TimeDelta::milliseconds(-1300).abs(), TimeDelta::milliseconds(1300)); + assert_eq!(TimeDelta::milliseconds(-1700).abs(), TimeDelta::milliseconds(1700)); + assert_eq!(TimeDelta::milliseconds(-i64::MAX).abs(), TimeDelta::milliseconds(i64::MAX)); } #[test] #[allow(clippy::erasing_op)] fn test_duration_mul() { - assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() * i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 0, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 1, Duration::nanoseconds(1)); - assert_eq!(Duration::nanoseconds(1) * 1_000_000_000, Duration::seconds(1)); - assert_eq!(Duration::nanoseconds(1) * -1_000_000_000, -Duration::seconds(1)); - assert_eq!(-Duration::nanoseconds(1) * 1_000_000_000, -Duration::seconds(1)); + assert_eq!(TimeDelta::zero() * i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() * i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 0, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 1, TimeDelta::nanoseconds(1)); + assert_eq!(TimeDelta::nanoseconds(1) * 1_000_000_000, TimeDelta::seconds(1)); + assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::seconds(1)); + assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::seconds(1)); assert_eq!( - Duration::nanoseconds(30) * 333_333_333, - Duration::seconds(10) - Duration::nanoseconds(10) + TimeDelta::nanoseconds(30) * 333_333_333, + TimeDelta::seconds(10) - TimeDelta::nanoseconds(10) ); assert_eq!( - (Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3, - Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3) + (TimeDelta::nanoseconds(1) + TimeDelta::seconds(1) + TimeDelta::days(1)) * 3, + TimeDelta::nanoseconds(3) + TimeDelta::seconds(3) + TimeDelta::days(3) ); - assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3)); - assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3)); + assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::seconds(-3)); + assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::seconds(-3)); } #[test] fn test_duration_div() { - assert_eq!(Duration::zero() / i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() / i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(123_456_789) / 1, Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333)); - assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333)); - assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500)); - assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333)); - assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); + assert_eq!(TimeDelta::zero() / i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() / i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / 1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / -1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / -1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / 1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::seconds(1) / 3, TimeDelta::nanoseconds(333_333_333)); + assert_eq!(TimeDelta::seconds(4) / 3, TimeDelta::nanoseconds(1_333_333_333)); + assert_eq!(TimeDelta::seconds(-1) / 2, TimeDelta::milliseconds(-500)); + assert_eq!(TimeDelta::seconds(1) / -2, TimeDelta::milliseconds(-500)); + assert_eq!(TimeDelta::seconds(-1) / -2, TimeDelta::milliseconds(500)); + assert_eq!(TimeDelta::seconds(-4) / 3, TimeDelta::nanoseconds(-1_333_333_333)); + assert_eq!(TimeDelta::seconds(-4) / -3, TimeDelta::nanoseconds(1_333_333_333)); } #[test] fn test_duration_sum() { - let duration_list_1 = [Duration::zero(), Duration::seconds(1)]; - let sum_1: Duration = duration_list_1.iter().sum(); - assert_eq!(sum_1, Duration::seconds(1)); - - let duration_list_2 = - [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; - let sum_2: Duration = duration_list_2.iter().sum(); - assert_eq!(sum_2, Duration::seconds(17)); - - let duration_arr = - [Duration::zero(), Duration::seconds(1), Duration::seconds(6), Duration::seconds(10)]; - let sum_3: Duration = duration_arr.into_iter().sum(); - assert_eq!(sum_3, Duration::seconds(17)); + let duration_list_1 = [TimeDelta::zero(), TimeDelta::seconds(1)]; + let sum_1: TimeDelta = duration_list_1.iter().sum(); + assert_eq!(sum_1, TimeDelta::seconds(1)); + + let duration_list_2 = [ + TimeDelta::zero(), + TimeDelta::seconds(1), + TimeDelta::seconds(6), + TimeDelta::seconds(10), + ]; + let sum_2: TimeDelta = duration_list_2.iter().sum(); + assert_eq!(sum_2, TimeDelta::seconds(17)); + + let duration_arr = [ + TimeDelta::zero(), + TimeDelta::seconds(1), + TimeDelta::seconds(6), + TimeDelta::seconds(10), + ]; + let sum_3: TimeDelta = duration_arr.into_iter().sum(); + assert_eq!(sum_3, TimeDelta::seconds(17)); } #[test] fn test_duration_fmt() { - assert_eq!(Duration::zero().to_string(), "PT0S"); - assert_eq!(Duration::days(42).to_string(), "P42D"); - assert_eq!(Duration::days(-42).to_string(), "-P42D"); - assert_eq!(Duration::seconds(42).to_string(), "PT42S"); - assert_eq!(Duration::milliseconds(42).to_string(), "PT0.042S"); - assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S"); - assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S"); - assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(), "P7DT6.543S"); - assert_eq!(Duration::seconds(-86_401).to_string(), "-P1DT1S"); - assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S"); - - // the format specifier should have no effect on `Duration` + assert_eq!(TimeDelta::zero().to_string(), "PT0S"); + assert_eq!(TimeDelta::days(42).to_string(), "P42D"); + assert_eq!(TimeDelta::days(-42).to_string(), "-P42D"); + assert_eq!(TimeDelta::seconds(42).to_string(), "PT42S"); + assert_eq!(TimeDelta::milliseconds(42).to_string(), "PT0.042S"); + assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); + assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); + assert_eq!((TimeDelta::days(7) + TimeDelta::milliseconds(6543)).to_string(), "P7DT6.543S"); + assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-P1DT1S"); + assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S"); + + // the format specifier should have no effect on `TimeDelta` assert_eq!( - format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)), + format!("{:30}", TimeDelta::days(1) + TimeDelta::milliseconds(2345)), "P1DT2.345S" ); } #[test] fn test_to_std() { - assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); - assert_eq!(Duration::seconds(86_401).to_std(), Ok(StdDuration::new(86_401, 0))); - assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123_000_000))); - assert_eq!( - Duration::milliseconds(123_765).to_std(), - Ok(StdDuration::new(123, 765_000_000)) - ); - assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); - assert_eq!(MAX.to_std(), Ok(StdDuration::new(9_223_372_036_854_775, 807_000_000))); - assert_eq!(Duration::seconds(-1).to_std(), Err(OutOfRangeError(()))); - assert_eq!(Duration::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::seconds(1).to_std(), Ok(Duration::new(1, 0))); + assert_eq!(TimeDelta::seconds(86_401).to_std(), Ok(Duration::new(86_401, 0))); + assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(Duration::new(0, 123_000_000))); + assert_eq!(TimeDelta::milliseconds(123_765).to_std(), Ok(Duration::new(123, 765_000_000))); + assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(Duration::new(0, 777))); + assert_eq!(MAX.to_std(), Ok(Duration::new(9_223_372_036_854_775, 807_000_000))); + assert_eq!(TimeDelta::seconds(-1).to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); } #[test] fn test_from_std() { - assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); - assert_eq!(Ok(Duration::seconds(86_401)), Duration::from_std(StdDuration::new(86_401, 0))); - assert_eq!( - Ok(Duration::milliseconds(123)), - Duration::from_std(StdDuration::new(0, 123_000_000)) - ); + assert_eq!(Ok(TimeDelta::seconds(1)), TimeDelta::from_std(Duration::new(1, 0))); + assert_eq!(Ok(TimeDelta::seconds(86_401)), TimeDelta::from_std(Duration::new(86_401, 0))); assert_eq!( - Ok(Duration::milliseconds(123_765)), - Duration::from_std(StdDuration::new(123, 765_000_000)) + Ok(TimeDelta::milliseconds(123)), + TimeDelta::from_std(Duration::new(0, 123_000_000)) ); - assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777))); assert_eq!( - Ok(MAX), - Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_000)) + Ok(TimeDelta::milliseconds(123_765)), + TimeDelta::from_std(Duration::new(123, 765_000_000)) ); + assert_eq!(Ok(TimeDelta::nanoseconds(777)), TimeDelta::from_std(Duration::new(0, 777))); + assert_eq!(Ok(MAX), TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_000))); assert_eq!( - Duration::from_std(StdDuration::new(9_223_372_036_854_776, 0)), + TimeDelta::from_std(Duration::new(9_223_372_036_854_776, 0)), Err(OutOfRangeError(())) ); assert_eq!( - Duration::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_001)), + TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_001)), Err(OutOfRangeError(())) ); } #[test] fn test_duration_const() { - const ONE_WEEK: Duration = Duration::weeks(1); - const ONE_DAY: Duration = Duration::days(1); - const ONE_HOUR: Duration = Duration::hours(1); - const ONE_MINUTE: Duration = Duration::minutes(1); - const ONE_SECOND: Duration = Duration::seconds(1); - const ONE_MILLI: Duration = Duration::milliseconds(1); - const ONE_MICRO: Duration = Duration::microseconds(1); - const ONE_NANO: Duration = Duration::nanoseconds(1); - let combo: Duration = ONE_WEEK + const ONE_WEEK: TimeDelta = TimeDelta::weeks(1); + const ONE_DAY: TimeDelta = TimeDelta::days(1); + const ONE_HOUR: TimeDelta = TimeDelta::hours(1); + const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1); + const ONE_SECOND: TimeDelta = TimeDelta::seconds(1); + const ONE_MILLI: TimeDelta = TimeDelta::milliseconds(1); + const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); + const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); + let combo: TimeDelta = ONE_WEEK + ONE_DAY + ONE_HOUR + ONE_MINUTE @@ -1171,26 +1184,26 @@ mod tests { + ONE_MICRO + ONE_NANO; - assert!(ONE_WEEK != Duration::zero()); - assert!(ONE_DAY != Duration::zero()); - assert!(ONE_HOUR != Duration::zero()); - assert!(ONE_MINUTE != Duration::zero()); - assert!(ONE_SECOND != Duration::zero()); - assert!(ONE_MILLI != Duration::zero()); - assert!(ONE_MICRO != Duration::zero()); - assert!(ONE_NANO != Duration::zero()); + assert!(ONE_WEEK != TimeDelta::zero()); + assert!(ONE_DAY != TimeDelta::zero()); + assert!(ONE_HOUR != TimeDelta::zero()); + assert!(ONE_MINUTE != TimeDelta::zero()); + assert!(ONE_SECOND != TimeDelta::zero()); + assert!(ONE_MILLI != TimeDelta::zero()); + assert!(ONE_MICRO != TimeDelta::zero()); + assert!(ONE_NANO != TimeDelta::zero()); assert_eq!( combo, - Duration::seconds(86400 * 7 + 86400 + 3600 + 60 + 1) - + Duration::nanoseconds(1 + 1_000 + 1_000_000) + TimeDelta::seconds(86400 * 7 + 86400 + 3600 + 60 + 1) + + TimeDelta::nanoseconds(1 + 1_000 + 1_000_000) ); } #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { - let duration = Duration::seconds(1); + let duration = TimeDelta::seconds(1); let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); - assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); } } diff --git a/src/lib.rs b/src/lib.rs index 985bb1c805..e9c0d1c867 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -466,13 +466,13 @@ extern crate alloc; mod duration; -pub use duration::Duration; #[cfg(feature = "std")] #[doc(no_inline)] pub use duration::OutOfRangeError; +pub use duration::TimeDelta; -/// Alias of [`Duration`]. -pub type TimeDelta = Duration; +/// Alias of [`TimeDelta`]. +pub type Duration = TimeDelta; use core::fmt; @@ -566,7 +566,7 @@ pub mod serde { #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] pub mod rkyv { pub use crate::datetime::ArchivedDateTime; - pub use crate::duration::ArchivedDuration; + pub use crate::duration::ArchivedTimeDelta; pub use crate::month::ArchivedMonth; pub use crate::naive::date::ArchivedNaiveDate; pub use crate::naive::datetime::ArchivedNaiveDateTime; @@ -577,6 +577,9 @@ pub mod rkyv { pub use crate::offset::local::ArchivedLocal; pub use crate::offset::utc::ArchivedUtc; pub use crate::weekday::ArchivedWeekday; + + /// Alias of [`ArchivedTimeDelta`] + pub type ArchivedDuration = ArchivedTimeDelta; } /// Out of range error type used in various converting APIs From ec7fc642b777a216a077e8bd8b4f6255bd85a603 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 20:19:01 +0100 Subject: [PATCH 678/999] Rename `duration` module to `time_delta` --- src/lib.rs | 8 ++++---- src/naive/datetime/mod.rs | 2 +- src/{duration.rs => time_delta.rs} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename src/{duration.rs => time_delta.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index e9c0d1c867..9be839bd2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -465,11 +465,11 @@ #[cfg(feature = "alloc")] extern crate alloc; -mod duration; +mod time_delta; #[cfg(feature = "std")] #[doc(no_inline)] -pub use duration::OutOfRangeError; -pub use duration::TimeDelta; +pub use time_delta::OutOfRangeError; +pub use time_delta::TimeDelta; /// Alias of [`TimeDelta`]. pub type Duration = TimeDelta; @@ -566,7 +566,6 @@ pub mod serde { #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] pub mod rkyv { pub use crate::datetime::ArchivedDateTime; - pub use crate::duration::ArchivedTimeDelta; pub use crate::month::ArchivedMonth; pub use crate::naive::date::ArchivedNaiveDate; pub use crate::naive::datetime::ArchivedNaiveDateTime; @@ -576,6 +575,7 @@ pub mod rkyv { #[cfg(feature = "clock")] pub use crate::offset::local::ArchivedLocal; pub use crate::offset::utc::ArchivedUtc; + pub use crate::time_delta::ArchivedTimeDelta; pub use crate::weekday::ArchivedWeekday; /// Alias of [`ArchivedTimeDelta`] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 300c808e1e..c5c8953c16 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,13 +13,13 @@ use core::{fmt, str}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::NANOS_PER_SEC; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; +use crate::time_delta::NANOS_PER_SEC; use crate::{ expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone, Timelike, Weekday, diff --git a/src/duration.rs b/src/time_delta.rs similarity index 100% rename from src/duration.rs rename to src/time_delta.rs From 32fa2221446a9ddd44317a24b3c136e5fba3f383 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 20:50:18 +0100 Subject: [PATCH 679/999] Update main documentation --- src/lib.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9be839bd2d..18d1faac73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,23 +52,18 @@ //! //! ## Overview //! -//! ### Duration -//! -//! Chrono currently uses its own [`Duration`] type to represent the magnitude -//! of a time span. Since this has the same name as the newer, standard type for -//! duration, the reference will refer this type as `OldDuration`. -//! -//! Note that this is an "accurate" duration represented as seconds and -//! nanoseconds and does not represent "nominal" components such as days or -//! months. -//! -//! Chrono does not yet natively support -//! the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -//! but it will be supported in the future. -//! Meanwhile you can convert between two types with -//! [`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -//! and -//! [`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) +//! ### Time delta / Duration +//! +//! Chrono has a [`TimeDelta`] type to represent the magnitude of a time span. This is an +//! "accurate" duration represented as seconds and nanoseconds, and does not represent "nominal" +//! components such as days or months. +//! +//! The [`TimeDelta`] type was previously named `Duration` (and is still available as a type alias +//! with that name). A notable difference with the similar [`core::time::Duration`] is that it is a +//! signed value instead of unsigned. +//! +//! Chrono currently only supports a small number of operations with [`core::time::Duration`] . +//! You can convert between both types with the [`TimeDelta::from_std`] and [`TimeDelta::to_std`] //! methods. //! //! ### Date and Time From ce97c2ef52060c836ce63deec94911e64b9dca1e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2024 20:51:51 +0100 Subject: [PATCH 680/999] `TimeDelta` is not a full ISO 8601 duration --- src/time_delta.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index f2b3492d74..a2c737969e 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -40,7 +40,7 @@ const SECS_PER_DAY: i64 = 86_400; /// The number of (non-leap) seconds in a week. const SECS_PER_WEEK: i64 = 604_800; -/// ISO 8601 time duration with nanosecond precision. +/// Time duration with nanosecond precision. /// /// This also allows for negative durations; see individual methods for details. /// From ef9a4c9539da5e463a0b8c9dd45920f3a265f421 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 5 Feb 2024 13:38:51 +0100 Subject: [PATCH 681/999] Sync with 0.5 branch --- Cargo.toml | 1 - src/offset/mod.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c8d8304c7..08a963acce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ arbitrary = { version = "1.0.0", features = ["derive"], optional = true } wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API - [target.'cfg(windows)'.dependencies] windows-targets = { version = "0.52", optional = true } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 03764c38e3..11928b42dd 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -211,7 +211,7 @@ pub trait Offset: Sized + Clone + fmt::Debug { /// The time zone. /// -/// The methods here are the primary constructors for [`Date`] and [`DateTime`] types. +/// The methods here are the primary constructors for the [`DateTime`] type. pub trait TimeZone: Sized + Clone { /// An associated offset type. /// This type is used to store the actual offset in date and time types. @@ -347,7 +347,7 @@ pub trait TimeZone: Sized + Clone { /// and the number of nanoseconds since the last whole non-leap second. /// /// The nanosecond part can exceed 1,000,000,000 in order to represent a - /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Panics @@ -364,7 +364,7 @@ pub trait TimeZone: Sized + Clone { /// and the number of nanoseconds since the last whole non-leap second. /// /// The nanosecond part can exceed 1,000,000,000 in order to represent a - /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Errors From c1f04222cacc16393da218aeb2091b9d90bc1f2f Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 2 Feb 2024 13:52:01 +0100 Subject: [PATCH 682/999] Remove check for DurationExceedsTimestamp This check is not necessary and prevented rounding and truncation from working correctly on timestamps close to the Unix epoch. --- src/round.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/round.rs b/src/round.rs index 281816330e..e97913fd38 100644 --- a/src/round.rs +++ b/src/round.rs @@ -177,9 +177,6 @@ where return Err(RoundingError::DurationExceedsLimit); } let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); - } if span == 0 { return Ok(original); } @@ -216,9 +213,6 @@ where return Err(RoundingError::DurationExceedsLimit); } let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); - } let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), @@ -769,4 +763,26 @@ mod tests { let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } + + #[test] + fn test_duration_trunc_close_to_epoch() { + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + let span = Duration::minutes(15); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + let span = Duration::minutes(15); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00"); + } + + #[test] + fn test_duration_round_close_to_epoch() { + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + let span = Duration::minutes(15); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + let span = Duration::minutes(15); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + } } From 8eb92d96c41db779d0a28445c38471106a9037cb Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 2 Feb 2024 14:16:57 +0100 Subject: [PATCH 683/999] Replace DurationExceedsTimestamp example with a note. --- src/round.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/round.rs b/src/round.rs index e97913fd38..abea0c988f 100644 --- a/src/round.rs +++ b/src/round.rs @@ -231,15 +231,7 @@ where pub enum RoundingError { /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch. /// - /// ``` rust - /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; - /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); - /// - /// assert_eq!( - /// dt.duration_round(TimeDelta::days(365)), - /// Err(RoundingError::DurationExceedsTimestamp), - /// ); - /// ``` + /// Note: this error is not produced anymore. DurationExceedsTimestamp, /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. @@ -767,22 +759,22 @@ mod tests { #[test] fn test_duration_trunc_close_to_epoch() { let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); - let span = Duration::minutes(15); + let span = TimeDelta::minutes(15); assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); - let span = Duration::minutes(15); + let span = TimeDelta::minutes(15); assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00"); } #[test] fn test_duration_round_close_to_epoch() { let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); - let span = Duration::minutes(15); + let span = TimeDelta::minutes(15); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); - let span = Duration::minutes(15); + let span = TimeDelta::minutes(15); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); } } From 5656a9ed67eaac5f552a40d1122e4d0ca100487e Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 2 Feb 2024 15:41:25 +0100 Subject: [PATCH 684/999] Add a test for duration_round close to min / max --- src/round.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/round.rs b/src/round.rs index abea0c988f..315d45386d 100644 --- a/src/round.rs +++ b/src/round.rs @@ -758,23 +758,40 @@ mod tests { #[test] fn test_duration_trunc_close_to_epoch() { - let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); let span = TimeDelta::minutes(15); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); - let span = TimeDelta::minutes(15); assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00"); } #[test] fn test_duration_round_close_to_epoch() { - let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); let span = TimeDelta::minutes(15); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); - let span = TimeDelta::minutes(15); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + } + + #[test] + fn test_duration_round_close_to_min_max() { + let span = TimeDelta::nanoseconds(i64::MAX); + + let dt = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 - 1).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1677-09-21 00:12:43.145224193"); + + let dt = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 + 1).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 + 1).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "2262-04-11 23:47:16.854775807"); + + let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 - 1).unwrap(); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); } } From 51cb026a588e4b28a20a2e4c3c7e22ba8a134f2e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 15:47:30 +0200 Subject: [PATCH 685/999] Replace `86_400` with `MAX_RFC3339_OFFSET` --- src/format/parse.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 04628ac861..40bc7c8e85 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -216,7 +216,8 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); - if offset <= -86_400 || offset >= 86_400 { + const MAX_RFC3339_OFFSET: i32 = 86_400; + if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { return Err(OUT_OF_RANGE); } parsed.set_offset(i64::from(offset))?; From 9c07a015df43827e1ffb9db629295c5beb015e72 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Feb 2024 13:07:55 +0100 Subject: [PATCH 686/999] Make range check more accurate, and add comment why it is not useless --- src/format/parse.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 40bc7c8e85..dea3d91fe0 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -216,7 +216,11 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); - const MAX_RFC3339_OFFSET: i32 = 86_400; + // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant. + // But it is possible to read the offset directly from `Parsed`. We want to only successfully + // populate `Parsed` if the input is fully valid RFC 3339. + // Max for the hours field is `23`, and for the minutes field `59`. + const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60; if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { return Err(OUT_OF_RANGE); } From 42db00a981d7535f5ab382d81f9de8c8cb026314 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Feb 2024 17:02:47 +0100 Subject: [PATCH 687/999] Remove incomplete parsing support for `-0000` offset --- src/format/parse.rs | 14 +++++--------- src/format/scan.rs | 44 +++++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index dea3d91fe0..05ebcf3119 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -142,10 +142,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } s = scan::space(s)?; // mandatory - if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { - // only set the offset when it is definitely known (i.e. not `-0000`) - parsed.set_offset(i64::from(offset))?; - } + parsed.set_offset(i64::from(try_consume!(scan::timezone_offset_2822(s))))?; // optional comments while let Ok((s_out, ())) = scan::comment_2822(s) { @@ -1644,7 +1641,6 @@ mod tests { ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone // named timezones that have specific timezone offsets // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), @@ -1666,14 +1662,14 @@ mod tests { ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), // named single-letter timezone "J" is specifically not valid - ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 J", Err(INVALID)), ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(INVALID)), // bad offset: zulu not allowed ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(INVALID)), // bad named timezone ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; diff --git a/src/format/scan.rs b/src/format/scan.rs index 45b5bcbec0..1ab87b9dd5 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -287,37 +287,39 @@ where /// See [RFC 2822 Section 4.3]. /// /// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 -pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> { // tries to parse legacy time zone names let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len()); if upto > 0 { let name = &s.as_bytes()[..upto]; let s = &s[upto..]; - let offset_hours = |o| Ok((s, Some(o * 3600))); - if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") { - offset_hours(0) + let offset_hours = |o| Ok((s, o * 3600)); + // RFC 2822 requires support for some named North America timezones, a small subset of all + // named timezones. + if name.eq_ignore_ascii_case(b"gmt") + || name.eq_ignore_ascii_case(b"ut") + || name.eq_ignore_ascii_case(b"z") + { + return offset_hours(0); } else if name.eq_ignore_ascii_case(b"edt") { - offset_hours(-4) + return offset_hours(-4); } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { - offset_hours(-5) + return offset_hours(-5); } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { - offset_hours(-6) + return offset_hours(-6); } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { - offset_hours(-7) + return offset_hours(-7); } else if name.eq_ignore_ascii_case(b"pst") { - offset_hours(-8) + return offset_hours(-8); } else if name.len() == 1 { - match name[0] { + if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] { // recommended by RFC 2822: consume but treat it as -0000 - b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), - _ => Ok((s, None)), + return Ok((s, 0)); } - } else { - Ok((s, None)) } + Err(INVALID) } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?; - Ok((s_, Some(offset))) + timezone_offset(s, |s| Ok(s), false, false, false) } } @@ -396,11 +398,11 @@ mod tests { #[test] fn test_timezone_offset_2822() { - assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", Some(-21600))); - assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", Some(-28800))); - assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", Some(-25200))); - assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", Some(-57060))); - assert_eq!(timezone_offset_2822("Gp").unwrap(), ("", None)); + assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", -21600)); + assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", -28800)); + assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", -25200)); + assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", -57060)); + assert_eq!(timezone_offset_2822("Gp"), Err(INVALID)); } #[test] From ded9fe158574e7e3133c6498d828c51a6c9becc8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Feb 2024 22:10:08 +0100 Subject: [PATCH 688/999] Move `TOO_LONG` error out of `parse_internal` --- src/format/parse.rs | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 05ebcf3119..26340d3d13 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -10,8 +10,8 @@ use core::usize; use super::scan; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; -use super::{ParseError, ParseErrorKind, ParseResult}; -use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; +use super::{ParseError, ParseResult}; +use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { @@ -247,7 +247,11 @@ where I: Iterator, B: Borrow>, { - parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) + match parse_internal(parsed, s, items) { + Ok("") => Ok(()), + Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error + Err((_, e)) => Err(e), + } } /// Tries to parse given string into `parsed` with given formatting items. @@ -273,11 +277,7 @@ where I: Iterator, B: Borrow>, { - match parse_internal(parsed, s, items) { - Ok(s) => Ok(s), - Err((s, ParseError(ParseErrorKind::TooLong))) => Ok(s), - Err((_s, e)) => Err(e), - } + parse_internal(parsed, s, items).map_err(|(_s, e)| e) } fn parse_internal<'a, 'b, I, B>( @@ -505,13 +505,7 @@ where } } } - - // if there are trailling chars, it is an error - if !s.is_empty() { - Err((s, TOO_LONG)) - } else { - Ok(s) - } + Ok(s) } /// Accepts a relaxed form of RFC3339. @@ -570,11 +564,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult Item::Space(""), ]; - s = match parse_internal(parsed, s, DATE_ITEMS.iter()) { - Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => remainder, - Err((_s, e)) => return Err(e), - Ok(_) => return Err(NOT_ENOUGH), - }; + s = parse_internal(parsed, s, DATE_ITEMS.iter()).map_err(|(_s, e)| e)?; s = match s.as_bytes().first() { Some(&b't' | &b'T' | &b' ') => &s[1..], @@ -582,11 +572,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult None => return Err(TOO_SHORT), }; - s = match parse_internal(parsed, s, TIME_ITEMS.iter()) { - Err((s, e)) if e.0 == ParseErrorKind::TooLong => s, - Err((_s, e)) => return Err(e), - Ok(_) => return Err(NOT_ENOUGH), - }; + s = parse_internal(parsed, s, TIME_ITEMS.iter()).map_err(|(_s, e)| e)?; s = s.trim_start(); let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { (&s[3..], 0) From 73388ca42afbd173c51e10482b4d48ef2211e6b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Feb 2024 21:27:18 +0100 Subject: [PATCH 689/999] Update pure-rust-locales to 0.8.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 08a963acce..d086ad38d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ __internal_bench = [] num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.7", optional = true } +pure-rust-locales = { version = "0.8", optional = true } rkyv = { version = "0.7.43", optional = true, default-features = false } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } From 5fb4fabd13af0bda9fcfb472ca9f01674387c4c5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Feb 2024 21:27:46 +0100 Subject: [PATCH 690/999] Add test for localized formatting of `%X` --- src/format/strftime.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 557034d15a..042f0b43e2 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -904,6 +904,22 @@ mod tests { ); } + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_time() { + let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap(); + // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+ + assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM"); + assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM"); + assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ"); + assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ"); + } + #[test] #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))] fn test_type_sizes() { From acb693ac6428ee31daef8206e5aae6d15d88fa09 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 20:20:47 +0200 Subject: [PATCH 691/999] Windows: rewrite using `GetTimeZoneInformationForYear` And add test against previous implementation --- src/naive/datetime/mod.rs | 17 ++ src/offset/local/mod.rs | 259 +++++++++++++++++++++++ src/offset/local/win_bindings.rs | 20 ++ src/offset/local/win_bindings.txt | 1 + src/offset/local/windows.rs | 335 ++++++++++++++++++++---------- 5 files changed, 526 insertions(+), 106 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c5c8953c16..d767483571 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -790,6 +790,23 @@ impl NaiveDateTime { NaiveDateTime { date, time } } + /// Subtracts given `FixedOffset` from the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + #[allow(unused)] // currently only used in `Local` but not on all platforms + pub(crate) fn overflowing_sub_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + /// Subtracts given `TimeDelta` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 724f3597ad..51bd852ef7 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -3,6 +3,9 @@ //! The local (system) time zone. +#[cfg(windows)] +use std::cmp::Ordering; + #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -183,11 +186,96 @@ impl TimeZone for Local { } } +#[cfg(windows)] +#[derive(Copy, Clone, Eq, PartialEq)] +struct Transition { + transition_utc: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, +} + +#[cfg(windows)] +impl Transition { + fn new( + transition_local: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, + ) -> Transition { + // It is no problem if the transition time in UTC falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let transition_utc = transition_local.overflowing_sub_offset(offset_before); + Transition { transition_utc, offset_before, offset_after } + } +} + +#[cfg(windows)] +impl PartialOrd for Transition { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.transition_utc.cmp(&other.transition_utc)) + } +} + +#[cfg(windows)] +impl Ord for Transition { + fn cmp(&self, other: &Self) -> Ordering { + self.transition_utc.cmp(&other.transition_utc) + } +} + +// Calculate the time in UTC given a local time and transitions. +// `transitions` must be sorted. +#[cfg(windows)] +fn lookup_with_dst_transitions( + transitions: &[Transition], + dt: NaiveDateTime, +) -> LocalResult { + for t in transitions.iter() { + // A transition can result in the wall clock time going forward (creating a gap) or going + // backward (creating a fold). We are interested in the earliest and latest wall time of the + // transition, as this are the times between which `dt` does may not exist or is ambiguous. + // + // It is no problem if the transition times falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let (offset_min, offset_max) = + match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() { + true => (t.offset_before, t.offset_after), + false => (t.offset_after, t.offset_before), + }; + let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min); + let wall_latest = t.transition_utc.overflowing_add_offset(offset_max); + + if dt < wall_earliest { + return LocalResult::Single(t.offset_before); + } else if dt <= wall_latest { + return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) { + Ordering::Equal => LocalResult::Single(t.offset_before), + Ordering::Less => LocalResult::Ambiguous(t.offset_before, t.offset_after), + Ordering::Greater => { + if dt == wall_earliest { + LocalResult::Single(t.offset_before) + } else if dt == wall_latest { + LocalResult::Single(t.offset_after) + } else { + LocalResult::None + } + } + }; + } + } + LocalResult::Single(transitions.last().unwrap().offset_after) +} + #[cfg(test)] mod tests { use super::Local; + #[cfg(windows)] + use crate::offset::local::{lookup_with_dst_transitions, Transition}; use crate::offset::TimeZone; use crate::{Datelike, TimeDelta, Utc}; + #[cfg(windows)] + use crate::{FixedOffset, LocalResult, NaiveDate, NaiveDateTime}; #[test] fn verify_correct_offsets() { @@ -264,6 +352,177 @@ mod tests { } } + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions() { + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }; + + #[track_caller] + #[allow(clippy::too_many_arguments)] + fn compare_lookup( + transitions: &[Transition], + y: i32, + m: u32, + d: u32, + h: u32, + n: u32, + s: u32, + result: LocalResult, + ) { + let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + assert_eq!(lookup_with_dst_transitions(transitions, dt), result); + } + + // dst transition before std transition + // dst offset > std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, LocalResult::Single(std)); + + // std transition before dst transition + // dst offset > std offset + let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, LocalResult::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::None); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, LocalResult::Single(dst)); + + // dst transition before std transition + // dst offset < std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, LocalResult::None); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + + // std transition before dst transition + // dst offset < std offset + let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap(); + let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, LocalResult::None); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); + + // offset stays the same + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + + // single transition + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); + } + + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions_limits() { + // Transition beyond UTC year end doesn't panic in year of `NaiveDate::MAX` + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst), + Transition::new(NaiveDateTime::MAX, dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()), + LocalResult::Single(std) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()), + LocalResult::Single(dst) + ); + // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX), + LocalResult::Ambiguous(dst, std) + ); + + // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN` + let std = FixedOffset::west_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MIN, std, dst), + Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()), + LocalResult::Single(dst) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()), + LocalResult::Single(std) + ); + // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN), + LocalResult::Ambiguous(std, dst) + ); + } + #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 592726c732..7574fb3317 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,10 +1,30 @@ // Bindings generated by `windows-bindgen` 0.52.0 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] +::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); ::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); ::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); ::windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); pub type BOOL = i32; +pub type BOOLEAN = u8; +#[repr(C)] +pub struct DYNAMIC_TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, + pub TimeZoneKeyName: [u16; 128], + pub DynamicDaylightTimeDisabled: BOOLEAN, +} +impl ::core::marker::Copy for DYNAMIC_TIME_ZONE_INFORMATION {} +impl ::core::clone::Clone for DYNAMIC_TIME_ZONE_INFORMATION { + fn clone(&self) -> Self { + *self + } +} #[repr(C)] pub struct FILETIME { pub dwLowDateTime: u32, diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt index ce5d3acfe2..7fb3e2fa1c 100644 --- a/src/offset/local/win_bindings.txt +++ b/src/offset/local/win_bindings.txt @@ -1,6 +1,7 @@ --out src/offset/local/win_bindings.rs --config flatten sys --filter + Windows.Win32.System.Time.GetTimeZoneInformationForYear Windows.Win32.System.Time.SystemTimeToFileTime Windows.Win32.System.Time.SystemTimeToTzSpecificLocalTime Windows.Win32.System.Time.TzSpecificLocalTimeToSystemTime diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 539b6880fd..cee09eca8b 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -8,132 +8,255 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::mem::MaybeUninit; -use std::io::Error; +use std::cmp::Ordering; +use std::convert::TryFrom; +use std::mem::MaybeUninit; use std::ptr; -use std::result::Result; -use super::win_bindings::{ - SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, - FILETIME, SYSTEMTIME, -}; +use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; -use super::FixedOffset; -use crate::{Datelike, LocalResult, NaiveDateTime, Timelike}; - -/// This macro calls a Windows API FFI and checks whether the function errored with the provided error_id. If an error returns, -/// the macro will return an `Error::last_os_error()`. -/// -/// # Safety -/// -/// The provided error ID must align with the provided Windows API, providing the wrong ID could lead to UB. -macro_rules! windows_sys_call { - ($name:ident($($arg:expr),*), $error_id:expr) => { - if $name($($arg),*) == $error_id { - return Err(Error::last_os_error()); - } - } -} - -const HECTONANOSECS_IN_SEC: i64 = 10_000_000; -const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; +use crate::offset::local::{lookup_with_dst_transitions, Transition}; +use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; +// We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates +// as Chrono. Also it really isn't that difficult to work out the correct offset from the provided +// DST rules. +// +// This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC +// falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is +// very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`. pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { - offset(utc, false) + // Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be + // using the rules for the year of the corresponding local time. But this matches what + // `SystemTimeToTzSpecificLocalTime` is documented to do. + let tz_info = match TzInfo::for_year(utc.year()) { + Some(tz_info) => tz_info, + None => return LocalResult::None, + }; + let offset = match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + if dst_transition_utc < std_transition_utc { + match utc >= &dst_transition_utc && utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } else { + match utc >= &std_transition_utc && utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + } + (Some(std_transition), None) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + match utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } + (None, Some(dst_transition)) => { + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + match utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + (None, None) => tz_info.std_offset, + }; + LocalResult::Single(offset) } +// We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle +// ambiguous cases (during a DST transition). Instead we get the timezone information for the +// current year and compute it ourselves, like we do on Unix. pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { - offset(local, true) -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -pub(super) fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { - let naive_sys_time = system_time_from_naive_date_time(d); - - let local_sys_time = match local { - false => from_utc_time(naive_sys_time), - true => from_local_time(naive_sys_time), + let tz_info = match TzInfo::for_year(local.year()) { + Some(tz_info) => tz_info, + None => return LocalResult::None, }; - - if let Ok(offset) = local_sys_time { - return LocalResult::Single(offset); + // Create a sorted slice of transitions and use `lookup_with_dst_transitions`. + match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition = + Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset); + let dst_transition = + Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset); + let transitions = match std_transition.cmp(&dst_transition) { + Ordering::Less => [std_transition, dst_transition], + Ordering::Greater => [dst_transition, std_transition], + Ordering::Equal => { + // This doesn't make sense. Let's just return the standard offset. + return LocalResult::Single(tz_info.std_offset); + } + }; + lookup_with_dst_transitions(&transitions, *local) + } + (Some(std_transition), None) => { + let transitions = + [Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, Some(dst_transition)) => { + let transitions = + [Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, None) => return LocalResult::Single(tz_info.std_offset), } - LocalResult::None -} - -fn from_utc_time(utc_time: SYSTEMTIME) -> Result { - let local_time = utc_to_local_time(&utc_time)?; - let utc_secs = system_time_as_unix_seconds(&utc_time)?; - let local_secs = system_time_as_unix_seconds(&local_time)?; - let offset = (local_secs - utc_secs) as i32; - Ok(FixedOffset::east_opt(offset).unwrap()) } -fn from_local_time(local_time: SYSTEMTIME) -> Result { - let utc_time = local_to_utc_time(&local_time)?; - let utc_secs = system_time_as_unix_seconds(&utc_time)?; - let local_secs = system_time_as_unix_seconds(&local_time)?; - let offset = (local_secs - utc_secs) as i32; - Ok(FixedOffset::east_opt(offset).unwrap()) +// The basis for Windows timezone and DST support has been in place since Windows 2000. It does not +// allow for complex rules like the IANA timezone database: +// - A timezone has the same base offset the whole year. +// - There seem to be either zero or two DST transitions (but we support having just one). +// - As of Vista(?) only years from 2004 until a few years into the future are supported. +// - All other years get the base settings, which seem to be that of the current year. +// +// These details don't matter much, we just work with the offsets and transition dates Windows +// returns through `GetTimeZoneInformationForYear` for a particular year. +struct TzInfo { + // Offset from UTC during standard time. + std_offset: FixedOffset, + // Offset from UTC during daylight saving time. + dst_offset: FixedOffset, + // Transition from standard time to daylight saving time, given in local standard time. + std_transition: Option, + // Transition from daylight saving time to standard time, given in local daylight saving time. + dst_transition: Option, } -fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { - SYSTEMTIME { - // Valid values: 1601-30827 - wYear: dt.year() as u16, - // Valid values:1-12 - wMonth: dt.month() as u16, - // Valid values: 0-6, starting Sunday. - // NOTE: enum returns 1-7, starting Monday, so we are - // off here, but this is not currently used in local. - wDayOfWeek: dt.weekday() as u16, - // Valid values: 1-31 - wDay: dt.day() as u16, - // Valid values: 0-23 - wHour: dt.hour() as u16, - // Valid values: 0-59 - wMinute: dt.minute() as u16, - // Valid values: 0-59 - wSecond: dt.second() as u16, - // Valid values: 0-999 - wMilliseconds: 0, +impl TzInfo { + fn for_year(year: i32) -> Option { + // The API limits years to 1601..=30827. + // Working with timezones and daylight saving time this far into the past or future makes + // little sense. But whatever is extrapolated for 1601 or 30827 is what can be extrapolated + // for years beyond. + let ref_year = year.clamp(1601, 30827) as u16; + let tz_info = unsafe { + let mut tz_info = MaybeUninit::::uninit(); + if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 { + return None; + } + tz_info.assume_init() + }; + Some(TzInfo { + std_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.StandardBias) * 60)?, + dst_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.DaylightBias) * 60)?, + std_transition: system_time_from_naive_date_time(tz_info.StandardDate, year), + dst_transition: system_time_from_naive_date_time(tz_info.DaylightDate, year), + }) } } -pub(crate) fn local_to_utc_time(local: &SYSTEMTIME) -> Result { - let mut sys_time = MaybeUninit::::uninit(); - unsafe { - windows_sys_call!( - TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()), - 0 - ) +fn system_time_from_naive_date_time(st: SYSTEMTIME, year: i32) -> Option { + if st.wYear == 0 && st.wMonth == 0 { + return None; // No DST transitions for this year in this timezone. + } + let time = NaiveTime::from_hms_milli_opt( + st.wHour as u32, + st.wMinute as u32, + st.wSecond as u32, + st.wMilliseconds as u32, + )?; + // In Chrono's Weekday, Monday is 0 whereas in SYSTEMTIME Monday is 1 and Sunday is 0. + // Therefore we move back one day after converting the u16 value to a Weekday. + let day_of_week = Weekday::try_from(u8::try_from(st.wDayOfWeek).ok()?).ok()?.pred(); + if st.wYear != 0 { + return NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32) + .map(|d| d.and_time(time)); + } + let date = if let Some(date) = + NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, st.wDay as u8) + { + date + } else if st.wDay == 5 { + NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, 4)? + } else { + return None; }; - // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can - // assume the value is initialized. - Ok(unsafe { sys_time.assume_init() }) + Some(date.and_time(time)) } -pub(crate) fn utc_to_local_time(utc_time: &SYSTEMTIME) -> Result { - let mut local = MaybeUninit::::uninit(); - unsafe { - windows_sys_call!( - SystemTimeToTzSpecificLocalTime(ptr::null(), utc_time, local.as_mut_ptr()), - 0 - ) +#[cfg(test)] +mod tests { + use crate::offset::local::win_bindings::{ + SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, FILETIME, SYSTEMTIME, }; - // SAFETY: SystemTimeToTzSpecificLocalTime must have succeeded at this point, so we can - // assume the value is initialized. - Ok(unsafe { local.assume_init() }) -} + use crate::{DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime}; + use crate::{Datelike, TimeZone, Timelike}; + use std::mem::MaybeUninit; + use std::ptr; -/// Returns a i64 value representing the unix seconds conversion of the current `WinSystemTime`. -pub(crate) fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> Result { - let mut init = MaybeUninit::::uninit(); - unsafe { windows_sys_call!(SystemTimeToFileTime(st, init.as_mut_ptr()), 0) } - // SystemTimeToFileTime must have succeeded at this point, so we can assum the value is - // initalized. - let filetime = unsafe { init.assume_init() }; - let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); - let unix_secs = (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC; - Ok(unix_secs) + #[test] + fn verify_against_tz_specific_local_time_to_system_time() { + // The implementation in Windows itself is the source of truth on how to work with the OS + // timezone information. This test compares for every hour over a period of 125 years our + // implementation to `TzSpecificLocalTimeToSystemTime`. + // + // This uses parts of a previous Windows `Local` implementation in chrono. + fn from_local_time(dt: &NaiveDateTime) -> DateTime { + let st = system_time_from_naive_date_time(dt); + let utc_time = local_to_utc_time(&st); + let utc_secs = system_time_as_unix_seconds(&utc_time); + let local_secs = system_time_as_unix_seconds(&st); + let offset = (local_secs - utc_secs) as i32; + let offset = FixedOffset::east_opt(offset).unwrap(); + DateTime::from_naive_utc_and_offset(*dt - offset, offset) + } + fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { + SYSTEMTIME { + // Valid values: 1601-30827 + wYear: dt.year() as u16, + // Valid values:1-12 + wMonth: dt.month() as u16, + // Valid values: 0-6, starting Sunday. + // NOTE: enum returns 1-7, starting Monday, so we are + // off here, but this is not currently used in local. + wDayOfWeek: dt.weekday() as u16, + // Valid values: 1-31 + wDay: dt.day() as u16, + // Valid values: 0-23 + wHour: dt.hour() as u16, + // Valid values: 0-59 + wMinute: dt.minute() as u16, + // Valid values: 0-59 + wSecond: dt.second() as u16, + // Valid values: 0-999 + wMilliseconds: 0, + } + } + fn local_to_utc_time(local: &SYSTEMTIME) -> SYSTEMTIME { + let mut sys_time = MaybeUninit::::uninit(); + unsafe { TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()) }; + // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can + // assume the value is initialized. + unsafe { sys_time.assume_init() } + } + const HECTONANOSECS_IN_SEC: i64 = 10_000_000; + const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; + fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> i64 { + let mut init = MaybeUninit::::uninit(); + unsafe { + SystemTimeToFileTime(st, init.as_mut_ptr()); + } + // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is + // initalized. + let filetime = unsafe { init.assume_init() }; + let bit_shift = + ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); + (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC + } + + let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 30, 0).unwrap(); + + while date.year() < 2078 { + // Windows doesn't handle non-existing dates, it just treats it as valid. + if let Some(our_result) = Local.from_local_datetime(&date).earliest() { + assert_eq!(from_local_time(&date), our_result); + } + date += Duration::hours(1); + } + } } From fc67f3e6e0b41635bdf93b45ca09b8ac54672bb6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 14:42:44 +0200 Subject: [PATCH 692/999] Remove obsolete test --- src/datetime/tests.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 88fcd80dec..1fddd62ec1 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1499,41 +1499,6 @@ fn test_core_duration_max() { utc_dt += Duration::MAX; } -#[test] -#[cfg(all(target_os = "windows", feature = "clock"))] -fn test_from_naive_date_time_windows() { - let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); - - let max_year = NaiveDate::from_ymd_opt(30827, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); - - let too_low_year = - NaiveDate::from_ymd_opt(1600, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap(); - - let too_high_year = NaiveDate::from_ymd_opt(30829, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap(); - - let _ = Local.from_utc_datetime(&min_year); - let _ = Local.from_utc_datetime(&max_year); - - let _ = Local.from_local_datetime(&min_year); - let _ = Local.from_local_datetime(&max_year); - - let local_too_low = Local.from_local_datetime(&too_low_year); - let local_too_high = Local.from_local_datetime(&too_high_year); - - assert_eq!(local_too_low, LocalResult::None); - assert_eq!(local_too_high, LocalResult::None); - - let err = std::panic::catch_unwind(|| { - Local.from_utc_datetime(&too_low_year); - }); - assert!(err.is_err()); - - let err = std::panic::catch_unwind(|| { - Local.from_utc_datetime(&too_high_year); - }); - assert!(err.is_err()); -} - #[test] #[cfg(feature = "clock")] fn test_datetime_local_from_preserves_offset() { From 0ef34e4da331f8f3a580b24c429c27170e5a3d05 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 14:43:10 +0200 Subject: [PATCH 693/999] Extend test to more distant dates --- src/offset/local/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 51bd852ef7..37e78898c9 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -292,8 +292,7 @@ mod tests { #[test] fn verify_correct_offsets_distant_past() { - // let distant_past = Local::now() - TimeDelta::days(365 * 100); - let distant_past = Local::now() - TimeDelta::days(250 * 31); + let distant_past = Local::now() - TimeDelta::days(365 * 500); let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); @@ -306,7 +305,7 @@ mod tests { #[test] fn verify_correct_offsets_distant_future() { - let distant_future = Local::now() + TimeDelta::days(250 * 31); + let distant_future = Local::now() + TimeDelta::days(365 * 35000); let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); From d1cf0e93935f5c41af1ae91678c7ea7d8478bb02 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 19 Apr 2023 07:26:38 +0200 Subject: [PATCH 694/999] Add test for issue 651 --- src/datetime/tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 1fddd62ec1..be83955ee7 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1264,6 +1264,18 @@ fn test_datetime_from_timestamp_millis() { ); } +#[test] +#[cfg(feature = "clock")] +fn test_datetime_before_windows_api_limits() { + // dt corresponds to `FILETIME = 147221225472` from issue 651. + // (https://github.com/chronotope/chrono/issues/651) + // This used to fail on Windows for timezones with an offset of -5:00 or greater. + // The API limits years to 1601..=30827. + let dt = NaiveDate::from_ymd_opt(1601, 1, 1).unwrap().and_hms_milli_opt(4, 5, 22, 122).unwrap(); + let local_dt = Local.from_utc_datetime(&dt); + dbg!(local_dt); +} + #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { From be6af79ae5258cc0720a270816474cdbf761cacf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 10 Feb 2024 19:34:11 +0100 Subject: [PATCH 695/999] Make `Display` format of `TimeDelta` conform better to ISO 8601 --- src/time_delta.rs | 52 ++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index a2c737969e..e5e22b69ef 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -538,27 +538,30 @@ impl fmt::Display for TimeDelta { // but we need to print it anyway. let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; - let days = abs.secs / SECS_PER_DAY; - let secs = abs.secs - days * SECS_PER_DAY; - let hasdate = days != 0; - let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; - write!(f, "{}P", sign)?; - - if hasdate { - write!(f, "{}D", days)?; + // Plenty of ways to encode an empty string. `P0D` is short and not too strange. + if abs.secs == 0 && abs.nanos == 0 { + return f.write_str("0D"); } - if hastime { - if abs.nanos == 0 { - write!(f, "T{}S", secs)?; - } else if abs.nanos % NANOS_PER_MILLI == 0 { - write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?; - } else if abs.nanos % NANOS_PER_MICRO == 0 { - write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?; - } else { - write!(f, "T{}.{:09}S", secs, abs.nanos)?; + + f.write_fmt(format_args!("T{}", abs.secs))?; + + if abs.nanos > 0 { + // Count the number of significant digits, while removing all trailing zero's. + let mut figures = 9usize; + let mut fraction_digits = abs.nanos; + loop { + let div = fraction_digits / 10; + let last_digit = fraction_digits % 10; + if last_digit != 0 { + break; + } + fraction_digits = div; + figures -= 1; } + f.write_fmt(format_args!(".{:01$}", fraction_digits, figures))?; } + f.write_str("S")?; Ok(()) } } @@ -1111,21 +1114,24 @@ mod tests { #[test] fn test_duration_fmt() { - assert_eq!(TimeDelta::zero().to_string(), "PT0S"); - assert_eq!(TimeDelta::days(42).to_string(), "P42D"); - assert_eq!(TimeDelta::days(-42).to_string(), "-P42D"); + assert_eq!(TimeDelta::zero().to_string(), "P0D"); + assert_eq!(TimeDelta::days(42).to_string(), "PT3628800S"); + assert_eq!(TimeDelta::days(-42).to_string(), "-PT3628800S"); assert_eq!(TimeDelta::seconds(42).to_string(), "PT42S"); assert_eq!(TimeDelta::milliseconds(42).to_string(), "PT0.042S"); assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); - assert_eq!((TimeDelta::days(7) + TimeDelta::milliseconds(6543)).to_string(), "P7DT6.543S"); - assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-P1DT1S"); + assert_eq!( + (TimeDelta::days(7) + TimeDelta::milliseconds(6543)).to_string(), + "PT604806.543S" + ); + assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-PT86401S"); assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S"); // the format specifier should have no effect on `TimeDelta` assert_eq!( format!("{:30}", TimeDelta::days(1) + TimeDelta::milliseconds(2345)), - "P1DT2.345S" + "PT86402.345S" ); } From 5b7cf85c5305939503bf2114b91864f7e192bfb2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 11 Jul 2023 15:52:03 +0200 Subject: [PATCH 696/999] Add more documentation to `StrftimeItems::new` --- src/format/strftime.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 042f0b43e2..9b376851b8 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -162,6 +162,22 @@ use super::{locales, Locale}; use super::{Fixed, InternalInternal, Item, Numeric, Pad}; /// Parsing iterator for `strftime`-like format strings. +/// +/// See the [`format::strftime` module](crate::format::strftime) for supported formatting +/// specifiers. +/// +/// `StrftimeItems` is used in combination with more low-level methods such as [`format::parse()`] +/// or [`format_with_items`]. +/// +/// If formatting or parsing date and time values is not performance-critical, the methods +/// [`parse_from_str`] and [`format`] on types such as [`DateTime`](crate::DateTime) are easier to +/// use. +/// +/// [`format`]: crate::DateTime::format +/// [`format_with_items`]: crate::DateTime::format +/// [`parse_from_str`]: crate::DateTime::parse_from_str +/// [`DateTime`]: crate::DateTime +/// [`format::parse()`]: crate::format::parse() #[derive(Clone, Debug)] pub struct StrftimeItems<'a> { /// Remaining portion of the string. @@ -176,7 +192,30 @@ pub struct StrftimeItems<'a> { } impl<'a> StrftimeItems<'a> { - /// Creates a new parsing iterator from the `strftime`-like format string. + /// Creates a new parsing iterator from a `strftime`-like format string. + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + #[cfg_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")] + #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")] + /// use chrono::format::*; + /// + /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601) + /// + /// const ISO8601_YMD_ITEMS: &[Item<'static>] = &[ + /// Item::Numeric(Numeric::Year, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Month, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Day, Pad::Zero), + /// ]; + /// assert!(strftime_parser.eq(ISO8601_YMD_ITEMS.iter().cloned())); + /// ``` #[must_use] pub const fn new(s: &'a str) -> StrftimeItems<'a> { #[cfg(not(feature = "unstable-locales"))] From 79de122615b7d7497c236e37c7f88e9ca14993f6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 11 Jul 2023 16:26:26 +0200 Subject: [PATCH 697/999] Add more documentation to `StrftimeItems::new_with_locale` --- src/format/strftime.rs | 46 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 9b376851b8..ae45e06e43 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -228,7 +228,51 @@ impl<'a> StrftimeItems<'a> { } } - /// Creates a new parsing iterator from the `strftime`-like format string. + /// Creates a new parsing iterator from a `strftime`-like format string, with some formatting + /// specifiers adjusted to match [`Locale`]. + /// + /// Note: `StrftimeItems::new_with_locale` only localizes the *format*. You usually want to + /// combine it with other locale-aware methods such as + /// [`DateTime::format_localized_with_items`] to get things like localized month or day names. + /// + /// The `%x` formatting specifier will use the local date format, `%X` the local time format, + /// and `%c` the local format for date and time. + /// `%r` will use the local 12-hour clock format (e.g., 11:11:04 PM). Not all locales have such + /// a format, in which case we fall back to a 24-hour clock (`%X`). + /// + /// See the [`format::strftime` module](crate::format::strftime) for all supported formatting + /// specifiers. + /// + /// [`DateTime::format_localized_with_items`]: crate::DateTime::format_localized_with_items + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + #[cfg_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")] + #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")] + /// use chrono::format::{Locale, StrftimeItems}; + /// use chrono::{FixedOffset, TimeZone}; + /// + /// let dt = FixedOffset::east_opt(9 * 60 * 60) + /// .unwrap() + /// .with_ymd_and_hms(2023, 7, 11, 0, 34, 59) + /// .unwrap(); + /// + /// // Note: you usually want to combine `StrftimeItems::new_with_locale` with other + /// // locale-aware methods such as `DateTime::format_localized_with_items`. + /// // We use the regular `format_with_items` to show only how the formatting changes. + /// + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::en_US)); + /// assert_eq!(fmtr.to_string(), "07/11/2023"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ko_KR)); + /// assert_eq!(fmtr.to_string(), "2023년 07월 11일"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP)); + /// assert_eq!(fmtr.to_string(), "2023年07月11日"); + /// ``` #[cfg(feature = "unstable-locales")] #[must_use] pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { From 59eeb8c5374039cf79dcdf7379cd9e79a7d2d2ca Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 18:33:49 +0200 Subject: [PATCH 698/999] Add `StrftimeItems::parse` --- src/format/strftime.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index ae45e06e43..e55fe70961 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -156,10 +156,17 @@ Notes: China Daylight Time. */ +#[cfg(feature = "alloc")] +extern crate alloc; + use super::{fixed, internal_fixed, num, num0, nums}; #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalInternal, Item, Numeric, Pad}; +#[cfg(any(feature = "alloc", feature = "std"))] +use super::{ParseError, BAD_FORMAT}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; /// Parsing iterator for `strftime`-like format strings. /// @@ -278,6 +285,59 @@ impl<'a> StrftimeItems<'a> { pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } + + /// Parse format string into a `Vec` of formatting [`Item`]'s. + /// + /// If you need to format or parse multiple values with the same format string, it is more + /// efficient to convert it to a `Vec` of formatting [`Item`]'s than to re-parse the format + /// string on every use. + /// + /// The `format_with_items` methods on [`DateTime`], [`NaiveDateTime`], [`NaiveDate`] and + /// [`NaiveTime`] accept the result for formatting. [`format::parse()`] can make use of it for + /// parsing. + /// + /// [`DateTime`]: crate::DateTime::format_with_items + /// [`NaiveDateTime`]: crate::NaiveDateTime::format_with_items + /// [`NaiveDate`]: crate::NaiveDate::format_with_items + /// [`NaiveTime`]: crate::NaiveTime::format_with_items + /// [`format::parse()`]: crate::format::parse() + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{parse, Parsed, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// let fmt_items = StrftimeItems::new("%e %b %Y %k.%M").parse()?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// // Formatting + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// + /// // Parsing + /// let mut parsed = Parsed::new(); + /// parse(&mut parsed, "11 Jul 2023 9.00", fmt_items.as_slice().iter())?; + /// let parsed_dt = parsed.to_naive_datetime_with_offset(0)?; + /// assert_eq!(parsed_dt, datetime); + /// # Ok::<(), chrono::ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item), + true => Err(BAD_FORMAT), + }) + .collect() + } } const HAVE_ALTERNATES: &str = "z"; @@ -1020,4 +1080,13 @@ mod tests { assert_eq!(size_of::(), 28); assert_eq!(size_of::(), 2); } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_strftime_parse() { + let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z"); + let fmt_items = fmt_str.parse().unwrap(); + let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); + assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000"); + } } From 58a2149a8ef5eaab7236bc447a4a37a3965b579b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 11 Jul 2023 13:33:39 +0200 Subject: [PATCH 699/999] Add `StrftimeItems::parse_to_owned` --- src/format/mod.rs | 16 ++++++++++++++++ src/format/strftime.rs | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/format/mod.rs b/src/format/mod.rs index fda4d425b6..a822882786 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -363,6 +363,22 @@ const fn internal_fixed(val: InternalInternal) -> Item<'static> { Item::Fixed(Fixed::Internal(InternalFixed { val })) } +impl<'a> Item<'a> { + /// Convert items that contain a reference to the format string into an owned variant. + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn to_owned(self) -> Item<'static> { + match self { + Item::Literal(s) => Item::OwnedLiteral(Box::from(s)), + Item::Space(s) => Item::OwnedSpace(Box::from(s)), + Item::Numeric(n, p) => Item::Numeric(n, p), + Item::Fixed(f) => Item::Fixed(f), + Item::OwnedLiteral(l) => Item::OwnedLiteral(l), + Item::OwnedSpace(s) => Item::OwnedSpace(s), + Item::Error => Item::Error, + } + } +} + /// An error from the `parse` function. #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ParseError(ParseErrorKind); diff --git a/src/format/strftime.rs b/src/format/strftime.rs index e55fe70961..26fa391201 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -338,6 +338,49 @@ impl<'a> StrftimeItems<'a> { }) .collect() } + + /// Parse format string into a `Vec` of [`Item`]'s that contain no references to slices of the + /// format string. + /// + /// A `Vec` created with [`StrftimeItems::parse`] contains references to the format string, + /// binding the lifetime of the `Vec` to that string. [`StrftimeItems::parse_to_owned`] will + /// convert the references to owned types. + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{Item, ParseError, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result>, ParseError> { + /// // `fmt_string` is dropped at the end of this function. + /// let fmt_string = format!("{} {}", date_fmt, time_fmt); + /// StrftimeItems::new(&fmt_string).parse_to_owned() + /// } + /// + /// let fmt_items = format_items("%e %b %Y", "%k.%M")?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// # Ok::<(), ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse_to_owned(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item.to_owned()), + true => Err(BAD_FORMAT), + }) + .collect() + } } const HAVE_ALTERNATES: &str = "z"; From dc196062650c05528cbe259e340210f0340a05d1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 10 Feb 2024 22:21:53 +0100 Subject: [PATCH 700/999] Prepare 0.4.34 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 8ee34c2148..904009d9ad 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-01-25 -version: 0.4.33 +date-released: 2024-02-11 +version: 0.4.34 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index d086ad38d6..04e427f393 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.33" +version = "0.4.34" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 1cebe0cfdaca0aa315dbbea8d20a8ab009a21fdd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 09:49:03 +0100 Subject: [PATCH 701/999] Run GitHub Actions on push to 0.5.x branch --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 520a39ed2f..8893465406 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -6,7 +6,7 @@ env: on: push: - branches: [main, 0.4.x] + branches: [main, 0.5.x] pull_request: jobs: # Run code coverage using cargo-llvm-cov then upload to codecov.io diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 18f65d78e2..3e06ad3209 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ env: on: push: - branches: [main, 0.4.x] + branches: [main, 0.5.x] pull_request: jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18ae42cd48..b639d866c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ env: on: push: - branches: [main, 0.4.x] + branches: [main, 0.5.x] pull_request: jobs: From ca10fea3221877575aaa9e57aa988c4eb9e7b613 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 13:59:34 +0200 Subject: [PATCH 702/999] Remove `DateImpl` type alias --- src/naive/date.rs | 10 +++++----- src/naive/internals.rs | 11 ++++------- src/naive/isoweek.rs | 6 +++--- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 190a6bac60..668c183c63 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -27,7 +27,7 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; -use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; +use super::internals::{self, Mdf, Of, YearFlags}; use super::isoweek; const MAX_YEAR: i32 = internals::MAX_YEAR; @@ -196,7 +196,7 @@ impl Days { )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - ymdf: DateImpl, // (year << 13) | of + ymdf: i32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -233,7 +233,7 @@ impl NaiveDate { } debug_assert!(YearFlags::from_year(year).0 == flags.0); match Of::new(ordinal, flags) { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. } } @@ -245,7 +245,7 @@ impl NaiveDate { return None; // Out-of-range } match mdf.to_of() { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), None => None, // Non-existing date } } @@ -1058,7 +1058,7 @@ impl NaiveDate { /// Does not check if the year flags match the year. #[inline] const fn with_of(&self, of: Of) -> NaiveDate { - NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl } + NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as i32 } } /// Makes a new `NaiveDate` for the next calendar date. diff --git a/src/naive/internals.rs b/src/naive/internals.rs index c6d7536a0c..a570d468cd 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -18,18 +18,15 @@ use crate::Weekday; use core::fmt; -/// The internal date representation: `year << 13 | Of` -pub(super) type DateImpl = i32; - /// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes /// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with /// `NaiveDate::MAX` pushes it beyond the valid, representable range. -pub(super) const MAX_YEAR: DateImpl = (i32::MAX >> 13) - 1; +pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; /// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes /// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with /// `NaiveDate::MIN` pushes it beyond the valid, representable range. -pub(super) const MIN_YEAR: DateImpl = (i32::MIN >> 13) + 1; +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; /// The year flags (aka the dominical letter). /// @@ -285,8 +282,8 @@ impl Of { of.validate() } - pub(super) const fn from_date_impl(date_impl: DateImpl) -> Of { - // We assume the value in the `DateImpl` is valid. + pub(super) const fn from_date_impl(date_impl: i32) -> Of { + // We assume the value in `date_impl` is valid. Of((date_impl & 0b1_1111_1111_1111) as u32) } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 4b3d8d9d85..795359d8d6 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -5,7 +5,7 @@ use core::fmt; -use super::internals::{DateImpl, Of, YearFlags}; +use super::internals::{Of, YearFlags}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -28,7 +28,7 @@ pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, // which year number might not match the calendar year number. - ywf: DateImpl, // (year << 10) | (week << 4) | flag + ywf: i32, // (year << 10) | (week << 4) | flag } /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. @@ -53,7 +53,7 @@ pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { } }; let flags = YearFlags::from_year(year); - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) } + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } } impl IsoWeek { From e9be4fd8e996f4453f693919b9223bd293051f67 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 08:07:26 +0200 Subject: [PATCH 703/999] Rename `ymdf` field to `yof` --- src/naive/date.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 668c183c63..07a9761ceb 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -196,7 +196,7 @@ impl Days { )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - ymdf: i32, // (year << 13) | of + yof: i32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -233,7 +233,7 @@ impl NaiveDate { } debug_assert!(YearFlags::from_year(year).0 == flags.0); match Of::new(ordinal, flags) { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), + Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. } } @@ -245,7 +245,7 @@ impl NaiveDate { return None; // Out-of-range } match mdf.to_of() { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), + Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), None => None, // Non-existing date } } @@ -787,10 +787,10 @@ impl NaiveDate { pub(crate) const fn add_days(self, days: i32) -> Option { // fast path if the result is within the same year const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; - if let Some(ordinal) = ((self.ymdf & ORDINAL_MASK) >> 4).checked_add(days) { + if let Some(ordinal) = ((self.yof & ORDINAL_MASK) >> 4).checked_add(days) { if ordinal > 0 && ordinal <= 365 { - let year_and_flags = self.ymdf & !ORDINAL_MASK; - return Some(NaiveDate { ymdf: year_and_flags | (ordinal << 4) }); + let year_and_flags = self.yof & !ORDINAL_MASK; + return Some(NaiveDate { yof: year_and_flags | (ordinal << 4) }); } } // do the full check @@ -1041,7 +1041,7 @@ impl NaiveDate { /// Returns the packed ordinal-flags. #[inline] const fn of(&self) -> Of { - Of::from_date_impl(self.ymdf) + Of::from_date_impl(self.yof) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -1058,7 +1058,7 @@ impl NaiveDate { /// Does not check if the year flags match the year. #[inline] const fn with_of(&self, of: Of) -> NaiveDate { - NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as i32 } + NaiveDate { yof: (self.yof & !0b1_1111_1111_1111) | of.inner() as i32 } } /// Makes a new `NaiveDate` for the next calendar date. @@ -1433,13 +1433,13 @@ impl NaiveDate { /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); /// ``` pub const fn leap_year(&self) -> bool { - self.ymdf & (0b1000) == 0 + self.yof & (0b1000) == 0 } // This duplicates `Datelike::year()`, because trait methods can't be const yet. #[inline] const fn year(&self) -> i32 { - self.ymdf >> 13 + self.yof >> 13 } /// Returns the day of year starting from 1. @@ -1484,16 +1484,16 @@ impl NaiveDate { } /// The minimum possible `NaiveDate` (January 1, 262144 BCE). - pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; + pub const MIN: NaiveDate = NaiveDate { yof: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). - pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; + pub const MAX: NaiveDate = NaiveDate { yof: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). pub(crate) const BEFORE_MIN: NaiveDate = - NaiveDate { ymdf: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; + NaiveDate { yof: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). pub(crate) const AFTER_MAX: NaiveDate = - NaiveDate { ymdf: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; + NaiveDate { yof: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { From b0f40bcedd329e734847348359dd1af72db8bd4b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 08:21:05 +0200 Subject: [PATCH 704/999] Add description of `NaiveDate` internal format --- src/naive/date.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 07a9761ceb..67dfa2941a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2,6 +2,16 @@ // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date without timezone. +//! +//! The implementation is optimized for determining year, month, day and day of week. +//! +//! Format of `NaiveDate`: +//! `YYYY_YYYY_YYYY_YYYY_YYYO_OOOO_OOOO_LWWW` +//! `Y`: Year +//! `O`: Ordinal +//! `L`: leap year flag (1 = common year, 0 is leap year) +//! `W`: weekday before the first day of the year +//! `LWWW`: will also be referred to as the year flags (`F`) #[cfg(feature = "alloc")] use core::borrow::Borrow; From 2e00a1ca0b2614d7a9b17e718e66945b7d31b657 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 12:19:29 +0200 Subject: [PATCH 705/999] Move `MIN_YEAR` and `MAX_YEAR` constants --- src/naive/date.rs | 13 ++++++++++--- src/naive/internals.rs | 10 ---------- src/naive/isoweek.rs | 6 +++--- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 67dfa2941a..e6aacf9973 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -40,9 +40,6 @@ use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, Mdf, Of, YearFlags}; use super::isoweek; -const MAX_YEAR: i32 = internals::MAX_YEAR; -const MIN_YEAR: i32 = internals::MIN_YEAR; - /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. #[derive(Debug)] @@ -2302,6 +2299,16 @@ const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { (val.div_euclid(div), val.rem_euclid(div)) } +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/internals.rs b/src/naive/internals.rs index a570d468cd..1f9482cba7 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -18,16 +18,6 @@ use crate::Weekday; use core::fmt; -/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes -/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with -/// `NaiveDate::MAX` pushes it beyond the valid, representable range. -pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; - -/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes -/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with -/// `NaiveDate::MIN` pushes it beyond the valid, representable range. -pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; - /// The year flags (aka the dominical letter). /// /// There are 14 possible classes of year in the Gregorian calendar: diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 795359d8d6..c4649db557 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -155,7 +155,7 @@ impl fmt::Debug for IsoWeek { mod tests { #[cfg(feature = "rkyv-validation")] use super::IsoWeek; - use crate::naive::{internals, NaiveDate}; + use crate::naive::date::{self, NaiveDate}; use crate::Datelike; #[test] @@ -163,13 +163,13 @@ mod tests { let minweek = NaiveDate::MIN.iso_week(); let maxweek = NaiveDate::MAX.iso_week(); - assert_eq!(minweek.year(), internals::MIN_YEAR); + assert_eq!(minweek.year(), date::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); #[cfg(feature = "alloc")] assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); - assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); + assert_eq!(maxweek.year(), date::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); #[cfg(feature = "alloc")] From 1835cfcde272d233a19db4ecadeb3104e8bc1b32 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 16:51:31 +0200 Subject: [PATCH 706/999] Remove `Of::succ` --- src/naive/date.rs | 18 +++++++++++++++--- src/naive/internals.rs | 11 ----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index e6aacf9973..ee2a83df8e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1098,9 +1098,10 @@ impl NaiveDate { #[inline] #[must_use] pub const fn succ_opt(&self) -> Option { - match self.of().succ() { - Some(of) => Some(self.with_of(of)), - None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1), + let new_ol = (self.yof & OL_MASK) + (1 << 4); + match new_ol <= MAX_OL { + true => Some(NaiveDate { yof: self.yof & !OL_MASK | new_ol }), + false => NaiveDate::from_yo_opt(self.year() + 1, 1), } } @@ -2309,6 +2310,17 @@ pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; /// `NaiveDate::MIN` pushes it beyond the valid, representable range. pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; +const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + +const LEAP_YEAR_MASK: i32 = 0b1000; + +// OL: ordinal and leap year flag. +// With only these parts of the date an ordinal 366 in a common year would be encoded as +// `((366 << 1) | 1) << 3`, and in a leap year as `((366 << 1) | 0) << 3`, which is less. +// This allows for efficiently checking the ordinal exists depending on whether this is a leap year. +const OL_MASK: i32 = ORDINAL_MASK | LEAP_YEAR_MASK; +const MAX_OL: i32 = 366 << 4; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1f9482cba7..1a957cd727 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -346,13 +346,6 @@ impl Of { Mdf::from_of(*self) } - /// Returns an `Of` with the next day, or `None` if this is the last day of the year. - #[inline] - pub(super) const fn succ(&self) -> Option { - let of = Of(self.0 + (1 << 4)); - of.validate() - } - /// Returns an `Of` with the previous day, or `None` if this is the first day of the year. #[inline] pub(super) const fn pred(&self) -> Option { @@ -867,10 +860,6 @@ mod tests { assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); - assert!(Of::new(365, regular_year).unwrap().succ().is_none()); - assert!(Of::new(365, leap_year).unwrap().succ().is_some()); - assert!(Of::new(366, leap_year).unwrap().succ().is_none()); - assert!(Of::new(1, regular_year).unwrap().pred().is_none()); assert!(Of::new(1, leap_year).unwrap().pred().is_none()); } From dd873d25dfd523b13ca65bffa7860a011dbe062e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 16:58:09 +0200 Subject: [PATCH 707/999] Remove `Of::pred` --- src/naive/date.rs | 7 ++++--- src/naive/internals.rs | 12 ------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index ee2a83df8e..74528d1a9f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1135,9 +1135,10 @@ impl NaiveDate { #[inline] #[must_use] pub const fn pred_opt(&self) -> Option { - match self.of().pred() { - Some(of) => Some(self.with_of(of)), - None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), + let new_shifted_ordinal = (self.yof & ORDINAL_MASK) - (1 << 4); + match new_shifted_ordinal > 0 { + true => Some(NaiveDate { yof: self.yof & !ORDINAL_MASK | new_shifted_ordinal }), + false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1a957cd727..fb22aa4f6e 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -345,15 +345,6 @@ impl Of { pub(super) const fn to_mdf(&self) -> Mdf { Mdf::from_of(*self) } - - /// Returns an `Of` with the previous day, or `None` if this is the first day of the year. - #[inline] - pub(super) const fn pred(&self) -> Option { - match self.ordinal() { - 1 => None, - _ => Some(Of(self.0 - (1 << 4))), - } - } } impl fmt::Debug for Of { @@ -859,9 +850,6 @@ mod tests { assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none()); assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); - - assert!(Of::new(1, regular_year).unwrap().pred().is_none()); - assert!(Of::new(1, leap_year).unwrap().pred().is_none()); } #[test] From 26746b81d0de6cb5d6a450e0dc49aabf34e38c7e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 17:01:44 +0200 Subject: [PATCH 708/999] Remove `Of::with_ordinal` --- src/naive/date.rs | 15 ++++++++++++++- src/naive/internals.rs | 30 ------------------------------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 74528d1a9f..4e9fa4779a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1836,7 +1836,14 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { - self.of().with_ordinal(ordinal).map(|of| self.with_of(of)) + if ordinal == 0 || ordinal > 366 { + return None; + } + let yof = (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate { yof }), + false => None, // Does not exist: Ordinal 366 in a common year. + } } /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. @@ -2946,7 +2953,10 @@ mod tests { assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_day(30), None); assert_eq!(d.with_day(u32::MAX), None); + } + #[test] + fn test_date_with_ordinal() { let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); assert_eq!(d.with_ordinal(0), None); assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); @@ -2954,6 +2964,9 @@ mod tests { assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal(1 << 28 | 60), None); + let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(366), None); assert_eq!(d.with_ordinal(u32::MAX), None); } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index fb22aa4f6e..acb7b2741f 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -315,12 +315,6 @@ impl Of { self.0 >> 4 } - #[inline] - pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { - let of = Of((ordinal << 4) | (self.0 & 0b1111)); - of.validate() - } - #[inline] pub(super) const fn flags(&self) -> YearFlags { YearFlags((self.0 & 0b1111) as u8) @@ -664,30 +658,6 @@ mod tests { } } - #[test] - fn test_of_with_fields() { - fn check(flags: YearFlags, ordinal: u32) { - let of = Of::new(ordinal, flags).unwrap(); - - for ordinal in 0u32..=1024 { - let of = of.with_ordinal(ordinal); - assert_eq!(of, Of::new(ordinal, flags)); - if let Some(of) = of { - assert_eq!(of.ordinal(), ordinal); - } - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 365); - } - for &flags in LEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 366); - } - } - #[test] fn test_of_weekday() { assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); From 5ba8ed1c908e71f247573406c2001ec475a183de Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 12:19:37 +0200 Subject: [PATCH 709/999] Remove `Of::ordinal` --- src/naive/date.rs | 25 +++++++++++++++---------- src/naive/internals.rs | 17 +---------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4e9fa4779a..6b5c9f559f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -238,10 +238,14 @@ impl NaiveDate { if year < MIN_YEAR || year > MAX_YEAR { return None; // Out-of-range } + if ordinal == 0 || ordinal > 366 { + return None; // Invalid + } debug_assert!(YearFlags::from_year(year).0 == flags.0); - match Of::new(ordinal, flags) { - Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), - None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. + let yof = (year << 13) | (ordinal << 4) as i32 | flags.0 as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate { yof }), + false => None, // Does not exist: Ordinal 366 in a common year. } } @@ -803,7 +807,7 @@ impl NaiveDate { // do the full check let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); + let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.ordinal()); let cycle = try_opt!((cycle as i32).checked_add(days)); let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; @@ -1228,8 +1232,8 @@ impl NaiveDate { let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal()) as i64; - let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal()) as i64; + let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2)) } @@ -1455,7 +1459,7 @@ impl NaiveDate { // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. #[inline] const fn ordinal(&self) -> u32 { - self.of().ordinal() + ((self.yof & ORDINAL_MASK) >> 4) as u32 } // This duplicates `Datelike::month()`, because trait methods can't be const yet. @@ -1648,7 +1652,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal(&self) -> u32 { - self.of().ordinal() + ((self.yof & ORDINAL_MASK) >> 4) as u32 } /// Returns the day of year starting from 0. @@ -1665,7 +1669,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal0(&self) -> u32 { - self.of().ordinal() - 1 + self.ordinal() - 1 } /// Returns the day of week. @@ -2531,7 +2535,7 @@ mod tests { NaiveDate::MAX == calculated_max, "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", calculated_max.of().flags(), - calculated_max.of().ordinal() + calculated_max.ordinal() ); // let's also check that the entire range do not exceed 2^44 seconds @@ -2695,6 +2699,7 @@ mod tests { assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); assert_eq!(yo_opt(2012, 367), None); + assert_eq!(yo_opt(2012, 1 << 28 | 60), None); assert_eq!(yo_opt(2014, 0), None); assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index acb7b2741f..0af7d44923 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -267,6 +267,7 @@ pub(super) struct Of(u32); impl Of { #[inline] + #[cfg(test)] pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { let of = Of((ordinal << 4) | flags as u32); of.validate() @@ -310,11 +311,6 @@ impl Of { } } - #[inline] - pub(super) const fn ordinal(&self) -> u32 { - self.0 >> 4 - } - #[inline] pub(super) const fn flags(&self) -> YearFlags { YearFlags((self.0 & 0b1111) as u8) @@ -647,17 +643,6 @@ mod tests { } } - #[test] - fn test_of_fields() { - for &flags in FLAGS.iter() { - for ordinal in 1u32..=366 { - if let Some(of) = Of::new(ordinal, flags) { - assert_eq!(of.ordinal(), ordinal); - } - } - } - } - #[test] fn test_of_weekday() { assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); From 5a8b1e42b8f5784a3254e7765b1356cc643b67c6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 11:35:22 +0100 Subject: [PATCH 710/999] Mark `format::Numeric` and `format::Fixed` as `non_exhaustive` --- src/format/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/format/mod.rs b/src/format/mod.rs index a822882786..75bc02ee31 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -97,6 +97,7 @@ pub enum Pad { /// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. +#[non_exhaustive] #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). @@ -168,6 +169,7 @@ impl fmt::Debug for InternalNumeric { /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. +#[non_exhaustive] #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. From 92b576b21998788d3a57ee9ec0d71a5447f139de Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 14:36:25 +0200 Subject: [PATCH 711/999] Move `iso_week_from_yof` to `IsoWeek::from_yof` --- src/naive/date.rs | 3 +-- src/naive/isoweek.rs | 48 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 6b5c9f559f..c4daae1370 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -38,7 +38,6 @@ use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, Mdf, Of, YearFlags}; -use super::isoweek; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -1689,7 +1688,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - isoweek::iso_week_from_yof(self.year(), self.of()) + IsoWeek::from_yof(self.year(), self.of()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index c4649db557..45a142a0dc 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -31,32 +31,32 @@ pub struct IsoWeek { ywf: i32, // (year << 10) | (week << 4) | flag } -/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. -// -// internal use only. we don't expose the public constructor for `IsoWeek` for now, -// because the year range for the week date and the calendar date do not match and -// it is confusing to have a date that is out of range in one and not in another. -// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. -pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { - let (rawweek, _) = of.isoweekdate_raw(); - let (year, week) = if rawweek < 1 { - // previous year - let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); - (year - 1, prevlastweek) - } else { - let lastweek = of.flags().nisoweeks(); - if rawweek > lastweek { - // next year - (year + 1, 1) +impl IsoWeek { + /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. + // + // internal use only. we don't expose the public constructor for `IsoWeek` for now, + // because the year range for the week date and the calendar date do not match and + // it is confusing to have a date that is out of range in one and not in another. + // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + pub(super) fn from_yof(year: i32, of: Of) -> Self { + let (rawweek, _) = of.isoweekdate_raw(); + let (year, week) = if rawweek < 1 { + // previous year + let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek) } else { - (year, rawweek) - } - }; - let flags = YearFlags::from_year(year); - IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } -} + let lastweek = of.flags().nisoweeks(); + if rawweek > lastweek { + // next year + (year + 1, 1) + } else { + (year, rawweek) + } + }; + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } + } -impl IsoWeek { /// Returns the year number for this ISO week. /// /// # Example From 583812d70035d53a564069e6faded03f01cd23df Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 13:14:45 +0200 Subject: [PATCH 712/999] Change `IsoWeek::from_yof` to consuming `ordinal` and `year_flags` remove `Of::isoweekdate_raw` --- src/naive/date.rs | 34 ++++++++++++++++++++++++++++++++-- src/naive/internals.rs | 17 ----------------- src/naive/isoweek.rs | 8 ++++---- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index c4daae1370..4b965d2711 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1688,7 +1688,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - IsoWeek::from_yof(self.year(), self.of()) + IsoWeek::from_yof(self.year(), self.ordinal(), self.of().flags()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. @@ -2516,7 +2516,7 @@ mod serde { #[cfg(test)] mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; - use crate::naive::internals::YearFlags; + use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; use crate::{Datelike, TimeDelta, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, @@ -3375,6 +3375,36 @@ mod tests { } } + #[test] + fn test_isoweekdate_with_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + // January 4 should be in the first week + let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); + let iso_week = jan4.iso_week(); + assert_eq!(jan4.of().flags(), year_flags); + assert_eq!(iso_week.week(), 1); + } + } + + // Used for testing some methods with all combinations of `YearFlags`. + // (year, flags, first weekday of year) + const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ + (2006, A, Weekday::Sun), + (2005, B, Weekday::Sat), + (2010, C, Weekday::Fri), + (2009, D, Weekday::Thu), + (2003, E, Weekday::Wed), + (2002, F, Weekday::Tue), + (2001, G, Weekday::Mon), + (2012, AG, Weekday::Sun), + (2000, BA, Weekday::Sat), + (2016, CB, Weekday::Fri), + (2004, DC, Weekday::Thu), + (2020, ED, Weekday::Wed), + (2008, FE, Weekday::Tue), + (2024, GF, Weekday::Mon), + ]; + #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 0af7d44923..451d937235 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -322,14 +322,6 @@ impl Of { weekday_from_u32_mod7((of >> 4) + (of & 0b111)) } - #[inline] - pub(super) fn isoweekdate_raw(&self) -> (u32, Weekday) { - // week ordinal = ordinal + delta - let Of(of) = *self; - let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, weekday_from_u32_mod7(weekord)) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { @@ -740,15 +732,6 @@ mod tests { } } - #[test] - fn test_of_isoweekdate_raw() { - for &flags in FLAGS.iter() { - // January 4 should be in the first week - let (week, _) = Of::new(4 /* January 4 */, flags).unwrap().isoweekdate_raw(); - assert_eq!(week, 1); - } - } - #[test] fn test_of_to_mdf() { for i in 0u32..=8192 { diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 45a142a0dc..1d0ddfba36 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -5,7 +5,7 @@ use core::fmt; -use super::internals::{Of, YearFlags}; +use super::internals::YearFlags; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -38,14 +38,14 @@ impl IsoWeek { // because the year range for the week date and the calendar date do not match and // it is confusing to have a date that is out of range in one and not in another. // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. - pub(super) fn from_yof(year: i32, of: Of) -> Self { - let (rawweek, _) = of.isoweekdate_raw(); + pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { + let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; let (year, week) = if rawweek < 1 { // previous year let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); (year - 1, prevlastweek) } else { - let lastweek = of.flags().nisoweeks(); + let lastweek = year_flags.nisoweeks(); if rawweek > lastweek { // next year (year + 1, 1) From 3dbea44f84fc417304dd0aaeffb3e7229ea9ad87 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 13:08:04 +0200 Subject: [PATCH 713/999] Remove `Of::weekday` --- src/naive/date.rs | 33 ++++++++++++++++++++++- src/naive/internals.rs | 60 ------------------------------------------ 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4b965d2711..22a486b7e3 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1473,10 +1473,19 @@ impl NaiveDate { self.mdf().day() } + /// Returns the day of week. // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. #[inline] const fn weekday(&self) -> Weekday { - self.of().weekday() + match (((self.yof & ORDINAL_MASK) >> 4) + (self.yof & WEEKDAY_FLAGS_MASK)) % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } } /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. @@ -2332,6 +2341,10 @@ const LEAP_YEAR_MASK: i32 = 0b1000; const OL_MASK: i32 = ORDINAL_MASK | LEAP_YEAR_MASK; const MAX_OL: i32 = 366 << 4; +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const WEEKDAY_FLAGS_MASK: i32 = 0b111; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where @@ -3375,6 +3388,24 @@ mod tests { } } + #[test] + fn test_weekday_with_yearflags() { + for (year, year_flags, first_weekday) in YEAR_FLAGS { + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); + dbg!(year); + assert_eq!(first_day_of_year.of().flags(), year_flags); + assert_eq!(first_day_of_year.weekday(), first_weekday); + + let mut prev = first_day_of_year.weekday(); + for ordinal in 2u32..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + let expected = prev.succ(); + assert_eq!(date.weekday(), expected); + prev = expected; + } + } + } + #[test] fn test_isoweekdate_with_yearflags() { for (year, year_flags, _) in YEAR_FLAGS { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 451d937235..3da29f51e1 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -15,7 +15,6 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -use crate::Weekday; use core::fmt; /// The year flags (aka the dominical letter). @@ -316,12 +315,6 @@ impl Of { YearFlags((self.0 & 0b1111) as u8) } - #[inline] - pub(super) const fn weekday(&self) -> Weekday { - let Of(of) = *self; - weekday_from_u32_mod7((of >> 4) + (of & 0b111)) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { @@ -447,27 +440,10 @@ impl fmt::Debug for Mdf { } } -/// Create a `Weekday` from an `u32`, with Monday = 0. -/// Infallible, takes any `n` and applies `% 7`. -#[inline] -const fn weekday_from_u32_mod7(n: u32) -> Weekday { - match n % 7 { - 0 => Weekday::Mon, - 1 => Weekday::Tue, - 2 => Weekday::Wed, - 3 => Weekday::Thu, - 4 => Weekday::Fri, - 5 => Weekday::Sat, - _ => Weekday::Sun, - } -} - #[cfg(test)] mod tests { - use super::weekday_from_u32_mod7; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; - use crate::Weekday; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; @@ -635,34 +611,6 @@ mod tests { } } - #[test] - fn test_of_weekday() { - assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); - assert_eq!(Of::new(1, B).unwrap().weekday(), Weekday::Sat); - assert_eq!(Of::new(1, C).unwrap().weekday(), Weekday::Fri); - assert_eq!(Of::new(1, D).unwrap().weekday(), Weekday::Thu); - assert_eq!(Of::new(1, E).unwrap().weekday(), Weekday::Wed); - assert_eq!(Of::new(1, F).unwrap().weekday(), Weekday::Tue); - assert_eq!(Of::new(1, G).unwrap().weekday(), Weekday::Mon); - assert_eq!(Of::new(1, AG).unwrap().weekday(), Weekday::Sun); - assert_eq!(Of::new(1, BA).unwrap().weekday(), Weekday::Sat); - assert_eq!(Of::new(1, CB).unwrap().weekday(), Weekday::Fri); - assert_eq!(Of::new(1, DC).unwrap().weekday(), Weekday::Thu); - assert_eq!(Of::new(1, ED).unwrap().weekday(), Weekday::Wed); - assert_eq!(Of::new(1, FE).unwrap().weekday(), Weekday::Tue); - assert_eq!(Of::new(1, GF).unwrap().weekday(), Weekday::Mon); - - for &flags in FLAGS.iter() { - let mut prev = Of::new(1, flags).unwrap().weekday(); - for ordinal in 2u32..=flags.ndays() { - let of = Of::new(ordinal, flags).unwrap(); - let expected = prev.succ(); - assert_eq!(of.weekday(), expected); - prev = expected; - } - } - } - #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { @@ -789,12 +737,4 @@ mod tests { assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); } - - #[test] - fn test_weekday_from_u32_mod7() { - for i in 0..=1000 { - assert_eq!(weekday_from_u32_mod7(i), Weekday::try_from((i % 7) as u8).unwrap()); - } - assert_eq!(weekday_from_u32_mod7(u32::MAX), Weekday::Thu); - } } From 7e5357736fa0052f3d6c89cd05b5cba13f5803a9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 14:19:19 +0200 Subject: [PATCH 714/999] Remove `Of::new` and basic functionality test for `Of` --- src/naive/internals.rs | 48 ------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3da29f51e1..5ba10fe975 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -265,13 +265,6 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ pub(super) struct Of(u32); impl Of { - #[inline] - #[cfg(test)] - pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { - let of = Of((ordinal << 4) | flags as u32); - of.validate() - } - pub(super) const fn from_date_impl(date_impl: i32) -> Of { // We assume the value in `date_impl` is valid. Of((date_impl & 0b1_1111_1111_1111) as u32) @@ -484,42 +477,6 @@ mod tests { assert_eq!(GF.nisoweeks(), 52); } - #[test] - fn test_of() { - fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { - for ordinal in ordinal1..=ordinal2 { - let of = match Of::new(ordinal, flags) { - Some(of) => of, - None if !expected => continue, - None => panic!("Of::new({}, {:?}) returned None", ordinal, flags), - }; - - assert!( - of.validate().is_some() == expected, - "ordinal {} = {:?} should be {} for dominical year {:?}", - ordinal, - of, - if expected { "valid" } else { "invalid" }, - flags - ); - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 365); - check(false, flags, 366, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - - for &flags in LEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 366); - check(false, flags, 367, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - } - #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { @@ -720,11 +677,6 @@ mod tests { fn test_invalid_returns_none() { let regular_year = YearFlags::from_year(2023); let leap_year = YearFlags::from_year(2024); - assert!(Of::new(0, regular_year).is_none()); - assert!(Of::new(366, regular_year).is_none()); - assert!(Of::new(366, leap_year).is_some()); - assert!(Of::new(367, regular_year).is_none()); - assert!(Mdf::new(0, 1, regular_year).is_none()); assert!(Mdf::new(13, 1, regular_year).is_none()); assert!(Mdf::new(1, 0, regular_year).is_none()); From 7c4b119161849fc8c19923709d9968afc00f5680 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 22:06:18 +0200 Subject: [PATCH 715/999] Remove `Of::flags`, add `NaiveDate::year_flags` --- src/naive/date.rs | 28 +++++++++++++++++++++------- src/naive/internals.rs | 5 ----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 22a486b7e3..b8aafdee87 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1488,6 +1488,11 @@ impl NaiveDate { } } + #[inline] + const fn year_flags(&self) -> YearFlags { + YearFlags((self.yof & YEAR_FLAGS_MASK) as u8) + } + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. pub(crate) const fn num_days_from_ce(&self) -> i32 { @@ -1697,7 +1702,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - IsoWeek::from_yof(self.year(), self.ordinal(), self.of().flags()) + IsoWeek::from_yof(self.year(), self.ordinal(), self.year_flags()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. @@ -2345,6 +2350,8 @@ const MAX_OL: i32 = 366 << 4; // Allows for quick day of week calculation from the 1-based ordinal. const WEEKDAY_FLAGS_MASK: i32 = 0b111; +const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where @@ -2541,12 +2548,12 @@ mod tests { assert!( NaiveDate::MIN == calculated_min, "`NaiveDate::MIN` should have year flag {:?}", - calculated_min.of().flags() + calculated_min.year_flags() ); assert!( NaiveDate::MAX == calculated_max, "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", - calculated_max.of().flags(), + calculated_max.year_flags(), calculated_max.ordinal() ); @@ -2561,11 +2568,11 @@ mod tests { ); const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; - assert_eq!(BEFORE_MIN.of().flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; - assert_eq!(AFTER_MAX.of().flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); } @@ -3388,12 +3395,19 @@ mod tests { } } + #[test] + fn test_date_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); + } + } + #[test] fn test_weekday_with_yearflags() { for (year, year_flags, first_weekday) in YEAR_FLAGS { let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); dbg!(year); - assert_eq!(first_day_of_year.of().flags(), year_flags); + assert_eq!(first_day_of_year.year_flags(), year_flags); assert_eq!(first_day_of_year.weekday(), first_weekday); let mut prev = first_day_of_year.weekday(); @@ -3412,7 +3426,7 @@ mod tests { // January 4 should be in the first week let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); let iso_week = jan4.iso_week(); - assert_eq!(jan4.of().flags(), year_flags); + assert_eq!(jan4.year_flags(), year_flags); assert_eq!(iso_week.week(), 1); } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 5ba10fe975..95690d0364 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -303,11 +303,6 @@ impl Of { } } - #[inline] - pub(super) const fn flags(&self) -> YearFlags { - YearFlags((self.0 & 0b1111) as u8) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { From 4bb5f6124c2998df8ae6e6a0a3f721c668dd6066 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 1 Aug 2023 16:37:52 +0200 Subject: [PATCH 716/999] Format `IsoWeek` comments --- src/naive/isoweek.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 1d0ddfba36..7776b928b7 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -25,8 +25,8 @@ use rkyv::{Archive, Deserialize, Serialize}; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct IsoWeek { - // note that this allows for larger year range than `NaiveDate`. - // this is crucial because we have an edge case for the first and last week supported, + // Note that this allows for larger year range than `NaiveDate`. + // This is crucial because we have an edge case for the first and last week supported, // which year number might not match the calendar year number. ywf: i32, // (year << 10) | (week << 4) | flag } @@ -34,10 +34,10 @@ pub struct IsoWeek { impl IsoWeek { /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. // - // internal use only. we don't expose the public constructor for `IsoWeek` for now, - // because the year range for the week date and the calendar date do not match and + // Internal use only. We don't expose the public constructor for `IsoWeek` for now + // because the year range for the week date and the calendar date do not match, and // it is confusing to have a date that is out of range in one and not in another. - // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + // Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; let (year, week) = if rawweek < 1 { From f95a973c4bb132d964f48974b129dc9c75093a86 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 16:25:34 +0200 Subject: [PATCH 717/999] Change `Mdf::from_of` to `Mdf::from_ol` --- src/naive/date.rs | 8 +------- src/naive/internals.rs | 20 +++++--------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index b8aafdee87..0bb6343160 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1045,13 +1045,7 @@ impl NaiveDate { /// Returns the packed month-day-flags. #[inline] const fn mdf(&self) -> Mdf { - self.of().to_mdf() - } - - /// Returns the packed ordinal-flags. - #[inline] - const fn of(&self) -> Of { - Of::from_date_impl(self.yof) + Mdf::from_ol((self.yof & OL_MASK) >> 3, self.year_flags()) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 95690d0364..3dd2e01e91 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -265,11 +265,6 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ pub(super) struct Of(u32); impl Of { - pub(super) const fn from_date_impl(date_impl: i32) -> Of { - // We assume the value in `date_impl` is valid. - Of((date_impl & 0b1_1111_1111_1111) as u32) - } - #[inline] pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option { let mdl = mdf >> 3; @@ -306,7 +301,7 @@ impl Of { #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { - Mdf::from_of(*self) + Mdf::from_ol(self.0 as i32 >> 3, YearFlags((self.0 & 0b1111) as u8)) } } @@ -345,15 +340,10 @@ impl Mdf { } #[inline] - pub(super) const fn from_of(Of(of): Of) -> Mdf { - let ol = of >> 3; - if ol <= MAX_OL { - // Array is indexed from `[1..=MAX_OL]`, with a `0` index having a meaningless value. - Mdf(of + ((OL_TO_MDL[ol as usize] as u32) << 3)) - } else { - // Panicking here would be reasonable, but we are just going on with a safe value. - Mdf(0) - } + pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf { + debug_assert!(ol > 1 && ol <= MAX_OL as i32); + // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value. + Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) } #[cfg(test)] From 6894b6fc3965dcec15ec6c0a536deb5b375d8959 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 07:18:06 +0200 Subject: [PATCH 718/999] Add `Mdf::ordinal` and `Mdf::year_flags`; remove `Of` type --- src/naive/date.rs | 36 ++++++----- src/naive/internals.rs | 136 ++++++----------------------------------- 2 files changed, 39 insertions(+), 133 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0bb6343160..990164fb35 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -37,7 +37,7 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; -use super::internals::{self, Mdf, Of, YearFlags}; +use super::internals::{self, Mdf, YearFlags}; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -251,11 +251,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` from year and packed month-day-flags. /// Does not check whether the flags are correct for the provided year. const fn from_mdf(year: i32, mdf: Mdf) -> Option { - if year < MIN_YEAR || year > MAX_YEAR { - return None; // Out-of-range - } - match mdf.to_of() { - Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), + match mdf.ordinal() { + Some(ordinal) => NaiveDate::from_ordinal_and_flags(year, ordinal, mdf.year_flags()), None => None, // Non-existing date } } @@ -1053,16 +1050,13 @@ impl NaiveDate { /// Returns `None` when the resulting `NaiveDate` would be invalid. #[inline] const fn with_mdf(&self, mdf: Mdf) -> Option { - Some(self.with_of(try_opt!(mdf.to_of()))) - } - - /// Makes a new `NaiveDate` with the packed ordinal-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// Does not check if the year flags match the year. - #[inline] - const fn with_of(&self, of: Of) -> NaiveDate { - NaiveDate { yof: (self.yof & !0b1_1111_1111_1111) | of.inner() as i32 } + debug_assert!(self.year_flags().0 == mdf.year_flags().0); + match mdf.ordinal() { + Some(ordinal) => { + Some(NaiveDate { yof: (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32 }) + } + None => None, // Non-existing date + } } /// Makes a new `NaiveDate` for the next calendar date. @@ -3425,6 +3419,16 @@ mod tests { } } + #[test] + fn test_date_to_mdf_to_date() { + for (year, year_flags, _) in YEAR_FLAGS { + for ordinal in 1..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); + } + } + } + // Used for testing some methods with all combinations of `YearFlags`. // (year, flags, first weekday of year) const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3dd2e01e91..34f6f84f4a 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -166,7 +166,6 @@ impl fmt::Debug for YearFlags { } // OL: (ordinal << 1) | leap year flag -pub(super) const MIN_OL: u32 = 1 << 1; pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; @@ -255,70 +254,8 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 98, // 12 ]; -/// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. -/// -/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), -/// which is an index to the `OL_TO_MDL` lookup table. -/// -/// The methods implemented on `Of` always return a valid value. -#[derive(PartialEq, PartialOrd, Copy, Clone)] -pub(super) struct Of(u32); - -impl Of { - #[inline] - pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option { - let mdl = mdf >> 3; - if mdl > MAX_MDL { - // Panicking on out-of-bounds indexing would be reasonable, but just return `None`. - return None; - } - // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. - let v = MDL_TO_OL[mdl as usize]; - let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3)); - of.validate() - } - - #[inline] - pub(super) const fn inner(&self) -> u32 { - self.0 - } - - /// Returns `(ordinal << 1) | leap-year-flag`. - #[inline] - const fn ol(&self) -> u32 { - self.0 >> 3 - } - - #[inline] - const fn validate(self) -> Option { - let ol = self.ol(); - match ol >= MIN_OL && ol <= MAX_OL { - true => Some(self), - false => None, - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] - #[inline] - pub(super) const fn to_mdf(&self) -> Mdf { - Mdf::from_ol(self.0 as i32 >> 3, YearFlags((self.0 & 0b1111) as u8)) - } -} - -impl fmt::Debug for Of { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Of(of) = *self; - write!( - f, - "Of(({} << 4) | {:#04o} /*{:?}*/)", - of >> 4, - of & 0b1111, - YearFlags((of & 0b1111) as u8) - ) - } -} - /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` +/// `M_MMMD_DDDD_LFFF` /// /// The whole bits except for the least 3 bits are referred as `Mdl` /// (month, day of month and leap flag), @@ -397,10 +334,18 @@ impl Mdf { Mdf((mdf & !0b1111) | flags as u32) } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] - pub(super) const fn to_of(&self) -> Option { - Of::from_mdf(*self) + pub(super) const fn ordinal(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some((mdl - v as i32 as u32) >> 1), + } + } + + #[inline] + pub(super) const fn year_flags(&self) -> YearFlags { + YearFlags((self.0 & 0b1111) as u8) } } @@ -420,7 +365,7 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { - use super::{Mdf, Of}; + use super::Mdf; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; @@ -622,56 +567,13 @@ mod tests { } } - #[test] - fn test_of_to_mdf() { - for i in 0u32..=8192 { - if let Some(of) = Of(i).validate() { - assert!(of.to_mdf().valid()); - } - } - } - - #[test] - fn test_mdf_to_of() { - for i in 0u32..=8192 { - let mdf = Mdf(i); - assert_eq!(mdf.valid(), mdf.to_of().is_some()); - } - } - - #[test] - fn test_of_to_mdf_to_of() { - for i in 0u32..=8192 { - if let Some(of) = Of(i).validate() { - assert_eq!(of, of.to_mdf().to_of().unwrap()); - } - } - } - - #[test] - fn test_mdf_to_of_to_mdf() { - for i in 0u32..=8192 { - let mdf = Mdf(i); - if mdf.valid() { - assert_eq!(mdf, mdf.to_of().unwrap().to_mdf()); - } - } - } - #[test] fn test_invalid_returns_none() { - let regular_year = YearFlags::from_year(2023); - let leap_year = YearFlags::from_year(2024); - assert!(Mdf::new(0, 1, regular_year).is_none()); - assert!(Mdf::new(13, 1, regular_year).is_none()); - assert!(Mdf::new(1, 0, regular_year).is_none()); - assert!(Mdf::new(1, 32, regular_year).is_none()); - assert!(Mdf::new(2, 31, regular_year).is_some()); - - assert!(Of::from_mdf(Mdf::new(2, 30, regular_year).unwrap()).is_none()); - assert!(Of::from_mdf(Mdf::new(2, 30, leap_year).unwrap()).is_none()); - assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none()); - assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); - assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); + let flags = YearFlags::from_year(2023); + assert!(Mdf::new(0, 1, flags).is_none()); + assert!(Mdf::new(13, 1, flags).is_none()); + assert!(Mdf::new(1, 0, flags).is_none()); + assert!(Mdf::new(1, 32, flags).is_none()); + assert!(Mdf::new(2, 31, flags).is_some()); } } From 3016c30014064bd1c67d285bd23c9e232b9efb80 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 13:20:15 +0100 Subject: [PATCH 719/999] Move `cycle_to_yo` and `yo_to_cycle` to `naive::date` module --- src/naive/date.rs | 51 +++++++++++++++++++++++++++++++++++++----- src/naive/internals.rs | 39 -------------------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 990164fb35..3d64812184 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -37,7 +37,7 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; -use super::internals::{self, Mdf, YearFlags}; +use super::internals::{Mdf, YearFlags}; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -485,7 +485,7 @@ impl NaiveDate { let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 let year_div_400 = days.div_euclid(146_097); let cycle = days.rem_euclid(146_097); - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } @@ -803,12 +803,12 @@ impl NaiveDate { // do the full check let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.ordinal()); + let cycle = yo_to_cycle(year_mod_400 as u32, self.ordinal()); let cycle = try_opt!((cycle as i32).checked_add(days)); let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } @@ -1219,8 +1219,8 @@ impl NaiveDate { let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; - let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; + let cycle1 = yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2)) } @@ -2309,6 +2309,23 @@ impl Default for NaiveDate { } } +const fn cycle_to_yo(cycle: u32) -> (u32, u32) { + let mut year_mod_400 = cycle / 365; + let mut ordinal0 = cycle % 365; + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; + if ordinal0 < delta { + year_mod_400 -= 1; + ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; + } else { + ordinal0 -= delta; + } + (year_mod_400, ordinal0 + 1) +} + +const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { + year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 +} + const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { (val.div_euclid(div), val.rem_euclid(div)) } @@ -2340,6 +2357,28 @@ const WEEKDAY_FLAGS_MASK: i32 = 0b111; const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; +const YEAR_DELTAS: &[u8; 401] = &[ + 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, + 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, + 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 + 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, + 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, + 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, + 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, + 48, 49, 49, 49, // 200 + 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, + 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, + 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, + 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, + 72, 73, 73, 73, // 300 + 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, + 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, + 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, + 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, + 96, 97, 97, 97, 97, // 400+1 +]; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 34f6f84f4a..26576d865e 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -63,45 +63,6 @@ const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 ]; -const YEAR_DELTAS: &[u8; 401] = &[ - 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, - 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, - 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, - 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 - 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, - 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, - 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, - 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, - 48, 49, 49, 49, // 200 - 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, - 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, - 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, - 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, - 72, 73, 73, 73, // 300 - 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, - 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, - 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, - 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, - 96, 97, 97, 97, 97, // 400+1 -]; - -pub(super) const fn cycle_to_yo(cycle: u32) -> (u32, u32) { - let mut year_mod_400 = cycle / 365; - let mut ordinal0 = cycle % 365; - let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; - if ordinal0 < delta { - year_mod_400 -= 1; - ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; - } else { - ordinal0 -= delta; - } - (year_mod_400, ordinal0 + 1) -} - -pub(super) const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { - year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 -} - impl YearFlags { #[allow(unreachable_pub)] // public as an alias for benchmarks only #[doc(hidden)] // for benchmarks only From dd88b4b26a77288d38c99228c4fc91156d103725 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 12:09:39 +0100 Subject: [PATCH 720/999] Move `naive/date.rs` to `naive/date/mod.rs` --- src/naive/{date.rs => date/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/naive/{date.rs => date/mod.rs} (100%) diff --git a/src/naive/date.rs b/src/naive/date/mod.rs similarity index 100% rename from src/naive/date.rs rename to src/naive/date/mod.rs From 792dbd0e0bafab77a5c3d003ccbda0a9354d627c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 12:14:21 +0100 Subject: [PATCH 721/999] Split out `test` module to its own file --- src/naive/date/mod.rs | 958 +--------------------------------------- src/naive/date/tests.rs | 890 +++++++++++++++++++++++++++++++++++++ 2 files changed, 893 insertions(+), 955 deletions(-) create mode 100644 src/naive/date/tests.rs diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 3d64812184..b7cf5bd9fc 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -39,6 +39,9 @@ use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{Mdf, YearFlags}; +#[cfg(test)] +mod tests; + /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. #[derive(Debug)] @@ -2559,958 +2562,3 @@ mod serde { } } } - -#[cfg(test)] -mod tests { - use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; - use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; - use crate::{Datelike, TimeDelta, Weekday}; - - // 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 year flag {:?}", - calculated_min.year_flags() - ); - assert!( - NaiveDate::MAX == calculated_max, - "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", - calculated_max.year_flags(), - calculated_max.ordinal() - ); - - // 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 - ); - - const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; - assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); - assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); - - const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; - assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); - assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); - } - - #[test] - fn diff_months() { - // identity - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), - Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) - ); - - // add with months exceeding `i32::MAX` - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3) - .unwrap() - .checked_add_months(Months::new(i32::MAX as u32 + 1)), - None - ); - - // sub with months exceeding `i32::MIN` - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3) - .unwrap() - .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), - None - ); - - // add overflowing year - assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); - - // add underflowing year - assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); - - // sub crossing year 0 boundary - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), - Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) - ); - - // add crossing year boundary - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), - Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) - ); - - // sub crossing year boundary - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), - Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) - ); - - // add clamping day, non-leap year - assert_eq!( - NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), - Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) - ); - - // add to leap day - assert_eq!( - NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), - Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) - ); - - // add into december - assert_eq!( - NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), - Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) - ); - - // sub into december - assert_eq!( - NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), - Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) - ); - - // add into january - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), - Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) - ); - - // sub into january - assert_eq!( - NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), - Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) - ); - } - - #[test] - fn test_readme_doomsday() { - for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { - // even months - let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); - let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); - let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); - let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); - let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); - - // nine to five, seven-eleven - let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); - let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); - let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); - let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); - - // "March 0" - let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); - - let weekday = d30.weekday(); - let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; - assert!(other_dates.iter().all(|d| d.weekday() == weekday)); - } - } - - #[test] - fn test_date_from_ymd() { - let ymd_opt = NaiveDate::from_ymd_opt; - - assert!(ymd_opt(2012, 0, 1).is_none()); - assert!(ymd_opt(2012, 1, 1).is_some()); - assert!(ymd_opt(2012, 2, 29).is_some()); - assert!(ymd_opt(2014, 2, 29).is_none()); - assert!(ymd_opt(2014, 3, 0).is_none()); - assert!(ymd_opt(2014, 3, 1).is_some()); - assert!(ymd_opt(2014, 3, 31).is_some()); - assert!(ymd_opt(2014, 3, 32).is_none()); - assert!(ymd_opt(2014, 12, 31).is_some()); - assert!(ymd_opt(2014, 13, 1).is_none()); - } - - #[test] - fn test_date_from_yo() { - let yo_opt = NaiveDate::from_yo_opt; - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - - assert_eq!(yo_opt(2012, 0), None); - assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); - assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); - assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); - assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); - assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); - assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); - assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); - assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); - assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); - assert_eq!(yo_opt(2012, 367), None); - assert_eq!(yo_opt(2012, 1 << 28 | 60), None); - - assert_eq!(yo_opt(2014, 0), None); - assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); - assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); - assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); - assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); - assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); - assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); - assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); - assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); - assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); - assert_eq!(yo_opt(2014, 366), None); - } - - #[test] - fn test_date_from_isoywd() { - let isoywd_opt = NaiveDate::from_isoywd_opt; - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - - assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); - assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); - assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); - - assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); - assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); - - assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); - assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); - assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); - } - - #[test] - fn test_date_from_isoywd_and_iso_week() { - for year in 2000..2401 { - for week in 1..54 { - for &weekday in [ - Weekday::Mon, - Weekday::Tue, - Weekday::Wed, - Weekday::Thu, - Weekday::Fri, - Weekday::Sat, - Weekday::Sun, - ] - .iter() - { - let d = NaiveDate::from_isoywd_opt(year, week, weekday); - if let Some(d) = d { - assert_eq!(d.weekday(), weekday); - let w = d.iso_week(); - assert_eq!(w.year(), year); - assert_eq!(w.week(), week); - } - } - } - } - - for year in 2000..2401 { - for month in 1..13 { - for day in 1..32 { - let d = NaiveDate::from_ymd_opt(year, month, day); - if let Some(d) = d { - let w = d.iso_week(); - let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); - assert_eq!(d, d_.unwrap()); - } - } - } - } - } - - #[test] - fn test_date_from_num_days_from_ce() { - let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; - assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); - assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); - assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); - assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); - assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); - assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); - assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); - assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); - assert_eq!( - from_ndays_from_ce(365 * 2 + 1), - Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap()) - ); - assert_eq!( - from_ndays_from_ce(365 * 3 + 1), - Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap()) - ); - assert_eq!( - from_ndays_from_ce(365 * 4 + 2), - Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap()) - ); - assert_eq!( - from_ndays_from_ce(146097 + 1), - Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap()) - ); - assert_eq!( - from_ndays_from_ce(146097 * 5 + 1), - Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) - ); - assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); - assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE - assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); - assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE - - for days in (-9999..10001).map(|x| x * 100) { - assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); - } - - assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); - assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); - assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); - assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); - - assert_eq!(from_ndays_from_ce(i32::MIN), None); - assert_eq!(from_ndays_from_ce(i32::MAX), None); - } - - #[test] - fn test_date_from_weekday_of_month_opt() { - let ymwd = NaiveDate::from_weekday_of_month_opt; - assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); - assert_eq!( - ymwd(2018, 8, Weekday::Wed, 1), - Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Thu, 1), - Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Sun, 1), - Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Mon, 1), - Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Tue, 1), - Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Wed, 2), - Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Sun, 2), - Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Thu, 3), - Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Thu, 4), - Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Thu, 5), - Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap()) - ); - assert_eq!( - ymwd(2018, 8, Weekday::Fri, 5), - Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap()) - ); - assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); - } - - #[test] - fn test_date_fields() { - fn check(year: i32, month: u32, day: u32, ordinal: u32) { - let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); - assert_eq!(d1.year(), year); - assert_eq!(d1.month(), month); - assert_eq!(d1.day(), day); - assert_eq!(d1.ordinal(), ordinal); - - let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); - assert_eq!(d2.year(), year); - assert_eq!(d2.month(), month); - assert_eq!(d2.day(), day); - assert_eq!(d2.ordinal(), ordinal); - - assert_eq!(d1, d2); - } - - check(2012, 1, 1, 1); - check(2012, 1, 2, 2); - check(2012, 2, 1, 32); - check(2012, 2, 29, 60); - check(2012, 3, 1, 61); - check(2012, 4, 9, 100); - check(2012, 7, 18, 200); - check(2012, 10, 26, 300); - check(2012, 12, 31, 366); - - check(2014, 1, 1, 1); - check(2014, 1, 2, 2); - check(2014, 2, 1, 32); - check(2014, 2, 28, 59); - check(2014, 3, 1, 60); - check(2014, 4, 10, 100); - check(2014, 7, 19, 200); - check(2014, 10, 27, 300); - check(2014, 12, 31, 365); - } - - #[test] - fn test_date_weekday() { - assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); - // May 20, 1875 = ISO 8601 reference date - assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); - assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); - } - - #[test] - fn test_date_with_fields() { - let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); - assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); - assert_eq!(d.with_year(-100), None); - assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); - assert_eq!(d.with_year(1900), None); - assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); - assert_eq!(d.with_year(2001), None); - assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); - assert_eq!(d.with_year(i32::MAX), None); - - let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); - assert_eq!(d.with_month(0), None); - assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); - assert_eq!(d.with_month(2), None); - assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); - assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); - assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); - assert_eq!(d.with_month(13), None); - assert_eq!(d.with_month(u32::MAX), None); - - let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); - assert_eq!(d.with_day(0), None); - assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); - assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); - assert_eq!(d.with_day(30), None); - assert_eq!(d.with_day(u32::MAX), None); - } - - #[test] - fn test_date_with_ordinal() { - let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); - assert_eq!(d.with_ordinal(0), None); - assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); - assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); - assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); - assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); - assert_eq!(d.with_ordinal(367), None); - assert_eq!(d.with_ordinal(1 << 28 | 60), None); - let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); - assert_eq!(d.with_ordinal(366), None); - assert_eq!(d.with_ordinal(u32::MAX), None); - } - - #[test] - fn test_date_num_days_from_ce() { - assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); - - for year in -9999..10001 { - assert_eq!( - NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), - NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 - ); - } - } - - #[test] - fn test_date_succ() { - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); - assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); - assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); - assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); - } - - #[test] - fn test_date_pred() { - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); - assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); - assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); - assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); - } - - #[test] - fn test_date_add() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: TimeDelta, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - } - - check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::seconds(86399), Some((2014, 1, 1))); - // always round towards zero - check((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::days(1), Some((2014, 1, 2))); - check((2014, 1, 1), TimeDelta::days(-1), Some((2013, 12, 31))); - check((2014, 1, 1), TimeDelta::days(364), Some((2014, 12, 31))); - check((2014, 1, 1), TimeDelta::days(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), TimeDelta::days(365 * 400 + 97), Some((2414, 1, 1))); - - check((-7, 1, 1), TimeDelta::days(365 * 12 + 3), Some((5, 1, 1))); - - // overflow check - check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); - check((0, 1, 1), TimeDelta::max_value(), None); - check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); - check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); - check((0, 1, 1), TimeDelta::min_value(), None); - } - - #[test] - fn test_date_sub() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: TimeDelta) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); - assert_eq!(lhs.signed_duration_since(rhs), diff); - assert_eq!(rhs.signed_duration_since(lhs), -diff); - } - - check((2014, 1, 1), (2014, 1, 1), TimeDelta::zero()); - check((2014, 1, 2), (2014, 1, 1), TimeDelta::days(1)); - check((2014, 12, 31), (2014, 1, 1), TimeDelta::days(364)); - check((2015, 1, 3), (2014, 1, 1), TimeDelta::days(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 400 + 97)); - - check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64)); - check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64)); - } - - #[test] - fn test_date_add_days() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); - assert_eq!(lhs.checked_add_days(rhs), sum); - } - - check((2014, 1, 1), Days::new(0), Some((2014, 1, 1))); - // always round towards zero - check((2014, 1, 1), Days::new(1), Some((2014, 1, 2))); - check((2014, 1, 1), Days::new(364), Some((2014, 12, 31))); - check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1))); - - check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1))); - - // overflow check - check( - (0, 1, 1), - Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), - Some((MAX_YEAR, 12, 31)), - ); - check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); - } - - #[test] - fn test_date_sub_days() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); - assert_eq!(lhs - diff, rhs); - } - - check((2014, 1, 1), (2014, 1, 1), Days::new(0)); - check((2014, 1, 2), (2014, 1, 1), Days::new(1)); - check((2014, 12, 31), (2014, 1, 1), Days::new(364)); - check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Days::new(365 * 400 + 97)); - - check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap())); - check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); - } - - #[test] - fn test_date_addassignment() { - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - let mut date = ymd(2016, 10, 1); - date += TimeDelta::days(10); - assert_eq!(date, ymd(2016, 10, 11)); - date += TimeDelta::days(30); - assert_eq!(date, ymd(2016, 11, 10)); - } - - #[test] - fn test_date_subassignment() { - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - let mut date = ymd(2016, 10, 11); - date -= TimeDelta::days(10); - assert_eq!(date, ymd(2016, 10, 1)); - date -= TimeDelta::days(2); - assert_eq!(date, ymd(2016, 9, 29)); - } - - #[test] - fn test_date_fmt() { - assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); - - assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); - assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); - assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); - assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); - assert_eq!( - format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), - "+12345-06-07" - ); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "-0000000123456-1-2", - " -123456 - 1 - 2 ", - "-12345-1-2", - "-1234-12-31", - "-7-6-5", - "350-2-28", - "360-02-29", - "0360-02-29", - "2015-2 -18", - "2015-02-18", - "+70-2-18", - "+70000-2-18", - "+00007-2-18", - ]; - for &s in &valid { - eprintln!("test_date_from_str valid {:?}", s); - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - eprintln!("d {:?} (NaiveDate)", d); - let s_ = format!("{:?}", d); - eprintln!("s_ {:?}", s_); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - eprintln!("d_ {:?} (NaiveDate)", d_); - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - let invalid = [ - "", // empty - "x", // invalid - "Fri, 09 Aug 2013 GMT", // valid date, wrong format - "Sat Jun 30 2012", // valid date, wrong format - "1441497364.649", // valid datetime, wrong format - "+1441497364.649", // valid datetime, wrong format - "+1441497364", // valid datetime, wrong format - "2014/02/03", // valid date, wrong format - "2014", // datetime missing data - "2014-01", // datetime missing data - "2014-01-00", // invalid day - "2014-11-32", // invalid day - "2014-13-01", // invalid month - "2014-13-57", // invalid month, day - "9999999-9-9", // invalid year (out of bounds) - ]; - for &s in &invalid { - eprintln!("test_date_from_str invalid {:?}", s); - assert!(s.parse::().is_err()); - } - } - - #[test] - fn test_date_parse_from_str() { - let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - assert_eq!( - NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymd(2014, 5, 7)) - ); // ignore time and offset - assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), - Ok(ymd(2015, 2, 2)) - ); - assert_eq!( - NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), - Ok(ymd(2013, 8, 9)) - ); - assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); - assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); - assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient - - assert_eq!( - NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), - NaiveDate::from_ymd_opt(2020, 1, 12), - ); - - assert_eq!( - NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), - NaiveDate::from_ymd_opt(2019, 1, 13), - ); - } - - #[test] - fn test_day_iterator_limit() { - assert_eq!( - NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), - 2 - ); - assert_eq!( - NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), - 2 - ); - } - - #[test] - fn test_week_iterator_limit() { - assert_eq!( - NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), - 2 - ); - assert_eq!( - NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), - 2 - ); - } - - #[test] - fn test_naiveweek() { - let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); - let asserts = [ - (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), - (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), - (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), - (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), - (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), - (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), - (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), - ]; - for (start, first_day, last_day) in asserts { - let week = date.week(start); - let days = week.days(); - assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); - assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); - assert!(days.contains(&date)); - } - } - - #[test] - fn test_naiveweek_min_max() { - let date_max = NaiveDate::MAX; - assert!(date_max.week(Weekday::Mon).first_day() <= date_max); - let date_min = NaiveDate::MIN; - assert!(date_min.week(Weekday::Mon).last_day() >= date_min); - } - - #[test] - fn test_weeks_from() { - // tests per: https://github.com/chronotope/chrono/issues/961 - // these internally use `weeks_from` via the parsing infrastructure - assert_eq!( - NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), - NaiveDate::from_ymd_opt(2020, 1, 12), - ); - assert_eq!( - NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), - NaiveDate::from_ymd_opt(2019, 1, 13), - ); - - // direct tests - for (y, starts_on) in &[ - (2019, Weekday::Tue), - (2020, Weekday::Wed), - (2021, Weekday::Fri), - (2022, Weekday::Sat), - (2023, Weekday::Sun), - (2024, Weekday::Mon), - (2025, Weekday::Wed), - (2026, Weekday::Thu), - ] { - for day in &[ - Weekday::Mon, - Weekday::Tue, - Weekday::Wed, - Weekday::Thu, - Weekday::Fri, - Weekday::Sat, - Weekday::Sun, - ] { - assert_eq!( - NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), - Some(if day == starts_on { 1 } else { 0 }) - ); - - // last day must always be in week 52 or 53 - assert!([52, 53] - .contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),); - } - } - - let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); - - // 400 years covers all year types - for day in &[ - Weekday::Mon, - Weekday::Tue, - Weekday::Wed, - Weekday::Thu, - Weekday::Fri, - Weekday::Sat, - Weekday::Sun, - ] { - // must always be below 54 - for dplus in 1..(400 * 366) { - assert!((base + Days::new(dplus)).weeks_from(*day) < 54) - } - } - } - - #[test] - fn test_with_0_overflow() { - let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap(); - assert!(dt.with_month0(4294967295).is_none()); - assert!(dt.with_day0(4294967295).is_none()); - assert!(dt.with_ordinal0(4294967295).is_none()); - } - - #[test] - fn test_leap_year() { - for year in 0..=MAX_YEAR { - let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); - let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); - assert_eq!(date.leap_year(), is_leap); - assert_eq!(date.leap_year(), date.with_ordinal(366).is_some()); - } - } - - #[test] - fn test_date_yearflags() { - for (year, year_flags, _) in YEAR_FLAGS { - assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); - } - } - - #[test] - fn test_weekday_with_yearflags() { - for (year, year_flags, first_weekday) in YEAR_FLAGS { - let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); - dbg!(year); - assert_eq!(first_day_of_year.year_flags(), year_flags); - assert_eq!(first_day_of_year.weekday(), first_weekday); - - let mut prev = first_day_of_year.weekday(); - for ordinal in 2u32..=year_flags.ndays() { - let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); - let expected = prev.succ(); - assert_eq!(date.weekday(), expected); - prev = expected; - } - } - } - - #[test] - fn test_isoweekdate_with_yearflags() { - for (year, year_flags, _) in YEAR_FLAGS { - // January 4 should be in the first week - let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); - let iso_week = jan4.iso_week(); - assert_eq!(jan4.year_flags(), year_flags); - assert_eq!(iso_week.week(), 1); - } - } - - #[test] - fn test_date_to_mdf_to_date() { - for (year, year_flags, _) in YEAR_FLAGS { - for ordinal in 1..=year_flags.ndays() { - let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); - assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); - } - } - } - - // Used for testing some methods with all combinations of `YearFlags`. - // (year, flags, first weekday of year) - const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ - (2006, A, Weekday::Sun), - (2005, B, Weekday::Sat), - (2010, C, Weekday::Fri), - (2009, D, Weekday::Thu), - (2003, E, Weekday::Wed), - (2002, F, Weekday::Tue), - (2001, G, Weekday::Mon), - (2012, AG, Weekday::Sun), - (2000, BA, Weekday::Sat), - (2016, CB, Weekday::Fri), - (2004, DC, Weekday::Thu), - (2020, ED, Weekday::Wed), - (2008, FE, Weekday::Tue), - (2024, GF, Weekday::Mon), - ]; - - #[test] - #[cfg(feature = "rkyv-validation")] - fn test_rkyv_validation() { - let date_min = NaiveDate::MIN; - let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); - assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); - - let date_max = NaiveDate::MAX; - let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); - assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); - } - - // MAX_YEAR-12-31 minus 0000-01-01 - // = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) - // = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 - // = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR) - const MAX_DAYS_FROM_YEAR_0: i32 = - (MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400; - - // MIN_YEAR-01-01 minus 0000-01-01 - // = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000) - const MIN_DAYS_FROM_YEAR_0: i32 = - MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400; - - // only used for testing, but duplicated in naive::datetime - const MAX_BITS: usize = 44; -} diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs new file mode 100644 index 0000000000..0bc31bef58 --- /dev/null +++ b/src/naive/date/tests.rs @@ -0,0 +1,890 @@ +use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; +use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; +use crate::{Datelike, TimeDelta, Weekday}; + +// 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 year flag {:?}", + calculated_min.year_flags() + ); + assert!( + NaiveDate::MAX == calculated_max, + "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", + calculated_max.year_flags(), + calculated_max.ordinal() + ); + + // 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 + ); + + const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; + assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); + + const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; + assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); +} + +#[test] +fn diff_months() { + // identity + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), + Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) + ); + + // add with months exceeding `i32::MAX` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_add_months(Months::new(i32::MAX as u32 + 1)), + None + ); + + // sub with months exceeding `i32::MIN` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), + None + ); + + // add overflowing year + assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); + + // add underflowing year + assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); + + // sub crossing year 0 boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), + Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) + ); + + // add crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), + Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) + ); + + // sub crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) + ); + + // add clamping day, non-leap year + assert_eq!( + NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), + Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) + ); + + // add to leap day + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), + Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) + ); + + // add into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), + Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) + ); + + // sub into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) + ); + + // add into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), + Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) + ); + + // sub into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), + Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) + ); +} + +#[test] +fn test_readme_doomsday() { + for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { + // even months + let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); + let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); + let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); + let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); + let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); + + // nine to five, seven-eleven + let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); + let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); + let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); + let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); + + // "March 0" + let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); + + let weekday = d30.weekday(); + let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; + assert!(other_dates.iter().all(|d| d.weekday() == weekday)); + } +} + +#[test] +fn test_date_from_ymd() { + let ymd_opt = NaiveDate::from_ymd_opt; + + assert!(ymd_opt(2012, 0, 1).is_none()); + assert!(ymd_opt(2012, 1, 1).is_some()); + assert!(ymd_opt(2012, 2, 29).is_some()); + assert!(ymd_opt(2014, 2, 29).is_none()); + assert!(ymd_opt(2014, 3, 0).is_none()); + assert!(ymd_opt(2014, 3, 1).is_some()); + assert!(ymd_opt(2014, 3, 31).is_some()); + assert!(ymd_opt(2014, 3, 32).is_none()); + assert!(ymd_opt(2014, 12, 31).is_some()); + assert!(ymd_opt(2014, 13, 1).is_none()); +} + +#[test] +fn test_date_from_yo() { + let yo_opt = NaiveDate::from_yo_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(yo_opt(2012, 0), None); + assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); + assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); + assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); + assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); + assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); + assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); + assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); + assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); + assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); + assert_eq!(yo_opt(2012, 367), None); + assert_eq!(yo_opt(2012, 1 << 28 | 60), None); + + assert_eq!(yo_opt(2014, 0), None); + assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); + assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); + assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); + assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); + assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); + assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); + assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); + assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); + assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); + assert_eq!(yo_opt(2014, 366), None); +} + +#[test] +fn test_date_from_isoywd() { + let isoywd_opt = NaiveDate::from_isoywd_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); + assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); + assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); + assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); + assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); + assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); + assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); + assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); + assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); + assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); + + assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); + assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); + assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); + assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); + assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); + + assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); + assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); + assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); + assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); + assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); +} + +#[test] +fn test_date_from_isoywd_and_iso_week() { + for year in 2000..2401 { + for week in 1..54 { + for &weekday in [ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] + .iter() + { + let d = NaiveDate::from_isoywd_opt(year, week, weekday); + if let Some(d) = d { + assert_eq!(d.weekday(), weekday); + let w = d.iso_week(); + assert_eq!(w.year(), year); + assert_eq!(w.week(), week); + } + } + } + } + + for year in 2000..2401 { + for month in 1..13 { + for day in 1..32 { + let d = NaiveDate::from_ymd_opt(year, month, day); + if let Some(d) = d { + let w = d.iso_week(); + let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); + assert_eq!(d, d_.unwrap()); + } + } + } + } +} + +#[test] +fn test_date_from_num_days_from_ce() { + let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; + assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); + assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); + assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); + assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); + assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); + assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap())); + assert_eq!( + from_ndays_from_ce(146097 * 5 + 1), + Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) + ); + assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE + assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE + + for days in (-9999..10001).map(|x| x * 100) { + assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); + } + + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); + + assert_eq!(from_ndays_from_ce(i32::MIN), None); + assert_eq!(from_ndays_from_ce(i32::MAX), None); +} + +#[test] +fn test_date_from_weekday_of_month_opt() { + let ymwd = NaiveDate::from_weekday_of_month_opt; + assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); +} + +#[test] +fn test_date_fields() { + fn check(year: i32, month: u32, day: u32, ordinal: u32) { + let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); + assert_eq!(d1.year(), year); + assert_eq!(d1.month(), month); + assert_eq!(d1.day(), day); + assert_eq!(d1.ordinal(), ordinal); + + let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(d2.year(), year); + assert_eq!(d2.month(), month); + assert_eq!(d2.day(), day); + assert_eq!(d2.ordinal(), ordinal); + + assert_eq!(d1, d2); + } + + check(2012, 1, 1, 1); + check(2012, 1, 2, 2); + check(2012, 2, 1, 32); + check(2012, 2, 29, 60); + check(2012, 3, 1, 61); + check(2012, 4, 9, 100); + check(2012, 7, 18, 200); + check(2012, 10, 26, 300); + check(2012, 12, 31, 366); + + check(2014, 1, 1, 1); + check(2014, 1, 2, 2); + check(2014, 2, 1, 32); + check(2014, 2, 28, 59); + check(2014, 3, 1, 60); + check(2014, 4, 10, 100); + check(2014, 7, 19, 200); + check(2014, 10, 27, 300); + check(2014, 12, 31, 365); +} + +#[test] +fn test_date_weekday() { + assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); + // May 20, 1875 = ISO 8601 reference date + assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); + assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); +} + +#[test] +fn test_date_with_fields() { + let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); + assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); + assert_eq!(d.with_year(-100), None); + assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); + assert_eq!(d.with_year(1900), None); + assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_year(2001), None); + assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); + assert_eq!(d.with_year(i32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); + assert_eq!(d.with_month(0), None); + assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); + assert_eq!(d.with_month(2), None); + assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); + assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); + assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); + assert_eq!(d.with_month(13), None); + assert_eq!(d.with_month(u32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); + assert_eq!(d.with_day(0), None); + assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); + assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_day(30), None); + assert_eq!(d.with_day(u32::MAX), None); +} + +#[test] +fn test_date_with_ordinal() { + let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(0), None); + assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); + assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); + assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); + assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal(1 << 28 | 60), None); + let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(366), None); + assert_eq!(d.with_ordinal(u32::MAX), None); +} + +#[test] +fn test_date_num_days_from_ce() { + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + + for year in -9999..10001 { + assert_eq!( + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 + ); + } +} + +#[test] +fn test_date_succ() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); + assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); + assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); + assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); +} + +#[test] +fn test_date_pred() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); + assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); + assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); + assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); +} + +#[test] +fn test_date_add() { + fn check((y1, m1, d1): (i32, u32, u32), rhs: TimeDelta, ymd: Option<(i32, u32, u32)>) { + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); + assert_eq!(lhs.checked_add_signed(rhs), sum); + assert_eq!(lhs.checked_sub_signed(-rhs), sum); + } + + check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::seconds(86399), Some((2014, 1, 1))); + // always round towards zero + check((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::days(1), Some((2014, 1, 2))); + check((2014, 1, 1), TimeDelta::days(-1), Some((2013, 12, 31))); + check((2014, 1, 1), TimeDelta::days(364), Some((2014, 12, 31))); + check((2014, 1, 1), TimeDelta::days(365 * 4 + 1), Some((2018, 1, 1))); + check((2014, 1, 1), TimeDelta::days(365 * 400 + 97), Some((2414, 1, 1))); + + check((-7, 1, 1), TimeDelta::days(365 * 12 + 3), Some((5, 1, 1))); + + // overflow check + check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); + check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); + check((0, 1, 1), TimeDelta::max_value(), None); + check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); + check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); + check((0, 1, 1), TimeDelta::min_value(), None); +} + +#[test] +fn test_date_sub() { + fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: TimeDelta) { + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); + assert_eq!(lhs.signed_duration_since(rhs), diff); + assert_eq!(rhs.signed_duration_since(lhs), -diff); + } + + check((2014, 1, 1), (2014, 1, 1), TimeDelta::zero()); + check((2014, 1, 2), (2014, 1, 1), TimeDelta::days(1)); + check((2014, 12, 31), (2014, 1, 1), TimeDelta::days(364)); + check((2015, 1, 3), (2014, 1, 1), TimeDelta::days(365 + 2)); + check((2018, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 4 + 1)); + check((2414, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 400 + 97)); + + check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64)); + check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64)); +} + +#[test] +fn test_date_add_days() { + fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) { + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); + assert_eq!(lhs.checked_add_days(rhs), sum); + } + + check((2014, 1, 1), Days::new(0), Some((2014, 1, 1))); + // always round towards zero + check((2014, 1, 1), Days::new(1), Some((2014, 1, 2))); + check((2014, 1, 1), Days::new(364), Some((2014, 12, 31))); + check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1))); + check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1))); + + check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1))); + + // overflow check + check((0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), Some((MAX_YEAR, 12, 31))); + check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); +} + +#[test] +fn test_date_sub_days() { + fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) { + let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); + let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); + assert_eq!(lhs - diff, rhs); + } + + check((2014, 1, 1), (2014, 1, 1), Days::new(0)); + check((2014, 1, 2), (2014, 1, 1), Days::new(1)); + check((2014, 12, 31), (2014, 1, 1), Days::new(364)); + check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2)); + check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1)); + check((2414, 1, 1), (2014, 1, 1), Days::new(365 * 400 + 97)); + + check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap())); + check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); +} + +#[test] +fn test_date_addassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 1); + date += TimeDelta::days(10); + assert_eq!(date, ymd(2016, 10, 11)); + date += TimeDelta::days(30); + assert_eq!(date, ymd(2016, 11, 10)); +} + +#[test] +fn test_date_subassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 11); + date -= TimeDelta::days(10); + assert_eq!(date, ymd(2016, 10, 1)); + date -= TimeDelta::days(2); + assert_eq!(date, ymd(2016, 9, 29)); +} + +#[test] +fn test_date_fmt() { + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); + + assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); + assert_eq!(format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07"); +} + +#[test] +fn test_date_from_str() { + // valid cases + let valid = [ + "-0000000123456-1-2", + " -123456 - 1 - 2 ", + "-12345-1-2", + "-1234-12-31", + "-7-6-5", + "350-2-28", + "360-02-29", + "0360-02-29", + "2015-2 -18", + "2015-02-18", + "+70-2-18", + "+70000-2-18", + "+00007-2-18", + ]; + for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + eprintln!("d {:?} (NaiveDate)", d); + let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + eprintln!("d_ {:?} (NaiveDate)", d_); + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_date_parse_from_str() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!( + NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymd(2014, 5, 7)) + ); // ignore time and offset + assert_eq!( + NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), + Ok(ymd(2015, 2, 2)) + ); + assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9))); + assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); + assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); + assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); +} + +#[test] +fn test_day_iterator_limit() { + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), + 2 + ); +} + +#[test] +fn test_week_iterator_limit() { + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + 2 + ); +} + +#[test] +fn test_naiveweek() { + let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); + let asserts = [ + (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), + (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), + (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), + (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), + (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), + (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), + (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); + assert!(days.contains(&date)); + } +} + +#[test] +fn test_naiveweek_min_max() { + let date_max = NaiveDate::MAX; + assert!(date_max.week(Weekday::Mon).first_day() <= date_max); + let date_min = NaiveDate::MIN; + assert!(date_min.week(Weekday::Mon).last_day() >= date_min); +} + +#[test] +fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!( + [52, 53].contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)), + ); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } +} + +#[test] +fn test_with_0_overflow() { + let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap(); + assert!(dt.with_month0(4294967295).is_none()); + assert!(dt.with_day0(4294967295).is_none()); + assert!(dt.with_ordinal0(4294967295).is_none()); +} + +#[test] +fn test_leap_year() { + for year in 0..=MAX_YEAR { + let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + assert_eq!(date.leap_year(), is_leap); + assert_eq!(date.leap_year(), date.with_ordinal(366).is_some()); + } +} + +#[test] +fn test_date_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); + } +} + +#[test] +fn test_weekday_with_yearflags() { + for (year, year_flags, first_weekday) in YEAR_FLAGS { + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); + dbg!(year); + assert_eq!(first_day_of_year.year_flags(), year_flags); + assert_eq!(first_day_of_year.weekday(), first_weekday); + + let mut prev = first_day_of_year.weekday(); + for ordinal in 2u32..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + let expected = prev.succ(); + assert_eq!(date.weekday(), expected); + prev = expected; + } + } +} + +#[test] +fn test_isoweekdate_with_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + // January 4 should be in the first week + let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); + let iso_week = jan4.iso_week(); + assert_eq!(jan4.year_flags(), year_flags); + assert_eq!(iso_week.week(), 1); + } +} + +#[test] +fn test_date_to_mdf_to_date() { + for (year, year_flags, _) in YEAR_FLAGS { + for ordinal in 1..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); + } + } +} + +// Used for testing some methods with all combinations of `YearFlags`. +// (year, flags, first weekday of year) +const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ + (2006, A, Weekday::Sun), + (2005, B, Weekday::Sat), + (2010, C, Weekday::Fri), + (2009, D, Weekday::Thu), + (2003, E, Weekday::Wed), + (2002, F, Weekday::Tue), + (2001, G, Weekday::Mon), + (2012, AG, Weekday::Sun), + (2000, BA, Weekday::Sat), + (2016, CB, Weekday::Fri), + (2004, DC, Weekday::Thu), + (2020, ED, Weekday::Wed), + (2008, FE, Weekday::Tue), + (2024, GF, Weekday::Mon), +]; + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let date_min = NaiveDate::MIN; + let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); + + let date_max = NaiveDate::MAX; + let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); +} + +// MAX_YEAR-12-31 minus 0000-01-01 +// = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) +// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 +// = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR) +const MAX_DAYS_FROM_YEAR_0: i32 = + (MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400; + +// MIN_YEAR-01-01 minus 0000-01-01 +// = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000) +const MIN_DAYS_FROM_YEAR_0: i32 = MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400; + +// only used for testing, but duplicated in naive::datetime +const MAX_BITS: usize = 44; From 352392ba1313af340775661b22804a75a6a40ff4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 13:06:05 +0100 Subject: [PATCH 722/999] Add `NaiveWeek::new` --- src/naive/date/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index b7cf5bd9fc..7d60346098 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -51,6 +51,11 @@ pub struct NaiveWeek { } impl NaiveWeek { + /// Create a new `NaiveWeek` + pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { + Self { date, start } + } + /// Returns a date representing the first day of the week. /// /// # Panics @@ -1421,7 +1426,7 @@ impl NaiveDate { /// specified. #[inline] pub const fn week(&self, start: Weekday) -> NaiveWeek { - NaiveWeek { date: *self, start } + NaiveWeek::new(*self, start) } /// Returns `true` if this is a leap year. From 41be21eeac9e857d00a847bb88d74c21a923e680 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 13:09:44 +0100 Subject: [PATCH 723/999] Move `NaiveWeek` to `naive` module --- src/naive/date/mod.rs | 101 +----------------------------- src/naive/date/tests.rs | 29 --------- src/naive/mod.rs | 135 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 128 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 7d60346098..f2266b7633 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -16,7 +16,7 @@ #[cfg(feature = "alloc")] use core::borrow::Borrow; use core::iter::FusedIterator; -use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] @@ -33,7 +33,7 @@ use crate::format::{ Parsed, StrftimeItems, }; use crate::month::Months; -use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; +use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; @@ -42,101 +42,6 @@ use super::internals::{Mdf, YearFlags}; #[cfg(test)] mod tests; -/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first -/// day of the week. -#[derive(Debug)] -pub struct NaiveWeek { - date: NaiveDate, - start: Weekday, -} - -impl NaiveWeek { - /// Create a new `NaiveWeek` - pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { - Self { date, start } - } - - /// 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 - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); - /// let week = date.week(Weekday::Mon); - /// assert!(week.first_day() <= date); - /// ``` - #[inline] - #[must_use] - pub const fn first_day(&self) -> NaiveDate { - let start = self.start.num_days_from_monday() as i32; - let ref_day = self.date.weekday().num_days_from_monday() as i32; - // Calculate the number of days to subtract from `self.date`. - // Do not construct an intermediate date beyond `self.date`, because that may be out of - // range if `date` is close to `NaiveDate::MAX`. - let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`") - } - - /// 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 - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); - /// let week = date.week(Weekday::Mon); - /// assert!(week.last_day() >= date); - /// ``` - #[inline] - #[must_use] - pub const fn last_day(&self) -> NaiveDate { - let end = self.start.pred().num_days_from_monday() as i32; - let ref_day = self.date.weekday().num_days_from_monday() as i32; - // Calculate the number of days to add to `self.date`. - // Do not construct an intermediate date before `self.date` (like with `first_day()`), - // because that may be out of range if `date` is close to `NaiveDate::MIN`. - let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`") - } - - /// Returns a [`RangeInclusive`] representing the whole week bounded by - /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::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 - /// - /// ``` - /// use chrono::{NaiveDate, Weekday}; - /// - /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); - /// let week = date.week(Weekday::Mon); - /// let days = week.days(); - /// assert!(days.contains(&date)); - /// ``` - #[inline] - #[must_use] - pub const fn days(&self) -> RangeInclusive { - self.first_day()..=self.last_day() - } -} - /// A duration in calendar days. /// /// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` @@ -1472,7 +1377,7 @@ impl NaiveDate { /// Returns the day of week. // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. #[inline] - const fn weekday(&self) -> Weekday { + pub(super) const fn weekday(&self) -> Weekday { match (((self.yof & ORDINAL_MASK) >> 4) + (self.yof & WEEKDAY_FLAGS_MASK)) % 7 { 0 => Weekday::Mon, 1 => Weekday::Tue, diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 0bc31bef58..8bc460b779 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -687,35 +687,6 @@ fn test_week_iterator_limit() { ); } -#[test] -fn test_naiveweek() { - let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); - let asserts = [ - (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), - (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), - (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), - (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), - (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), - (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), - (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), - ]; - for (start, first_day, last_day) in asserts { - let week = date.week(start); - let days = week.days(); - assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); - assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); - assert!(days.contains(&date)); - } -} - -#[test] -fn test_naiveweek_min_max() { - let date_max = NaiveDate::MAX; - assert!(date_max.week(Weekday::Mon).first_day() <= date_max); - let date_min = NaiveDate::MIN; - assert!(date_min.week(Weekday::Mon).last_day() >= date_min); -} - #[test] fn test_weeks_from() { // tests per: https://github.com/chronotope/chrono/issues/961 diff --git a/src/naive/mod.rs b/src/naive/mod.rs index c8c281b00e..79b11136ce 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -4,13 +4,18 @@ //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), //! but can be also used for the simpler date and time handling. +use core::ops::RangeInclusive; + +use crate::expect; +use crate::Weekday; + pub(crate) mod date; pub(crate) mod datetime; mod internals; pub(crate) mod isoweek; pub(crate) mod time; -pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator, NaiveWeek}; +pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; #[allow(deprecated)] pub use self::date::{MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] @@ -25,6 +30,101 @@ pub use self::time::NaiveTime; #[doc(hidden)] pub use self::internals::YearFlags as __BenchYearFlags; +/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first +/// day of the week. +#[derive(Debug)] +pub struct NaiveWeek { + date: NaiveDate, + start: Weekday, +} + +impl NaiveWeek { + /// Create a new `NaiveWeek` + pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { + Self { date, start } + } + + /// 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 + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.first_day() <= date); + /// ``` + #[inline] + #[must_use] + pub const fn first_day(&self) -> NaiveDate { + let start = self.start.num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to subtract from `self.date`. + // Do not construct an intermediate date beyond `self.date`, because that may be out of + // range if `date` is close to `NaiveDate::MAX`. + let days = start - ref_day - if start > ref_day { 7 } else { 0 }; + expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`") + } + + /// 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 + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.last_day() >= date); + /// ``` + #[inline] + #[must_use] + pub const fn last_day(&self) -> NaiveDate { + let end = self.start.pred().num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to add to `self.date`. + // Do not construct an intermediate date before `self.date` (like with `first_day()`), + // because that may be out of range if `date` is close to `NaiveDate::MIN`. + let days = end - ref_day + if end < ref_day { 7 } else { 0 }; + expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`") + } + + /// Returns a [`RangeInclusive`] representing the whole week bounded by + /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::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 + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// let days = week.days(); + /// assert!(days.contains(&date)); + /// ``` + #[inline] + #[must_use] + pub const fn days(&self) -> RangeInclusive { + self.first_day()..=self.last_day() + } +} + /// Serialization/Deserialization of naive types in alternate formats /// /// The various modules in here are intended to be used with serde's [`with` @@ -37,3 +137,36 @@ pub use self::internals::YearFlags as __BenchYearFlags; pub mod serde { pub use super::datetime::serde::*; } + +#[cfg(test)] +mod test { + use crate::{NaiveDate, Weekday}; + #[test] + fn test_naiveweek() { + let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); + let asserts = [ + (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), + (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), + (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), + (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), + (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), + (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), + (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); + assert!(days.contains(&date)); + } + } + + #[test] + fn test_naiveweek_min_max() { + let date_max = NaiveDate::MAX; + assert!(date_max.week(Weekday::Mon).first_day() <= date_max); + let date_min = NaiveDate::MIN; + assert!(date_min.week(Weekday::Mon).last_day() >= date_min); + } +} From d9d6b6352eb06f864f330a827ca3da30f3147da3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 15:21:20 +0100 Subject: [PATCH 724/999] Move `Days` to `naive` module --- src/naive/date/mod.rs | 18 +----------------- src/naive/mod.rs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index f2266b7633..5161e2ac08 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -33,7 +33,7 @@ use crate::format::{ Parsed, StrftimeItems, }; use crate::month::Months; -use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; +use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; @@ -42,22 +42,6 @@ use super::internals::{Mdf, YearFlags}; #[cfg(test)] mod tests; -/// A duration in calendar days. -/// -/// This is useful because when using `TimeDelta` it is possible 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, Ord)] -pub struct Days(pub(crate) u64); - -impl Days { - /// Construct a new `Days` from a number of days - pub const fn new(num: u64) -> Self { - Self(num) - } -} - /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date] from Jan 1, 262145 BCE to Dec 31, 262143 CE. /// Also supports the conversion from ISO 8601 ordinal and week date. diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 79b11136ce..4b33a5594d 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -15,7 +15,7 @@ mod internals; pub(crate) mod isoweek; pub(crate) mod time; -pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; +pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; #[allow(deprecated)] pub use self::date::{MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] @@ -125,6 +125,22 @@ impl NaiveWeek { } } +/// A duration in calendar days. +/// +/// This is useful because when using `TimeDelta` it is possible 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, Ord)] +pub struct Days(pub(crate) u64); + +impl Days { + /// Construct a new `Days` from a number of days + pub const fn new(num: u64) -> Self { + Self(num) + } +} + /// Serialization/Deserialization of naive types in alternate formats /// /// The various modules in here are intended to be used with serde's [`with` From 22a0d40011a7280848f8e817a0ed57e8010fdc50 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 25 Apr 2023 15:26:47 +0200 Subject: [PATCH 725/999] Add test for the size of chrono's main data types --- src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 18d1faac73..79cb66fa6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -627,3 +627,27 @@ macro_rules! expect { } }; } + +#[cfg(test)] +mod tests { + #[cfg(feature = "clock")] + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; + + #[test] + #[allow(deprecated)] + #[cfg(feature = "clock")] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 4); + assert_eq!(size_of::>(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::(), 12); + assert_eq!(size_of::>(), 16); + + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>>(), 20); + } +} From 0b4d5ffe8aa97de963e4dfa136848059e5c37942 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 11 Feb 2024 16:37:16 +0100 Subject: [PATCH 726/999] Use `NonZeroI32` inside `NaiveDate` --- src/lib.rs | 6 ++--- src/naive/date/mod.rs | 62 +++++++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 79cb66fa6a..5cba8f8f8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -639,15 +639,15 @@ mod tests { fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 4); - assert_eq!(size_of::>(), 8); + assert_eq!(size_of::>(), 4); assert_eq!(size_of::(), 8); assert_eq!(size_of::>(), 12); assert_eq!(size_of::(), 12); - assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>(), 12); assert_eq!(size_of::>(), 12); assert_eq!(size_of::>(), 16); assert_eq!(size_of::>(), 16); - assert_eq!(size_of::>>(), 20); + assert_eq!(size_of::>>(), 16); } } diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 5161e2ac08..44335a59f9 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -16,6 +16,7 @@ #[cfg(feature = "alloc")] use core::borrow::Borrow; use core::iter::FusedIterator; +use core::num::NonZeroI32; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -99,7 +100,7 @@ mod tests; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - yof: i32, // (year << 13) | of + yof: NonZeroI32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -140,7 +141,7 @@ impl NaiveDate { debug_assert!(YearFlags::from_year(year).0 == flags.0); let yof = (year << 13) | (ordinal << 4) as i32 | flags.0 as i32; match yof & OL_MASK <= MAX_OL { - true => Some(NaiveDate { yof }), + true => Some(NaiveDate::from_yof(yof)), false => None, // Does not exist: Ordinal 366 in a common year. } } @@ -691,10 +692,10 @@ impl NaiveDate { pub(crate) const fn add_days(self, days: i32) -> Option { // fast path if the result is within the same year const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; - if let Some(ordinal) = ((self.yof & ORDINAL_MASK) >> 4).checked_add(days) { + if let Some(ordinal) = ((self.yof() & ORDINAL_MASK) >> 4).checked_add(days) { if ordinal > 0 && ordinal <= 365 { - let year_and_flags = self.yof & !ORDINAL_MASK; - return Some(NaiveDate { yof: year_and_flags | (ordinal << 4) }); + let year_and_flags = self.yof() & !ORDINAL_MASK; + return Some(NaiveDate::from_yof(year_and_flags | (ordinal << 4))); } } // do the full check @@ -939,7 +940,7 @@ impl NaiveDate { /// Returns the packed month-day-flags. #[inline] const fn mdf(&self) -> Mdf { - Mdf::from_ol((self.yof & OL_MASK) >> 3, self.year_flags()) + Mdf::from_ol((self.yof() & OL_MASK) >> 3, self.year_flags()) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -950,7 +951,7 @@ impl NaiveDate { debug_assert!(self.year_flags().0 == mdf.year_flags().0); match mdf.ordinal() { Some(ordinal) => { - Some(NaiveDate { yof: (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32 }) + Some(NaiveDate::from_yof((self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32)) } None => None, // Non-existing date } @@ -986,9 +987,9 @@ impl NaiveDate { #[inline] #[must_use] pub const fn succ_opt(&self) -> Option { - let new_ol = (self.yof & OL_MASK) + (1 << 4); + let new_ol = (self.yof() & OL_MASK) + (1 << 4); match new_ol <= MAX_OL { - true => Some(NaiveDate { yof: self.yof & !OL_MASK | new_ol }), + true => Some(NaiveDate::from_yof(self.yof() & !OL_MASK | new_ol)), false => NaiveDate::from_yo_opt(self.year() + 1, 1), } } @@ -1023,9 +1024,9 @@ impl NaiveDate { #[inline] #[must_use] pub const fn pred_opt(&self) -> Option { - let new_shifted_ordinal = (self.yof & ORDINAL_MASK) - (1 << 4); + let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - (1 << 4); match new_shifted_ordinal > 0 { - true => Some(NaiveDate { yof: self.yof & !ORDINAL_MASK | new_shifted_ordinal }), + true => Some(NaiveDate::from_yof(self.yof() & !ORDINAL_MASK | new_shifted_ordinal)), false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), } } @@ -1330,20 +1331,20 @@ impl NaiveDate { /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); /// ``` pub const fn leap_year(&self) -> bool { - self.yof & (0b1000) == 0 + self.yof() & (0b1000) == 0 } // This duplicates `Datelike::year()`, because trait methods can't be const yet. #[inline] const fn year(&self) -> i32 { - self.yof >> 13 + self.yof() >> 13 } /// Returns the day of year starting from 1. // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. #[inline] const fn ordinal(&self) -> u32 { - ((self.yof & ORDINAL_MASK) >> 4) as u32 + ((self.yof() & ORDINAL_MASK) >> 4) as u32 } // This duplicates `Datelike::month()`, because trait methods can't be const yet. @@ -1362,7 +1363,7 @@ impl NaiveDate { // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. #[inline] pub(super) const fn weekday(&self) -> Weekday { - match (((self.yof & ORDINAL_MASK) >> 4) + (self.yof & WEEKDAY_FLAGS_MASK)) % 7 { + match (((self.yof() & ORDINAL_MASK) >> 4) + (self.yof() & WEEKDAY_FLAGS_MASK)) % 7 { 0 => Weekday::Mon, 1 => Weekday::Tue, 2 => Weekday::Wed, @@ -1375,7 +1376,7 @@ impl NaiveDate { #[inline] const fn year_flags(&self) -> YearFlags { - YearFlags((self.yof & YEAR_FLAGS_MASK) as u8) + YearFlags((self.yof() & YEAR_FLAGS_MASK) as u8) } /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. @@ -1394,17 +1395,32 @@ impl NaiveDate { ndays + self.ordinal() as i32 } + /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. + /// + /// In a valid value an ordinal is never `0`, and neither are the year flags. This method + /// doesn't do any validation; it only panics if the value is `0`. + #[inline] + const fn from_yof(yof: i32) -> NaiveDate { + NaiveDate { yof: expect!(NonZeroI32::new(yof), "invalid internal value") } + } + + /// Get the raw year-ordinal-flags `i32`. + #[inline] + const fn yof(&self) -> i32 { + self.yof.get() + } + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). - pub const MIN: NaiveDate = NaiveDate { yof: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; + pub const MIN: NaiveDate = NaiveDate::from_yof((MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/); /// The maximum possible `NaiveDate` (December 31, 262142 CE). - pub const MAX: NaiveDate = NaiveDate { yof: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; + pub const MAX: NaiveDate = NaiveDate::from_yof((MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/); /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). pub(crate) const BEFORE_MIN: NaiveDate = - NaiveDate { yof: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; + NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/); /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). pub(crate) const AFTER_MAX: NaiveDate = - NaiveDate { yof: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; + NaiveDate::from_yof(((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/); } impl Datelike for NaiveDate { @@ -1550,7 +1566,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal(&self) -> u32 { - ((self.yof & ORDINAL_MASK) >> 4) as u32 + ((self.yof() & ORDINAL_MASK) >> 4) as u32 } /// Returns the day of year starting from 0. @@ -1741,9 +1757,9 @@ impl Datelike for NaiveDate { if ordinal == 0 || ordinal > 366 { return None; } - let yof = (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32; + let yof = (self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32; match yof & OL_MASK <= MAX_OL { - true => Some(NaiveDate { yof }), + true => Some(NaiveDate::from_yof(yof)), false => None, // Does not exist: Ordinal 366 in a common year. } } From 4d4ff14bb9756265364ada3d1679e28a9370732e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 15:11:17 +0100 Subject: [PATCH 727/999] CI: test cross-compiling to `x86_64-unknown-illumos` instead of Solaris --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b639d866c8..9d9da22809 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -169,7 +169,7 @@ jobs: strategy: matrix: target: - - x86_64-sun-solaris + - x86_64-unknown-illumos runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 97403fdc300bd7de74d482c45b39f582ebe6cf8d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 21:26:46 +0100 Subject: [PATCH 728/999] Add benchmark for `NaiveDate::from_ymd` --- bench/benches/chrono.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index ab6a324453..528b7e8195 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -8,6 +8,16 @@ use chrono::prelude::*; use chrono::Locale; use chrono::{DateTime, FixedOffset, Local, TimeDelta, Utc, __BenchYearFlags}; +fn bench_date_from_ymd(c: &mut Criterion) { + c.bench_function("bench_date_from_ymd", |b| { + let expected = NaiveDate::from_ymd_opt(2024, 2, 12); + b.iter(|| { + let (y, m, d) = black_box((2024, 2, 12)); + assert_eq!(NaiveDate::from_ymd_opt(y, m, d), expected) + }) + }); +} + fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { c.bench_function("bench_datetime_parse_from_rfc2822", |b| { b.iter(|| { @@ -213,6 +223,7 @@ fn bench_datetime_with(c: &mut Criterion) { criterion_group!( benches, + bench_date_from_ymd, bench_datetime_parse_from_rfc2822, bench_datetime_parse_from_rfc3339, bench_datetime_from_str, From 7db1dc6d84f1536dd5fb9cd9d1ba3e2aa42404e3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 18:45:25 +0100 Subject: [PATCH 729/999] Add doc comments to `Mdf` methods --- src/naive/internals.rs | 68 +++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 26576d865e..71dad0b2d3 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -1,17 +1,4 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! The internal implementation of the calendar and ordinal date. -//! -//! The current implementation is optimized for determining year, month, day and day of week. -//! 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar, -//! which are included in every packed `NaiveDate` instance. -//! The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is -//! based on the moderately-sized lookup table (~1.5KB) -//! and the packed representation is chosen for the efficient lookup. -//! Every internal data structure does not validate its input, -//! but the conversion keeps the valid value valid and the invalid value invalid -//! so that the user-facing `NaiveDate` can validate the input as late as possible. +//! Internal helper types for working with dates. #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] @@ -130,6 +117,9 @@ impl fmt::Debug for YearFlags { pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; +// The next table are adjustment values to convert a date encoded as month-day-leapyear to +// ordinal-leapyear. OL = MDL - adjustment. +// Dates that do not exist are encoded as `XX`. const XX: i8 = -128; const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, @@ -218,17 +208,28 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` /// `M_MMMD_DDDD_LFFF` /// -/// The whole bits except for the least 3 bits are referred as `Mdl` -/// (month, day of month and leap flag), -/// which is an index to the `MDL_TO_OL` lookup table. +/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap +/// year flag), which is an index to the `MDL_TO_OL` lookup table. /// -/// The methods implemented on `Mdf` do not always return a valid value. -/// Dates that can't exist, like February 30, can still be represented. -/// Use `Mdl::valid` to check whether the date is valid. +/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is +/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for +/// efficient lookup. +/// +/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like +/// February 30, can still be represented. This allows the validation to be combined with the final +/// table lookup, which is good for performance. #[derive(PartialEq, PartialOrd, Copy, Clone)] pub(super) struct Mdf(u32); impl Mdf { + /// Makes a new `Mdf` value from month, day and `YearFlags`. + /// + /// This method doesn't fully validate the range of the `month` and `day` parameters, only as + /// much as what can't be deferred until later. The year `flags` are trusted to be correct. + /// + /// # Errors + /// + /// Returns `None` if `month > 12` or `day > 31`. #[inline] pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { match month >= 1 && month <= 12 && day >= 1 && day <= 31 { @@ -237,6 +238,10 @@ impl Mdf { } } + /// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year + /// `flags`. + /// + /// The `ol` is trusted to be valid, and the `flags` are trusted to match it. #[inline] pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf { debug_assert!(ol > 1 && ol <= MAX_OL as i32); @@ -257,12 +262,18 @@ impl Mdf { } } + /// Returns the month of this `Mdf`. #[inline] pub(super) const fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } + /// Replaces the month of this `Mdf`, keeping the day and flags. + /// + /// # Errors + /// + /// Returns `None` if `month > 12`. #[inline] pub(super) const fn with_month(&self, month: u32) -> Option { if month > 12 { @@ -273,12 +284,18 @@ impl Mdf { Some(Mdf((mdf & 0b1_1111_1111) | (month << 9))) } + /// Returns the day of this `Mdf`. #[inline] pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } + /// Replaces the day of this `Mdf`, keeping the month and flags. + /// + /// # Errors + /// + /// Returns `None` if `day > 31`. #[inline] pub(super) const fn with_day(&self, day: u32) -> Option { if day > 31 { @@ -289,12 +306,22 @@ impl Mdf { Some(Mdf((mdf & !0b1_1111_0000) | (day << 4))) } + /// Replaces the flags of this `Mdf`, keeping the month and day. #[inline] pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; Mdf((mdf & !0b1111) | flags as u32) } + /// Returns the ordinal that corresponds to this `Mdf`. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. #[inline] pub(super) const fn ordinal(&self) -> Option { let mdl = self.0 >> 3; @@ -304,6 +331,7 @@ impl Mdf { } } + /// Returns the year flags of this `Mdf`. #[inline] pub(super) const fn year_flags(&self) -> YearFlags { YearFlags((self.0 & 0b1111) as u8) From 6d8ce13a7185ac5701e72d707566a2f908b3bd6f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 07:21:39 +0100 Subject: [PATCH 730/999] `Mdf::new`: remove checks that are also done by the table lookup --- src/naive/internals.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 71dad0b2d3..f9dfaddb56 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -232,7 +232,7 @@ impl Mdf { /// Returns `None` if `month > 12` or `day > 31`. #[inline] pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { - match month >= 1 && month <= 12 && day >= 1 && day <= 31 { + match month <= 12 && day <= 31 { true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), false => None, } @@ -557,12 +557,9 @@ mod tests { } #[test] - fn test_invalid_returns_none() { + fn test_mdf_new_range() { let flags = YearFlags::from_year(2023); - assert!(Mdf::new(0, 1, flags).is_none()); assert!(Mdf::new(13, 1, flags).is_none()); - assert!(Mdf::new(1, 0, flags).is_none()); assert!(Mdf::new(1, 32, flags).is_none()); - assert!(Mdf::new(2, 31, flags).is_some()); } } From 8cc41ddf028d7f4ab01ba182149c86df9b86c752 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:30:07 +0100 Subject: [PATCH 731/999] Don't use `NaiveDate::from_ordinal_and_flags` in `from_mdf` It introduced a couple of extra checks in performance-sensitive code. --- src/naive/date/mod.rs | 6 +++--- src/naive/internals.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 44335a59f9..6b677e632e 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -149,10 +149,10 @@ impl NaiveDate { /// Makes a new `NaiveDate` from year and packed month-day-flags. /// Does not check whether the flags are correct for the provided year. const fn from_mdf(year: i32, mdf: Mdf) -> Option { - match mdf.ordinal() { - Some(ordinal) => NaiveDate::from_ordinal_and_flags(year, ordinal, mdf.year_flags()), - None => None, // Non-existing date + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range } + Some(NaiveDate::from_yof((year << 13) | try_opt!(mdf.ordinal_and_flags()))) } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index f9dfaddb56..f2c4ca065d 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -336,6 +336,24 @@ impl Mdf { pub(super) const fn year_flags(&self) -> YearFlags { YearFlags((self.0 & 0b1111) as u8) } + + /// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. + #[inline] + pub(super) const fn ordinal_and_flags(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some(self.0 as i32 - ((v as i32) << 3)), + } + } } impl fmt::Debug for Mdf { From 2703ca2d20ecb986b97bea03fe0f8d67ef9d3850 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:36:08 +0100 Subject: [PATCH 732/999] Use `NonZeroI32::new_unchecked` --- src/naive/date/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 6b677e632e..4ab09e1eb2 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1398,10 +1398,11 @@ impl NaiveDate { /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. /// /// In a valid value an ordinal is never `0`, and neither are the year flags. This method - /// doesn't do any validation; it only panics if the value is `0`. + /// doesn't do any validation. #[inline] const fn from_yof(yof: i32) -> NaiveDate { - NaiveDate { yof: expect!(NonZeroI32::new(yof), "invalid internal value") } + debug_assert!(yof != 0); + NaiveDate { yof: unsafe { NonZeroI32::new_unchecked(yof) } } } /// Get the raw year-ordinal-flags `i32`. From 6eec070603f2480901210f9694f6fd2d8abe8f7f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:34:41 +0100 Subject: [PATCH 733/999] Comparing against zero is slightly faster --- src/naive/internals.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index f2c4ca065d..98f8333cfd 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -120,7 +120,7 @@ pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; // The next table are adjustment values to convert a date encoded as month-day-leapyear to // ordinal-leapyear. OL = MDL - adjustment. // Dates that do not exist are encoded as `XX`. -const XX: i8 = -128; +const XX: i8 = 0; const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, @@ -255,7 +255,7 @@ impl Mdf { let mdl = mdf >> 3; if mdl <= MAX_MDL { // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. - MDL_TO_OL[mdl as usize] >= 0 + MDL_TO_OL[mdl as usize] > 0 } else { // Panicking here would be reasonable, but we are just going on with a safe value. false From e3ff4bc1d2d8a01df98f0230954d18ac71b32e42 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:31:43 +0100 Subject: [PATCH 734/999] Casting a positive `i8` through `u8` is slightly faster --- src/naive/internals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 98f8333cfd..7ea5650fa7 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -327,7 +327,7 @@ impl Mdf { let mdl = self.0 >> 3; match MDL_TO_OL[mdl as usize] { XX => None, - v => Some((mdl - v as i32 as u32) >> 1), + v => Some((mdl - v as u8 as u32) >> 1), } } From 83ad6207b2cea857025add52858cd03555e390e8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:30:47 +0100 Subject: [PATCH 735/999] Simplify test-only `Mdf::valid` method --- src/naive/internals.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 7ea5650fa7..ba65f04d47 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -249,19 +249,6 @@ impl Mdf { Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) } - #[cfg(test)] - pub(super) const fn valid(&self) -> bool { - let Mdf(mdf) = *self; - let mdl = mdf >> 3; - if mdl <= MAX_MDL { - // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. - MDL_TO_OL[mdl as usize] > 0 - } else { - // Panicking here would be reasonable, but we are just going on with a safe value. - false - } - } - /// Returns the month of this `Mdf`. #[inline] pub(super) const fn month(&self) -> u32 { @@ -354,6 +341,12 @@ impl Mdf { v => Some(self.0 as i32 - ((v as i32) << 3)), } } + + #[cfg(test)] + fn valid(&self) -> bool { + let mdl = self.0 >> 3; + MDL_TO_OL[mdl as usize] > 0 + } } impl fmt::Debug for Mdf { From 367fe84be65070408ac087d1d157b109f1e6f8b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 16:28:32 +0100 Subject: [PATCH 736/999] `MAX_OL` and `MAX_MDL` can be private --- src/naive/internals.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index ba65f04d47..41dc593a27 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -114,8 +114,8 @@ impl fmt::Debug for YearFlags { } // OL: (ordinal << 1) | leap year flag -pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year -pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; +const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year +const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; // The next table are adjustment values to convert a date encoded as month-day-leapyear to // ordinal-leapyear. OL = MDL - adjustment. From 21343bec7e4776bcc4a299110d6de48f7b27f6c2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 14:22:51 +0100 Subject: [PATCH 737/999] Extend `YearFlags` documentation/constants --- src/naive/internals.rs | 53 +++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 41dc593a27..da591f8d08 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -4,32 +4,47 @@ use core::fmt; -/// The year flags (aka the dominical letter). +/// Year flags (aka the dominical letter). +/// +/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`. /// /// There are 14 possible classes of year in the Gregorian calendar: /// common and leap years starting with Monday through Sunday. -/// The `YearFlags` stores this information into 4 bits `abbb`, -/// where `a` is `1` for the common year (simplifies the `Of` validation) -/// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year -/// (simplifies the day of week calculation from the 1-based ordinal). +/// +/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1` +/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero +/// `Weekday` of the last day in the preceding year. #[allow(unreachable_pub)] // public as an alias for benchmarks only #[derive(PartialEq, Eq, Copy, Clone, Hash)] pub struct YearFlags(pub(super) u8); -pub(super) const A: YearFlags = YearFlags(0o15); -pub(super) const AG: YearFlags = YearFlags(0o05); -pub(super) const B: YearFlags = YearFlags(0o14); -pub(super) const BA: YearFlags = YearFlags(0o04); -pub(super) const C: YearFlags = YearFlags(0o13); -pub(super) const CB: YearFlags = YearFlags(0o03); -pub(super) const D: YearFlags = YearFlags(0o12); -pub(super) const DC: YearFlags = YearFlags(0o02); -pub(super) const E: YearFlags = YearFlags(0o11); -pub(super) const ED: YearFlags = YearFlags(0o01); -pub(super) const F: YearFlags = YearFlags(0o17); -pub(super) const FE: YearFlags = YearFlags(0o07); -pub(super) const G: YearFlags = YearFlags(0o16); -pub(super) const GF: YearFlags = YearFlags(0o06); +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`. +const YEAR_STARTS_AFTER_THUESDAY: u8 = 1; +const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2; +const YEAR_STARTS_AFTER_THURSDAY: u8 = 3; +const YEAR_STARTS_AFTER_FRIDAY: u8 = 4; +const YEAR_STARTS_AFTER_SATURDAY: u8 = 5; +const YEAR_STARTS_AFTER_SUNDAY: u8 = 6; + +const COMMON_YEAR: u8 = 1 << 3; +const LEAP_YEAR: u8 = 0 << 3; + +pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY); +pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY); const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, From 042fd0c28634a6f4e904c957a0115f830cdd3e3d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 18:46:33 +0100 Subject: [PATCH 738/999] Use `TimeDelta` instead of `Duration` alias --- src/offset/local/windows.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index cee09eca8b..df29430e42 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -184,7 +184,7 @@ mod tests { use crate::offset::local::win_bindings::{ SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, FILETIME, SYSTEMTIME, }; - use crate::{DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime}; + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta}; use crate::{Datelike, TimeZone, Timelike}; use std::mem::MaybeUninit; use std::ptr; @@ -256,7 +256,7 @@ mod tests { if let Some(our_result) = Local.from_local_datetime(&date).earliest() { assert_eq!(from_local_time(&date), our_result); } - date += Duration::hours(1); + date += TimeDelta::hours(1); } } } From c8ff2c37a441d2111e5ae8be7aef0e540d90fc00 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Feb 2024 18:47:23 +0100 Subject: [PATCH 739/999] Fix clippy warning --- src/offset/local/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index df29430e42..de48f0653c 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -103,7 +103,7 @@ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult return LocalResult::Single(tz_info.std_offset), + (None, None) => LocalResult::Single(tz_info.std_offset), } } From e2e0dfe3159d0b3b7c8803b2ad0126a929665d64 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 14 Feb 2024 13:49:10 +0100 Subject: [PATCH 740/999] Run clippy also against Windows target --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3e06ad3209..b0917746ce 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,6 +25,9 @@ jobs: - run: | cargo clippy ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --all-targets --color=always \ -- -D warnings + - run: | + cargo clippy --target=x86_64-pc-windows-msvc --all-targets --color=always \ + -- -D warnings - run: | cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ -- -D warnings From 60f8b742c9ac1efcd5eca023cb2cdb56450f377d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 14 Feb 2024 14:34:40 +0100 Subject: [PATCH 741/999] CI: only run `cargo hack check` on Linux --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9d9da22809..775faad3ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: features_check: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From 45f8cecabc701c917bac43a6c20f9d926acdf078 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 13:57:40 +0100 Subject: [PATCH 742/999] Rustfmt doctests --- src/datetime/mod.rs | 156 ++++++++--- src/datetime/serde.rs | 184 ++++++++----- src/lib.rs | 122 +++++++-- src/naive/date/mod.rs | 265 ++++++++++-------- src/naive/datetime/mod.rs | 523 ++++++++++++++++++++++++------------ src/naive/datetime/serde.rs | 152 ++++++----- src/naive/isoweek.rs | 27 +- src/naive/time/mod.rs | 292 ++++++++++++-------- src/offset/fixed.rs | 12 +- src/offset/local/mod.rs | 2 +- src/offset/mod.rs | 8 +- src/offset/utc.rs | 2 +- src/round.rs | 35 ++- src/traits.rs | 8 +- 14 files changed, 1174 insertions(+), 614 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e4e460b280..059f97d816 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -81,7 +81,7 @@ impl DateTime { /// #[cfg_attr(not(feature = "clock"), doc = "```ignore")] #[cfg_attr(feature = "clock", doc = "```rust")] - /// use chrono::{Local, DateTime}; + /// use chrono::{DateTime, Local}; /// /// let dt = Local::now(); /// // Get components @@ -166,7 +166,8 @@ impl DateTime { /// use chrono::prelude::*; /// /// let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); - /// let other: DateTime = FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// let other: DateTime = + /// FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); /// assert_eq!(date.date_naive(), other.date_naive()); /// ``` #[inline] @@ -208,12 +209,22 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, NaiveDate}; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_milli_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_milli_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// ``` #[inline] @@ -227,12 +238,22 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, NaiveDate}; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_micro_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_micro_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] @@ -271,24 +292,54 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{Utc, NaiveDate}; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_nano_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_nano_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); /// - /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21).unwrap().and_hms_nano_opt(0, 12, 43, 145_224_192).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_192) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), Some(-9_223_372_036_854_775_808)); /// - /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11).unwrap().and_hms_nano_opt(23, 47, 16, 854_775_807).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_807) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), Some(9_223_372_036_854_775_807)); /// - /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21).unwrap().and_hms_nano_opt(0, 12, 43, 145_224_191).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_191) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), None); /// - /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11).unwrap().and_hms_nano_opt(23, 47, 16, 854_775_808).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_808) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.timestamp_nanos_opt(), None); /// ``` #[inline] @@ -566,18 +617,26 @@ impl DateTime { /// /// ```rust /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(18, 30, 9, 453_829) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T18:30:09Z"); /// /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); + /// let dt = pst + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(10, 30, 9, 453_829) + /// .unwrap(), + /// ) + /// .unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T10:30:09+08:00"); /// ``` #[cfg(feature = "alloc")] #[must_use] @@ -619,7 +678,8 @@ impl DateTime { /// ``` /// use chrono::{DateTime, Utc}; /// - /// let dt: DateTime = DateTime::::from_timestamp(1431648000, 0).expect("invalid timestamp"); + /// let dt: DateTime = + /// DateTime::::from_timestamp(1431648000, 0).expect("invalid timestamp"); /// /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); @@ -650,7 +710,8 @@ impl DateTime { /// ``` /// use chrono::{DateTime, Utc}; /// - /// let dt: DateTime = DateTime::::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// let dt: DateTime = + /// DateTime::::from_timestamp_millis(947638923004).expect("invalid timestamp"); /// /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); @@ -837,11 +898,21 @@ impl DateTime { /// # Example /// /// ```rust - /// use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; + /// use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone}; /// - /// let dt = DateTime::parse_from_str( - /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east_opt(0).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(1983, 4, 13).unwrap().and_hms_milli_opt(12, 9, 14, 274).unwrap()).unwrap())); + /// let dt = DateTime::parse_from_str("1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); + /// assert_eq!( + /// dt, + /// Ok(FixedOffset::east_opt(0) + /// .unwrap() + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(1983, 4, 13) + /// .unwrap() + /// .and_hms_milli_opt(12, 9, 14, 274) + /// .unwrap() + /// ) + /// .unwrap()) + /// ); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); @@ -867,10 +938,13 @@ impl DateTime { /// ```rust /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// let (datetime, remainder) = DateTime::parse_and_remainder( - /// "2015-02-18 23:16:09 +0200 trailing text", "%Y-%m-%d %H:%M:%S %z").unwrap(); + /// "2015-02-18 23:16:09 +0200 trailing text", + /// "%Y-%m-%d %H:%M:%S %z", + /// ) + /// .unwrap(); /// assert_eq!( /// datetime, - /// FixedOffset::east_opt(2*3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// assert_eq!(remainder, " trailing text"); /// ``` @@ -1213,8 +1287,14 @@ impl PartialOrd> for DateTime { /// ``` /// use chrono::prelude::*; /// - /// let earlier = Utc.with_ymd_and_hms(2015, 5, 15, 2, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); - /// let later = Utc.with_ymd_and_hms(2015, 5, 15, 3, 0, 0).unwrap().with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); + /// let earlier = Utc + /// .with_ymd_and_hms(2015, 5, 15, 2, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); + /// let later = Utc + /// .with_ymd_and_hms(2015, 5, 15, 3, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); /// /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index dcdb36dd0b..e9f5d7e3ef 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -127,13 +127,16 @@ impl<'de> de::Deserialize<'de> for DateTime { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds")] -/// time: DateTime +/// time: DateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -171,11 +174,16 @@ pub mod ts_nanoseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_nano_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -203,7 +211,7 @@ pub mod ts_nanoseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; @@ -267,13 +275,18 @@ pub mod ts_nanoseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds_option")] -/// time: Option> +/// time: Option>, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -310,11 +323,18 @@ pub mod ts_nanoseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_nano_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap().and_local_timezone(Utc).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -345,7 +365,7 @@ pub mod ts_nanoseconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; @@ -407,13 +427,16 @@ pub mod ts_nanoseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds")] -/// time: DateTime +/// time: DateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -442,11 +465,16 @@ pub mod ts_microseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_micro_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -472,7 +500,7 @@ pub mod ts_microseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; @@ -536,13 +564,18 @@ pub mod ts_microseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds_option")] -/// time: Option> +/// time: Option>, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -570,11 +603,18 @@ pub mod ts_microseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_micro_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -603,7 +643,7 @@ pub mod ts_microseconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; @@ -665,13 +705,16 @@ pub mod ts_microseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds")] -/// time: DateTime +/// time: DateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -700,11 +743,16 @@ pub mod ts_milliseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_milli_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -730,7 +778,7 @@ pub mod ts_milliseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; @@ -788,13 +836,18 @@ pub mod ts_milliseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds_option")] -/// time: Option> +/// time: Option>, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -822,11 +875,18 @@ pub mod ts_milliseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_milli_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap().and_local_timezone(Utc).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -862,7 +922,7 @@ pub mod ts_milliseconds_option { /// #[derive(Deserialize, PartialEq, Debug)] /// struct S { /// #[serde(default, deserialize_with = "from_milli_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; @@ -929,13 +989,11 @@ pub mod ts_milliseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds")] -/// time: DateTime +/// time: DateTime, /// } /// /// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -963,12 +1021,10 @@ pub mod ts_seconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_ts")] - /// time: DateTime + /// time: DateTime, /// } /// - /// let my_s = S { - /// time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(), - /// }; + /// let my_s = S { time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap() }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) @@ -993,7 +1049,7 @@ pub mod ts_seconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_ts")] - /// time: DateTime + /// time: DateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; @@ -1052,13 +1108,11 @@ pub mod ts_seconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds_option")] -/// time: Option> +/// time: Option>, /// } /// /// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -1086,12 +1140,10 @@ pub mod ts_seconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_tsopt")] - /// time: Option> + /// time: Option>, /// } /// - /// let my_s = S { - /// time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()), - /// }; + /// let my_s = S { time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()) }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) @@ -1119,7 +1171,7 @@ pub mod ts_seconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_tsopt")] - /// time: Option> + /// time: Option>, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; diff --git a/src/lib.rs b/src/lib.rs index 5cba8f8f8a..39f310dd22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ #![cfg_attr(feature = "now", doc = "```rust")] //! use chrono::prelude::*; //! -//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` +//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! # let _ = utc; //! ``` //! @@ -125,34 +125,74 @@ //! #![cfg_attr(not(feature = "now"), doc = "```ignore")] #![cfg_attr(feature = "now", doc = "```rust")] -//! use chrono::prelude::*; //! use chrono::offset::LocalResult; +//! use chrono::prelude::*; //! //! # fn doctest() -> Option<()> { //! //! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` -//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(9, 10, 11)?.and_local_timezone(Utc).unwrap()); +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_opt(9, 10, 11)? +//! .and_local_timezone(Utc) +//! .unwrap() +//! ); //! //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") //! assert_eq!(dt, NaiveDate::from_yo_opt(2014, 189)?.and_hms_opt(9, 10, 11)?.and_utc()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. -//! assert_eq!(dt, NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc()); -//! -//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_milli_opt(9, 10, 11, 12)?.and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_micro_opt(9, 10, 11, 12_000)?.and_local_timezone(Utc).unwrap()); -//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_nano_opt(9, 10, 11, 12_000_000)?.and_local_timezone(Utc).unwrap()); +//! assert_eq!( +//! dt, +//! NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc() +//! ); +//! +//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_milli_opt(9, 10, 11, 12)? +//! .and_local_timezone(Utc) +//! .unwrap(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_micro_opt(9, 10, 11, 12_000)? +//! .and_local_timezone(Utc) +//! .unwrap() +//! ); +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_nano_opt(9, 10, 11, 12_000_000)? +//! .and_local_timezone(Utc) +//! .unwrap() +//! ); //! //! // dynamic verification -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), -//! LocalResult::Single(NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc())); +//! assert_eq!( +//! Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), +//! LocalResult::Single( +//! NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc() +//! ) +//! ); //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); //! //! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -//! let local_dt = Local.from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap()).unwrap(); -//! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); +//! let local_dt = Local +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(), +//! ) +//! .unwrap(); +//! let fixed_dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8) +//! .unwrap() +//! .and_hms_milli_opt(18, 10, 11, 12) +//! .unwrap(), +//! ) +//! .unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; //! # } @@ -172,7 +212,15 @@ //! use chrono::TimeDelta; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: -//! let dt = FixedOffset::east_opt(9*3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap()).unwrap(); +//! let dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(21, 45, 59, 324310806) +//! .unwrap(), +//! ) +//! .unwrap(); //! //! // property accessors //! assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); @@ -186,7 +234,15 @@ //! // time zone accessor and manipulation //! assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); //! assert_eq!(dt.timezone(), FixedOffset::east_opt(9 * 3600).unwrap()); -//! assert_eq!(dt.with_timezone(&Utc), NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 45, 59, 324310806).unwrap().and_local_timezone(Utc).unwrap()); +//! assert_eq!( +//! dt.with_timezone(&Utc), +//! NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 45, 59, 324310806) +//! .unwrap() +//! .and_local_timezone(Utc) +//! .unwrap() +//! ); //! //! // a sample of property manipulations (validates dynamically) //! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -198,10 +254,14 @@ //! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); //! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::seconds(-2 * 3600 + 2)); //! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000), -//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::seconds(1_000_000_000), -//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap() +//! ); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::seconds(1_000_000_000), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap() +//! ); //! ``` //! //! ### Formatting and Parsing @@ -236,7 +296,10 @@ //! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -//! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); +//! assert_eq!( +//! dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), +//! "vendredi 28 novembre 2014, 12:00:09" +//! ); //! //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); //! assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); @@ -245,7 +308,12 @@ //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); //! //! // Note that milli/nanoseconds are only printed if they are non-zero -//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(12, 0, 9, 1).unwrap().and_local_timezone(Utc).unwrap(); +//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 0, 9, 1) +//! .unwrap() +//! .and_local_timezone(Utc) +//! .unwrap(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } //! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))] @@ -280,7 +348,7 @@ //! use chrono::prelude::*; //! //! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); -//! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9*3600).unwrap()); +//! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9 * 3600).unwrap()); //! //! // method 1 //! assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); @@ -288,10 +356,14 @@ //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); //! //! // method 2 -//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), -//! Ok(fixed_dt.clone())); -//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), -//! Ok(fixed_dt.clone())); +//! assert_eq!( +//! DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), +//! Ok(fixed_dt.clone()) +//! ); +//! assert_eq!( +//! DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), +//! Ok(fixed_dt.clone()) +//! ); //! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! //! // oops, the year is missing! diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 4ab09e1eb2..e3e2dd9f82 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -371,11 +371,11 @@ impl NaiveDate { /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// - /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); - /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); - /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); - /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); - /// assert_eq!(from_ndays_opt(100_000_000), None); + /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); + /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); + /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); + /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); + /// assert_eq!(from_ndays_opt(100_000_000), None); /// assert_eq!(from_ndays_opt(-100_000_000), None); /// ``` #[must_use] @@ -426,8 +426,10 @@ impl NaiveDate { /// /// ``` /// use chrono::{NaiveDate, Weekday}; - /// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), - /// NaiveDate::from_ymd_opt(2017, 3, 10)) + /// assert_eq!( + /// NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), + /// NaiveDate::from_ymd_opt(2017, 3, 10) + /// ) /// ``` #[must_use] pub const fn from_weekday_of_month_opt( @@ -456,10 +458,14 @@ impl NaiveDate { /// /// let parse_from_str = NaiveDate::parse_from_str; /// - /// assert_eq!(parse_from_str("2015-09-05", "%Y-%m-%d"), - /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); - /// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"), - /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())); + /// assert_eq!( + /// parse_from_str("2015-09-05", "%Y-%m-%d"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015", "%d%b%Y"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); /// ``` /// /// Time and offset is ignored for the purpose of parsing. @@ -467,8 +473,10 @@ impl NaiveDate { /// ``` /// # use chrono::NaiveDate; /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap())); + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap()) + /// ); /// ``` /// /// Out-of-bound dates or insufficient fields are errors. @@ -504,8 +512,8 @@ impl NaiveDate { /// /// ```rust /// # use chrono::{NaiveDate}; - /// let (date, remainder) = NaiveDate::parse_and_remainder( - /// "2015-02-18 trailing text", "%Y-%m-%d").unwrap(); + /// let (date, remainder) = + /// NaiveDate::parse_and_remainder("2015-02-18 trailing text", "%Y-%m-%d").unwrap(); /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); /// assert_eq!(remainder, " trailing text"); /// ``` @@ -566,7 +574,8 @@ impl NaiveDate { /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap() + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -716,7 +725,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); @@ -803,12 +812,12 @@ impl NaiveDate { /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); - /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); + /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); - /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); + /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); /// ``` #[inline] #[must_use] @@ -835,7 +844,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike, Weekday}; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// @@ -867,12 +876,12 @@ impl NaiveDate { /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); - /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); + /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); /// ``` #[inline] #[must_use] @@ -917,12 +926,12 @@ impl NaiveDate { /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); - /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); + /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); /// ``` #[inline] #[must_use] @@ -980,8 +989,10 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), - /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap())); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap()) + /// ); /// assert_eq!(NaiveDate::MAX.succ_opt(), None); /// ``` #[inline] @@ -1017,8 +1028,10 @@ impl NaiveDate { /// ``` /// use chrono::NaiveDate; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), - /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap())); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap()) + /// ); /// assert_eq!(NaiveDate::MIN.pred_opt(), None); /// ``` #[inline] @@ -1040,13 +1053,17 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(d.checked_add_signed(TimeDelta::days(40)), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); - /// assert_eq!(d.checked_add_signed(TimeDelta::days(-40)), - /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::days(40)), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::days(-40)), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); /// assert_eq!(d.checked_add_signed(TimeDelta::days(1_000_000_000)), None); /// assert_eq!(d.checked_add_signed(TimeDelta::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::days(1)), None); @@ -1069,13 +1086,17 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(d.checked_sub_signed(TimeDelta::days(40)), - /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap())); - /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-40)), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap())); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::days(40)), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::days(-40)), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); /// assert_eq!(d.checked_sub_signed(TimeDelta::days(1_000_000_000)), None); /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-1_000_000_000)), None); /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::days(1)), None); @@ -1098,7 +1119,7 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let since = NaiveDate::signed_duration_since; @@ -1108,8 +1129,8 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::days(-1)); /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), TimeDelta::days(100)); /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), TimeDelta::days(365)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), TimeDelta::days(365*4 + 1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), TimeDelta::days(365*400 + 97)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), TimeDelta::days(365 * 4 + 1)); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), TimeDelta::days(365 * 400 + 97)); /// ``` #[must_use] pub const fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { @@ -1151,13 +1172,13 @@ impl NaiveDate { /// # Example /// /// ``` - /// use chrono::NaiveDate; /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; /// /// let fmt = StrftimeItems::new("%Y-%m-%d"); /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -1266,8 +1287,8 @@ impl NaiveDate { /// /// let mut count = 0; /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; + /// assert_eq!(d, expected[idx]); + /// count += 1; /// } /// assert_eq!(count, 4); /// @@ -1297,8 +1318,8 @@ impl NaiveDate { /// /// let mut count = 0; /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; + /// assert_eq!(d, expected[idx]); + /// count += 1; /// } /// assert_eq!(count, 4); /// @@ -1430,7 +1451,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE @@ -1447,7 +1468,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); @@ -1464,7 +1485,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); @@ -1481,7 +1502,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day(), 8); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); @@ -1492,7 +1513,7 @@ impl Datelike for NaiveDate { /// (Note that this panics when `year` is out of range.) /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// fn ndays_in_month(year: i32, month: u32) -> u32 { /// // the first day of the next month... @@ -1521,7 +1542,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); @@ -1538,7 +1559,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal(), 251); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); @@ -1549,7 +1570,7 @@ impl Datelike for NaiveDate { /// (Note that this panics when `year` is out of range.) /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// fn ndays_in_year(year: i32) -> u32 { /// // the first day of the next year... @@ -1577,7 +1598,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal0(), 250); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal0(), 73); @@ -1592,7 +1613,7 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); @@ -1617,12 +1638,16 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(2016), - /// Some(NaiveDate::from_ymd_opt(2016, 9, 8).unwrap())); - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(-308), - /// Some(NaiveDate::from_ymd_opt(-308, 9, 8).unwrap())); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 8).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 8).unwrap()) + /// ); /// ``` /// /// A leap day (February 29) is a good example that this method can return `None`. @@ -1653,12 +1678,15 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap())); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); + /// // no February 30 /// ``` #[inline] fn with_month(&self, month: u32) -> Option { @@ -1675,12 +1703,15 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), - /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap())); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); + /// // no February 30 /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { @@ -1697,12 +1728,14 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), - /// None); // no September 31 + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), None); + /// // no September 31 /// ``` #[inline] fn with_day(&self, day: u32) -> Option { @@ -1718,12 +1751,14 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap())); - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), - /// None); // no September 31 + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), None); + /// // no September 31 /// ``` #[inline] fn with_day0(&self, day0: u32) -> Option { @@ -1807,18 +1842,18 @@ impl Datelike for NaiveDate { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(-1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(364), from_ymd(2014, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*4 + 1), from_ymd(2018, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365*400 + 97), from_ymd(2414, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(1), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(-1), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(364), from_ymd(2014, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365 * 4 + 1), from_ymd(2018, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365 * 400 + 97), from_ymd(2414, 1, 1)); /// ``` /// /// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed @@ -1860,7 +1895,7 @@ impl AddAssign for NaiveDate { /// # Example /// /// ``` -/// use chrono::{NaiveDate, Months}; +/// use chrono::{Months, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// @@ -1892,7 +1927,7 @@ impl Add for NaiveDate { /// # Example /// /// ``` -/// use chrono::{NaiveDate, Months}; +/// use chrono::{Months, NaiveDate}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// @@ -1950,18 +1985,18 @@ impl Sub for NaiveDate { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(-1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(364), from_ymd(2013, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*4 + 1), from_ymd(2010, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365*400 + 97), from_ymd(1614, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(1), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(-1), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(364), from_ymd(2013, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365 * 4 + 1), from_ymd(2010, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365 * 400 + 97), from_ymd(1614, 1, 1)); /// ``` /// /// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed @@ -2003,7 +2038,7 @@ impl SubAssign for NaiveDate { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// @@ -2012,8 +2047,8 @@ impl SubAssign for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::days(-1)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::days(100)); /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), TimeDelta::days(365*4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), TimeDelta::days(365*400 + 97)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), TimeDelta::days(365 * 4 + 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), TimeDelta::days(365 * 400 + 97)); /// ``` impl Sub for NaiveDate { type Output = TimeDelta; @@ -2109,8 +2144,8 @@ impl FusedIterator for NaiveDateWeeksIterator {} /// ``` /// use chrono::NaiveDate; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// @@ -2118,7 +2153,7 @@ impl FusedIterator for NaiveDateWeeksIterator {} /// /// ``` /// # use chrono::NaiveDate; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Debug for NaiveDate { @@ -2152,8 +2187,8 @@ impl fmt::Debug for NaiveDate { /// ``` /// use chrono::NaiveDate; /// -/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( 0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); /// ``` /// @@ -2161,7 +2196,7 @@ impl fmt::Debug for NaiveDate { /// /// ``` /// # use chrono::NaiveDate; -/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt( -1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); /// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); /// ``` impl fmt::Display for NaiveDate { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index d767483571..3c323cb352 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -58,7 +58,8 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ``` /// use chrono::{NaiveDate, NaiveDateTime}; /// -/// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// let dt: NaiveDateTime = +/// NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); /// # let _ = dt; /// ``` /// @@ -95,7 +96,7 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; /// /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); @@ -284,14 +285,21 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDateTime, NaiveDate}; + /// use chrono::{NaiveDate, NaiveDateTime}; /// /// let parse_from_str = NaiveDateTime::parse_from_str; /// - /// assert_eq!(parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), - /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap())); - /// assert_eq!(parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), - /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_micro_opt(13, 23, 45, 678_900).unwrap())); + /// assert_eq!( + /// parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_micro_opt(13, 23, 45, 678_900) + /// .unwrap()) + /// ); /// ``` /// /// Offset is ignored for the purpose of parsing. @@ -299,8 +307,10 @@ impl NaiveDateTime { /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// ``` /// /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by @@ -310,8 +320,13 @@ impl NaiveDateTime { /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), - /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_milli_opt(8, 59, 59, 1_123).unwrap())); + /// assert_eq!( + /// parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_milli_opt(8, 59, 59, 1_123) + /// .unwrap()) + /// ); /// ``` /// /// Missing seconds are assumed to be zero, @@ -320,8 +335,10 @@ impl NaiveDateTime { /// ``` /// # use chrono::{NaiveDateTime, NaiveDate}; /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), - /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap())); + /// assert_eq!( + /// parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), + /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap()) + /// ); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); @@ -347,7 +364,7 @@ impl NaiveDateTime { /// let fmt = "%Y-%m-%d %H:%M:%S"; /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); - ///``` + /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; @@ -366,7 +383,10 @@ impl NaiveDateTime { /// ```rust /// # use chrono::{NaiveDate, NaiveDateTime}; /// let (datetime, remainder) = NaiveDateTime::parse_and_remainder( - /// "2015-02-18 23:16:09 trailing text", "%Y-%m-%d %H:%M:%S").unwrap(); + /// "2015-02-18 23:16:09 trailing text", + /// "%Y-%m-%d %H:%M:%S", + /// ) + /// .unwrap(); /// assert_eq!( /// datetime, /// NaiveDate::from_ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() @@ -453,10 +473,12 @@ impl NaiveDateTime { /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_444); /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); + /// let dt = + /// NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); /// - /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_milli_opt(23, 59, 59, 100).unwrap(); + /// let dt = + /// NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_milli_opt(23, 59, 59, 100).unwrap(); /// assert_eq!(dt.timestamp_millis(), -900); /// ``` #[inline] @@ -479,7 +501,8 @@ impl NaiveDateTime { /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_444); /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); + /// let dt = + /// NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); /// ``` #[inline] @@ -577,10 +600,16 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) + /// .unwrap() + /// .and_hms_nano_opt(9, 10, 11, 123_456_789) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_millis(), 123); /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); /// ``` #[inline] @@ -599,10 +628,16 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) + /// .unwrap() + /// .and_hms_nano_opt(9, 10, 11, 123_456_789) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_micros(), 123_456); /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); /// ``` #[inline] @@ -621,10 +656,16 @@ impl NaiveDateTime { /// ``` /// use chrono::NaiveDate; /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 123_456_789).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) + /// .unwrap() + /// .and_hms_nano_opt(9, 10, 11, 123_456_789) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789); /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_nano_opt(8, 59, 59, 1_234_567_890).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) + /// .unwrap(); /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); /// ``` #[inline] @@ -647,26 +688,26 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60)), - /// Some(hms(4, 6, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400)), - /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap())); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(1)), Some(hms(3, 5, 8))); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1)), Some(hms(3, 5, 6))); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60)), Some(hms(4, 6, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400)), + /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()) + /// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); - /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::milliseconds(450)), - /// Some(hmsm(3, 5, 8, 430))); + /// assert_eq!( + /// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::milliseconds(450)), + /// Some(hmsm(3, 5, 8, 430)) + /// ); /// ``` /// /// Overflow returns `None`. @@ -727,13 +768,19 @@ impl NaiveDateTime { /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() /// .checked_add_months(Months::new(1)), /// Some(NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -821,26 +868,26 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60)), - /// Some(hms(2, 4, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400)), - /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap())); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(1)), Some(hms(3, 5, 6))); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1)), Some(hms(3, 5, 8))); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60)), Some(hms(2, 4, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400)), + /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()) + /// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); - /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::milliseconds(670)), - /// Some(hmsm(3, 5, 6, 780))); + /// assert_eq!( + /// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::milliseconds(670)), + /// Some(hmsm(3, 5, 6, 780)) + /// ); /// ``` /// /// Overflow returns `None`. @@ -897,13 +944,19 @@ impl NaiveDateTime { /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() /// .checked_sub_months(Months::new(1)), /// Some(NaiveDate::from_ymd_opt(2013, 12, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) /// ); /// /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), /// None /// ); @@ -941,18 +994,24 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveDate}; + /// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); - /// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), - /// TimeDelta::seconds(3600 + 60 + 1)); + /// assert_eq!( + /// d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), + /// TimeDelta::seconds(3600 + 60 + 1) + /// ); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); - /// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap().signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), - /// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500)); + /// assert_eq!( + /// d.and_hms_milli_opt(0, 7, 6, 500) + /// .unwrap() + /// .signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), + /// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500) + /// ); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that @@ -962,10 +1021,14 @@ impl NaiveDateTime { /// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); - /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), - /// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500)); - /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), - /// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500)); + /// assert_eq!( + /// leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), + /// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500) + /// ); + /// assert_eq!( + /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), + /// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500) + /// ); /// ``` #[must_use] pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { @@ -986,13 +1049,13 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::NaiveDate; /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; /// /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -1068,10 +1131,15 @@ impl NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, FixedOffset}; + /// use chrono::{FixedOffset, NaiveDate}; /// let hour = 3600; /// let tz = FixedOffset::east_opt(5 * hour).unwrap(); - /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap().and_local_timezone(tz).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_opt(23, 56, 4) + /// .unwrap() + /// .and_local_timezone(tz) + /// .unwrap(); /// assert_eq!(dt.timezone(), tz); /// ``` #[must_use] @@ -1085,7 +1153,8 @@ impl NaiveDateTime { /// /// ``` /// use chrono::{NaiveDate, Utc}; - /// let dt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); + /// let dt = + /// NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); /// assert_eq!(dt.timezone(), Utc); /// ``` #[must_use] @@ -1128,9 +1197,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.year(), 2015); /// ``` #[inline] @@ -1147,9 +1217,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.month(), 9); /// ``` #[inline] @@ -1166,9 +1237,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.month0(), 8); /// ``` #[inline] @@ -1185,9 +1257,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.day(), 25); /// ``` #[inline] @@ -1204,9 +1277,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.day0(), 24); /// ``` #[inline] @@ -1223,9 +1297,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.ordinal(), 268); /// ``` #[inline] @@ -1242,9 +1317,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.ordinal0(), 267); /// ``` #[inline] @@ -1259,9 +1335,10 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Weekday}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); /// assert_eq!(dt.weekday(), Weekday::Fri); /// ``` #[inline] @@ -1287,11 +1364,18 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_year(2016), Some(NaiveDate::from_ymd_opt(2016, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap())); - /// assert_eq!(dt.with_year(-308), Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// ``` #[inline] fn with_year(&self, year: i32) -> Option { @@ -1309,10 +1393,14 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_month(10), Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_month(13), None); // no month 13 /// assert_eq!(dt.with_month(2), None); // no February 30 /// ``` @@ -1333,10 +1421,14 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_month0(9), Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_month0(12), None); // no month 13 /// assert_eq!(dt.with_month0(1), None); // no February 30 /// ``` @@ -1356,10 +1448,14 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_day(30), Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_day(31), None); // no September 31 /// ``` #[inline] @@ -1378,10 +1474,14 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_day0(29), Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_day0(30), None); // no September 31 /// ``` #[inline] @@ -1401,18 +1501,26 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap())); - /// assert_eq!(dt.with_ordinal(366), - /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { @@ -1431,18 +1539,26 @@ impl Datelike for NaiveDateTime { /// # Example /// /// ``` - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap())); - /// assert_eq!(dt.with_ordinal0(365), - /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); /// ``` #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option { @@ -1460,7 +1576,8 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.hour(), 12); /// ``` #[inline] @@ -1477,7 +1594,8 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.minute(), 34); /// ``` #[inline] @@ -1494,7 +1612,8 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.second(), 56); /// ``` #[inline] @@ -1513,7 +1632,8 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); /// assert_eq!(dt.nanosecond(), 789_000_000); /// ``` #[inline] @@ -1534,9 +1654,14 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); - /// assert_eq!(dt.with_hour(7), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_hour(7), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap() + /// ) + /// ); /// assert_eq!(dt.with_hour(24), None); /// ``` #[inline] @@ -1557,9 +1682,17 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); - /// assert_eq!(dt.with_minute(45), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 45, 56, 789).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_minute(45), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 45, 56, 789) + /// .unwrap() + /// ) + /// ); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] @@ -1583,9 +1716,17 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); - /// assert_eq!(dt.with_second(17), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 17, 789).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_second(17), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 34, 17, 789) + /// .unwrap() + /// ) + /// ); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] @@ -1610,11 +1751,26 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 333_333_333).unwrap())); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 1_333_333_333).unwrap())); + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 333_333_333) + /// .unwrap() + /// ) + /// ); + /// assert_eq!( + /// dt.with_nanosecond(1_333_333_333), // leap second + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 1_333_333_333) + /// .unwrap() + /// ) + /// ); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` #[inline] @@ -1637,20 +1793,24 @@ impl Timelike for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(-1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(-1), hms(3, 5, 6)); /// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(3600 + 60), hms(4, 6, 7)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(86_400), -/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::days(365), -/// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap()); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::seconds(86_400), +/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::days(365), +/// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap() +/// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::milliseconds(450), hmsm(3, 5, 8, 430)); @@ -1774,23 +1934,28 @@ impl Add for NaiveDateTime { /// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// + Months::new(11), /// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// + Months::new(12), /// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// + Months::new(13), /// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// + Months::new(1), /// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// + Months::new(1), /// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() /// ); /// ``` @@ -1818,20 +1983,24 @@ impl Add for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(-1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(-1), hms(3, 5, 8)); /// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(3600 + 60), hms(2, 4, 7)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(86_400), -/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::days(365), -/// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap()); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::seconds(86_400), +/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::days(365), +/// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::milliseconds(670), hmsm(3, 5, 6, 780)); @@ -1951,15 +2120,18 @@ impl Sub for NaiveDateTime { /// use chrono::{Months, NaiveDate}; /// /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() - Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() +/// - Months::new(11), /// NaiveDate::from_ymd_opt(2013, 02, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() - Months::new(12), +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() +/// - Months::new(12), /// NaiveDate::from_ymd_opt(2013, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() /// ); /// assert_eq!( -/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() - Months::new(13), +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() +/// - Months::new(13), /// NaiveDate::from_ymd_opt(2012, 12, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() /// ); /// ``` @@ -1985,17 +2157,22 @@ impl Sub for NaiveDateTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveDate}; +/// use chrono::{NaiveDate, TimeDelta}; /// /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// let d = from_ymd(2016, 7, 8); -/// assert_eq!(d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), TimeDelta::seconds(3600 + 60 + 1)); +/// assert_eq!( +/// d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), +/// TimeDelta::seconds(3600 + 60 + 1) +/// ); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); -/// assert_eq!(d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), -/// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500)); +/// assert_eq!( +/// d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), +/// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500) +/// ); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that no other leap @@ -2005,10 +2182,14 @@ impl Sub for NaiveDateTime { /// # use chrono::{TimeDelta, NaiveDate}; /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); -/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), -/// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500)); -/// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, -/// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500)); +/// assert_eq!( +/// leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), +/// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500) +/// ); +/// assert_eq!( +/// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, +/// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500) +/// ); /// ``` impl Sub for NaiveDateTime { type Output = TimeDelta; @@ -2071,7 +2252,8 @@ impl Sub for NaiveDateTime { /// /// ``` /// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); /// ``` impl fmt::Debug for NaiveDateTime { @@ -2104,7 +2286,8 @@ impl fmt::Debug for NaiveDateTime { /// /// ``` /// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); /// ``` impl fmt::Display for NaiveDateTime { diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 0a6cfd915d..16088e368c 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -64,13 +64,14 @@ impl<'de> de::Deserialize<'de> for NaiveDateTime { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds")] -/// time: NaiveDateTime +/// time: NaiveDateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -106,11 +107,14 @@ pub mod ts_nanoseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_nano_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -138,7 +142,7 @@ pub mod ts_nanoseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; @@ -201,13 +205,16 @@ pub mod ts_nanoseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_nanoseconds_option")] -/// time: Option +/// time: Option, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -243,11 +250,16 @@ pub mod ts_nanoseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_nano_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_nano_opt(02, 04, 59, 918355733).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -278,7 +290,7 @@ pub mod ts_nanoseconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_nano_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; @@ -341,13 +353,14 @@ pub mod ts_nanoseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds")] -/// time: NaiveDateTime +/// time: NaiveDateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -375,11 +388,14 @@ pub mod ts_microseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_micro_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -405,7 +421,7 @@ pub mod ts_microseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; @@ -465,13 +481,16 @@ pub mod ts_microseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_microseconds_option")] -/// time: Option +/// time: Option, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -499,11 +518,16 @@ pub mod ts_microseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_micro_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -532,7 +556,7 @@ pub mod ts_microseconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_micro_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; @@ -595,13 +619,12 @@ pub mod ts_microseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds")] -/// time: NaiveDateTime +/// time: NaiveDateTime, /// } /// -/// let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -629,11 +652,14 @@ pub mod ts_milliseconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_milli_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -659,7 +685,7 @@ pub mod ts_milliseconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; @@ -719,13 +745,13 @@ pub mod ts_milliseconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_milliseconds_option")] -/// time: Option +/// time: Option, /// } /// -/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -753,11 +779,16 @@ pub mod ts_milliseconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_milli_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap()), + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), + /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -786,7 +817,7 @@ pub mod ts_milliseconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_milli_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; @@ -849,13 +880,11 @@ pub mod ts_milliseconds_option { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds")] -/// time: NaiveDateTime +/// time: NaiveDateTime, /// } /// /// let time = NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); @@ -883,12 +912,11 @@ pub mod ts_seconds { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// - /// let my_s = S { - /// time: NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(), - /// }; + /// let my_s = + /// S { time: NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap() }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) @@ -913,7 +941,7 @@ pub mod ts_seconds { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_ts")] - /// time: NaiveDateTime + /// time: NaiveDateTime, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; @@ -967,13 +995,11 @@ pub mod ts_seconds { /// #[derive(Deserialize, Serialize)] /// struct S { /// #[serde(with = "ts_seconds_option")] -/// time: Option +/// time: Option, /// } /// /// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()); -/// let my_s = S { -/// time: time.clone(), -/// }; +/// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); @@ -1001,7 +1027,7 @@ pub mod ts_seconds_option { /// #[derive(Serialize)] /// struct S { /// #[serde(serialize_with = "to_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s = S { @@ -1034,7 +1060,7 @@ pub mod ts_seconds_option { /// #[derive(Debug, PartialEq, Deserialize)] /// struct S { /// #[serde(deserialize_with = "from_tsopt")] - /// time: Option + /// time: Option, /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 7776b928b7..93d0dc423f 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -62,7 +62,7 @@ impl IsoWeek { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().year(), 2015); @@ -89,7 +89,7 @@ impl IsoWeek { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week(), 15); @@ -106,7 +106,7 @@ impl IsoWeek { /// # Example /// /// ``` - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// use chrono::{Datelike, NaiveDate, Weekday}; /// /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week0(), 14); @@ -124,19 +124,28 @@ impl IsoWeek { /// # Example /// /// ``` -/// use chrono::{NaiveDate, Datelike}; +/// use chrono::{Datelike, NaiveDate}; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), "2015-W36"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 3).unwrap().iso_week()), "0000-W01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), "9999-W52"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), +/// "2015-W36" +/// ); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), +/// "9999-W52" +/// ); /// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// /// ``` /// # use chrono::{NaiveDate, Datelike}; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt( 0, 1, 2).unwrap().iso_week()), "-0001-W52"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), "+10000-W52"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), +/// "+10000-W52" +/// ); /// ``` impl fmt::Debug for IsoWeek { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 0bdb76522c..57c4f5ca03 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -80,9 +80,17 @@ mod tests; /// /// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); /// -/// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_micro_opt(8, 59, 59, 1_000_000).unwrap(); -/// -/// let dt2 = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_nano_opt(23, 59, 59, 1_000_000_000).unwrap().and_local_timezone(Utc).unwrap(); +/// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1) +/// .unwrap() +/// .and_hms_micro_opt(8, 59, 59, 1_000_000) +/// .unwrap(); +/// +/// let dt2 = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_nano_opt(23, 59, 59, 1_000_000_000) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(); /// # let _ = (t, dt1, dt2); /// ``` /// @@ -164,9 +172,14 @@ mod tests; /// will be represented as the second part being 60, as required by ISO 8601. /// /// ``` -/// use chrono::{Utc, NaiveDate}; -/// -/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap().and_local_timezone(Utc).unwrap(); +/// use chrono::{NaiveDate, Utc}; +/// +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_milli_opt(23, 59, 59, 1_000) +/// .unwrap() +/// .and_local_timezone(Utc) +/// .unwrap(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// @@ -485,10 +498,14 @@ impl NaiveTime { /// /// let parse_from_str = NaiveTime::parse_from_str; /// - /// assert_eq!(parse_from_str("23:56:04", "%H:%M:%S"), - /// Ok(NaiveTime::from_hms_opt(23, 56, 4).unwrap())); - /// assert_eq!(parse_from_str("pm012345.6789", "%p%I%M%S%.f"), - /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap())); + /// assert_eq!( + /// parse_from_str("23:56:04", "%H:%M:%S"), + /// Ok(NaiveTime::from_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("pm012345.6789", "%p%I%M%S%.f"), + /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap()) + /// ); /// ``` /// /// Date and offset is ignored for the purpose of parsing. @@ -496,8 +513,10 @@ impl NaiveTime { /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap())); + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap()) + /// ); /// ``` /// /// [Leap seconds](#leap-second-handling) are correctly handled by @@ -507,8 +526,10 @@ impl NaiveTime { /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("08:59:60.123", "%H:%M:%S%.f"), - /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap())); + /// assert_eq!( + /// parse_from_str("08:59:60.123", "%H:%M:%S%.f"), + /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap()) + /// ); /// ``` /// /// Missing seconds are assumed to be zero, @@ -517,8 +538,7 @@ impl NaiveTime { /// ``` /// # use chrono::NaiveTime; /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("7:15", "%H:%M"), - /// Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); + /// assert_eq!(parse_from_str("7:15", "%H:%M"), Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); /// /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); /// assert!(parse_from_str("12", "%H").is_err()); @@ -552,8 +572,8 @@ impl NaiveTime { /// /// ```rust /// # use chrono::{NaiveTime}; - /// let (time, remainder) = NaiveTime::parse_and_remainder( - /// "3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); + /// let (time, remainder) = + /// NaiveTime::parse_and_remainder("3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); /// assert_eq!(time, NaiveTime::from_hms_opt(3, 4, 33).unwrap()); /// assert_eq!(remainder, " trailing text"); /// ``` @@ -569,16 +589,22 @@ impl NaiveTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveTime}; - /// - /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(11)), - /// (from_hms(14, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(23)), - /// (from_hms(2, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)), - /// (from_hms(20, 4, 5), -86_400)); + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(11)), + /// (from_hms(14, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(23)), + /// (from_hms(2, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)), + /// (from_hms(20, 4, 5), -86_400) + /// ); /// ``` #[must_use] pub const fn overflowing_add_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { @@ -625,16 +651,22 @@ impl NaiveTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveTime}; - /// - /// let from_hms = |h, m, s| { NaiveTime::from_hms_opt(h, m, s).unwrap() }; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(2)), - /// (from_hms(1, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(17)), - /// (from_hms(10, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(-22)), - /// (from_hms(1, 4, 5), -86_400)); + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(2)), + /// (from_hms(1, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(17)), + /// (from_hms(10, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(-22)), + /// (from_hms(1, 4, 5), -86_400) + /// ); /// ``` #[inline] #[must_use] @@ -656,27 +688,31 @@ impl NaiveTime { /// # Example /// /// ``` - /// use chrono::{TimeDelta, NaiveTime}; + /// use chrono::{NaiveTime, TimeDelta}; /// - /// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; + /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); /// let since = NaiveTime::signed_duration_since; /// - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), - /// TimeDelta::zero()); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), - /// TimeDelta::milliseconds(25)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), - /// TimeDelta::milliseconds(975)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), - /// TimeDelta::seconds(7)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), - /// TimeDelta::seconds(5 * 60)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), - /// TimeDelta::seconds(3 * 3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), - /// TimeDelta::seconds(-3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), + /// TimeDelta::milliseconds(25) + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), + /// TimeDelta::milliseconds(975) + /// ); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), TimeDelta::seconds(7)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), TimeDelta::seconds(5 * 60)); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), + /// TimeDelta::seconds(3 * 3600) + /// ); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), TimeDelta::seconds(-3600)); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), + /// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100) + /// ); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that @@ -760,13 +796,13 @@ impl NaiveTime { /// # Example /// /// ``` - /// use chrono::NaiveTime; /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveTime; /// /// let fmt = StrftimeItems::new("%H:%M:%S"); /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -927,7 +963,10 @@ impl Timelike for NaiveTime { /// use chrono::{NaiveTime, Timelike}; /// /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().nanosecond(), 0); - /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().nanosecond(), 12_345_678); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().nanosecond(), + /// 12_345_678 + /// ); /// ``` /// /// Leap seconds may have seemingly out-of-range return values. @@ -982,7 +1021,10 @@ impl Timelike for NaiveTime { /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap())); + /// assert_eq!( + /// dt.with_minute(45), + /// Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap()) + /// ); /// assert_eq!(dt.with_minute(60), None); /// ``` #[inline] @@ -1009,7 +1051,10 @@ impl Timelike for NaiveTime { /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap())); + /// assert_eq!( + /// dt.with_second(17), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap()) + /// ); /// assert_eq!(dt.with_second(60), None); /// ``` #[inline] @@ -1036,8 +1081,10 @@ impl Timelike for NaiveTime { /// use chrono::{NaiveTime, Timelike}; /// /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap())); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap()) + /// ); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` /// @@ -1067,12 +1114,15 @@ impl Timelike for NaiveTime { /// ``` /// use chrono::{NaiveTime, Timelike}; /// - /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), - /// 3723); - /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), - /// 86164); - /// assert_eq!(NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), - /// 86399); + /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), 3723); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), + /// 86164 + /// ); + /// assert_eq!( + /// NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), + /// 86399 + /// ); /// ``` #[inline] fn num_seconds_from_midnight(&self) -> u32 { @@ -1092,18 +1142,21 @@ impl Timelike for NaiveTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveTime}; -/// -/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(1), from_hmsm(3, 5, 8, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(280), from_hmsm(3, 5, 8, 230)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(-980), from_hmsm(3, 5, 6, 970)); +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(1), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-1), from_hmsm(3, 5, 6, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(7 * 60 * 60 - 6 * 60), +/// from_hmsm(9, 59, 7, 0) +/// ); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(280), from_hmsm(3, 5, 8, 230)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(-980), from_hmsm(3, 5, 6, 970)); /// ``` /// /// The addition wraps around. @@ -1207,16 +1260,19 @@ impl Add for NaiveTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveTime}; -/// -/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) - TimeDelta::milliseconds(280), from_hmsm(3, 5, 7, 670)); +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(1), from_hmsm(3, 5, 6, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(2 * 60 * 60 + 6 * 60), +/// from_hmsm(0, 59, 7, 0) +/// ); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) - TimeDelta::milliseconds(280), from_hmsm(3, 5, 7, 670)); /// ``` /// /// The subtraction wraps around. @@ -1320,9 +1376,9 @@ impl Sub for NaiveTime { /// # Example /// /// ``` -/// use chrono::{TimeDelta, NaiveTime}; +/// use chrono::{NaiveTime, TimeDelta}; /// -/// let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); /// /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), TimeDelta::milliseconds(25)); @@ -1331,8 +1387,10 @@ impl Sub for NaiveTime { /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), TimeDelta::seconds(5 * 60)); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), TimeDelta::seconds(3 * 3600)); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), TimeDelta::seconds(-3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), +/// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100) +/// ); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that @@ -1374,17 +1432,29 @@ impl Sub for NaiveTime { /// ``` /// use chrono::NaiveTime; /// -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), "23:56:04.012"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), "23:56:04.001234"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), "23:56:04.000123456"); +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); /// ``` /// /// Leap seconds may also be used. /// /// ``` /// # use chrono::NaiveTime; -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), "06:59:60.500"); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); /// ``` impl fmt::Debug for NaiveTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1430,17 +1500,29 @@ impl fmt::Debug for NaiveTime { /// ``` /// use chrono::NaiveTime; /// -/// assert_eq!(format!("{}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), "23:56:04.012"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), "23:56:04.001234"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), "23:56:04.000123456"); +/// assert_eq!(format!("{}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); /// ``` /// /// Leap seconds may also be used. /// /// ``` /// # use chrono::NaiveTime; -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), "06:59:60.500"); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); /// ``` impl fmt::Display for NaiveTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 8f37558988..745dad29a1 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -53,10 +53,8 @@ impl FixedOffset { #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::east_opt(5 * hour) - /// .unwrap() - /// .with_ymd_and_hms(2016, 11, 08, 0, 0, 0) - /// .unwrap(); + /// let datetime = + /// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") /// ``` #[must_use] @@ -89,10 +87,8 @@ impl FixedOffset { #[cfg_attr(feature = "std", doc = "```")] /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; - /// let datetime = FixedOffset::west_opt(5 * hour) - /// .unwrap() - /// .with_ymd_and_hms(2016, 11, 08, 0, 0, 0) - /// .unwrap(); + /// let datetime = + /// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") /// ``` #[must_use] diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 37e78898c9..b379b39e46 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -100,7 +100,7 @@ mod tz_info; /// # Example /// /// ``` -/// use chrono::{Local, DateTime, TimeZone}; +/// use chrono::{DateTime, Local, TimeZone}; /// /// let dt1: DateTime = Local::now(); /// let dt2: DateTime = Local.timestamp_opt(0, 0).unwrap(); diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 11928b42dd..bca9543f0e 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -375,7 +375,7 @@ pub trait TimeZone: Sized + Clone { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone}; + /// use chrono::{TimeZone, Utc}; /// /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); /// ``` @@ -407,7 +407,7 @@ pub trait TimeZone: Sized + Clone { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone, LocalResult}; + /// use chrono::{LocalResult, TimeZone, Utc}; /// match Utc.timestamp_millis_opt(1431648000) { /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), @@ -428,7 +428,7 @@ pub trait TimeZone: Sized + Clone { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone}; + /// use chrono::{TimeZone, Utc}; /// /// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648); /// ``` @@ -447,7 +447,7 @@ pub trait TimeZone: Sized + Clone { /// # Example /// /// ``` - /// use chrono::{Utc, TimeZone}; + /// use chrono::{TimeZone, Utc}; /// /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); /// ``` diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 29b832a1fa..be88e37eaf 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -33,7 +33,7 @@ use crate::{Date, DateTime}; /// # Example /// /// ``` -/// use chrono::{TimeZone, NaiveDateTime, Utc}; +/// use chrono::{NaiveDateTime, TimeZone, Utc}; /// /// let dt = Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_opt(61, 0).unwrap()); /// diff --git a/src/round.rs b/src/round.rs index 315d45386d..c70eea6a00 100644 --- a/src/round.rs +++ b/src/round.rs @@ -23,7 +23,12 @@ pub trait SubsecRound { /// # Example /// ``` rust /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -35,7 +40,12 @@ pub trait SubsecRound { /// # Example /// ``` rust /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -110,7 +120,12 @@ pub trait DurationRound: Sized { /// # Example /// ``` rust /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!( /// dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -127,7 +142,12 @@ pub trait DurationRound: Sized { /// # Example /// ``` rust /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// assert_eq!( /// dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -238,7 +258,12 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DurationRound, TimeDelta, RoundingError, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31) + /// .unwrap() + /// .and_hms_nano_opt(23, 59, 59, 1_75_500_000) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); /// /// assert_eq!( /// dt.duration_round(TimeDelta::days(300 * 365)), diff --git a/src/traits.rs b/src/traits.rs index 0018a7d9bd..ae6cde494f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -101,7 +101,7 @@ pub trait Datelike: Sized { /// # Examples /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(), @@ -134,7 +134,7 @@ pub trait Datelike: Sized { /// # Examples /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!( /// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(), @@ -146,7 +146,7 @@ pub trait Datelike: Sized { /// /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { /// date.with_year(year)?.with_month(month) @@ -238,7 +238,7 @@ pub trait Datelike: Sized { /// # Examples /// /// ``` - /// use chrono::{NaiveDate, Datelike}; + /// use chrono::{Datelike, NaiveDate}; /// /// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163); /// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366); From 10bc27575b79eeffac351f40206fbc645424e170 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 14:08:51 +0100 Subject: [PATCH 743/999] Format inline comments --- src/naive/date/mod.rs | 9 +++++---- src/offset/mod.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index e3e2dd9f82..f26dba9e6e 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1433,16 +1433,17 @@ impl NaiveDate { } /// The minimum possible `NaiveDate` (January 1, 262144 BCE). - pub const MIN: NaiveDate = NaiveDate::from_yof((MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/); + pub const MIN: NaiveDate = NaiveDate::from_yof((MIN_YEAR << 13) | (1 << 4) | 0o12 /* D */); /// The maximum possible `NaiveDate` (December 31, 262142 CE). - pub const MAX: NaiveDate = NaiveDate::from_yof((MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/); + pub const MAX: NaiveDate = + NaiveDate::from_yof((MAX_YEAR << 13) | (365 << 4) | 0o16 /* G */); /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). pub(crate) const BEFORE_MIN: NaiveDate = - NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/); + NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /* FE */); /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). pub(crate) const AFTER_MAX: NaiveDate = - NaiveDate::from_yof(((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/); + NaiveDate::from_yof(((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /* F */); } impl Datelike for NaiveDate { diff --git a/src/offset/mod.rs b/src/offset/mod.rs index bca9543f0e..26566ad020 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -47,7 +47,7 @@ pub enum LocalResult { Single(T), /// Given local time representation has multiple results and thus ambiguous. /// This can occur when, for example, the negative timezone transition. - Ambiguous(T /*min*/, T /*max*/), + Ambiguous(T /* min */, T /* max */), } impl LocalResult { From 188cf508b2b8b04c9f841da86b9ce512c4277cc7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 14:09:59 +0100 Subject: [PATCH 744/999] Remove trailing newlines in method documentation --- src/offset/local/tz_info/rule.rs | 1 - src/offset/local/tz_info/timezone.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 369e317a42..0ff4fe7bde 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -20,7 +20,6 @@ impl TransitionRule { /// Parse a POSIX TZ string containing a time zone description, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). /// /// TZ string extensions from [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536#section-3.3.1) may be used. - /// pub(super) fn from_tz_string( tz_string: &[u8], use_string_extensions: bool, diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 02b6f341e2..0965a8a3e3 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -25,7 +25,6 @@ impl TimeZone { /// Returns local time zone. /// /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. - /// pub(crate) fn local(env_tz: Option<&str>) -> Result { match env_tz { Some(tz) => Self::from_posix_tz(tz), From 2464a5d6e66c7ecf3b887ff984d604c753b383e3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 07:57:02 +0100 Subject: [PATCH 745/999] Add newlines between tests in `time_delta` module --- src/time_delta.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/time_delta.rs b/src/time_delta.rs index e5e22b69ef..e87bfa404e 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -670,6 +670,7 @@ mod tests { assert_eq!(TimeDelta::milliseconds(-999).num_seconds(), 0); assert_eq!(TimeDelta::milliseconds(-1001).num_seconds(), -1); } + #[test] fn test_duration_seconds_max_allowed() { let duration = TimeDelta::seconds(i64::MAX / 1_000); @@ -679,15 +680,18 @@ mod tests { i64::MAX as i128 / 1_000 * 1_000_000_000 ); } + #[test] fn test_duration_seconds_max_overflow() { assert!(TimeDelta::try_seconds(i64::MAX / 1_000 + 1).is_none()); } + #[test] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_max_overflow_panic() { let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); } + #[test] fn test_duration_seconds_min_allowed() { let duration = TimeDelta::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding @@ -697,10 +701,12 @@ mod tests { -i64::MAX as i128 / 1_000 * 1_000_000_000 ); } + #[test] fn test_duration_seconds_min_underflow() { assert!(TimeDelta::try_seconds(-i64::MAX / 1_000 - 1).is_none()); } + #[test] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_min_underflow_panic() { @@ -717,6 +723,7 @@ mod tests { assert_eq!(TimeDelta::microseconds(-999).num_milliseconds(), 0); assert_eq!(TimeDelta::microseconds(-1001).num_milliseconds(), -1); } + #[test] fn test_duration_milliseconds_max_allowed() { // The maximum number of milliseconds acceptable through the constructor is @@ -728,6 +735,7 @@ mod tests { i64::MAX as i128 * 1_000_000 ); } + #[test] fn test_duration_milliseconds_max_overflow() { // Here we ensure that trying to add one millisecond to the maximum storable @@ -736,6 +744,7 @@ mod tests { .checked_add(&TimeDelta::milliseconds(1)) .is_none()); } + #[test] fn test_duration_milliseconds_min_allowed() { // The minimum number of milliseconds acceptable through the constructor is @@ -748,6 +757,7 @@ mod tests { -i64::MAX as i128 * 1_000_000 ); } + #[test] fn test_duration_milliseconds_min_underflow() { // Here we ensure that trying to subtract one millisecond from the minimum @@ -756,6 +766,7 @@ mod tests { .checked_sub(&TimeDelta::milliseconds(1)) .is_none()); } + #[test] #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] fn test_duration_milliseconds_min_underflow_panic() { @@ -906,6 +917,7 @@ mod tests { i64::MAX as i128 * 1_000_000 ); } + #[test] fn test_duration_nanoseconds_max_overflow() { // This test establishes that a TimeDelta can store more nanoseconds than are @@ -922,6 +934,7 @@ mod tests { .checked_add(&TimeDelta::nanoseconds(1)) .is_none()); } + #[test] fn test_duration_nanoseconds_min_allowed() { // The number of nanoseconds acceptable through the constructor is far fewer @@ -943,6 +956,7 @@ mod tests { -i64::MAX as i128 * 1_000_000 ); } + #[test] fn test_duration_nanoseconds_min_underflow() { // This test establishes that a TimeDelta can store more nanoseconds than are @@ -971,6 +985,7 @@ mod tests { assert_eq!(MAX.num_microseconds(), None); assert_eq!(MAX.num_nanoseconds(), None); } + #[test] fn test_min() { assert_eq!( From ef4dc2b06bd6d8a5d97ff1df0c94f0484b638ad0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 15:21:08 +0100 Subject: [PATCH 746/999] Rename `ymd_opt` to `from_ymd` (in tests) --- src/naive/date/tests.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 8bc460b779..5a9dc6b3c2 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -151,18 +151,18 @@ fn test_readme_doomsday() { #[test] fn test_date_from_ymd() { - let ymd_opt = NaiveDate::from_ymd_opt; - - assert!(ymd_opt(2012, 0, 1).is_none()); - assert!(ymd_opt(2012, 1, 1).is_some()); - assert!(ymd_opt(2012, 2, 29).is_some()); - assert!(ymd_opt(2014, 2, 29).is_none()); - assert!(ymd_opt(2014, 3, 0).is_none()); - assert!(ymd_opt(2014, 3, 1).is_some()); - assert!(ymd_opt(2014, 3, 31).is_some()); - assert!(ymd_opt(2014, 3, 32).is_none()); - assert!(ymd_opt(2014, 12, 31).is_some()); - assert!(ymd_opt(2014, 13, 1).is_none()); + let from_ymd = NaiveDate::from_ymd_opt; + + assert!(from_ymd(2012, 0, 1).is_none()); + assert!(from_ymd(2012, 1, 1).is_some()); + assert!(from_ymd(2012, 2, 29).is_some()); + assert!(from_ymd(2014, 2, 29).is_none()); + assert!(from_ymd(2014, 3, 0).is_none()); + assert!(from_ymd(2014, 3, 1).is_some()); + assert!(from_ymd(2014, 3, 31).is_some()); + assert!(from_ymd(2014, 3, 32).is_none()); + assert!(from_ymd(2014, 12, 31).is_some()); + assert!(from_ymd(2014, 13, 1).is_none()); } #[test] From 5d8d4409499191224d6219853f3051f3c010c00c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 15:27:46 +0100 Subject: [PATCH 747/999] Rename `yo_opt` to `from_yo` (in tests) --- src/naive/date/tests.rs | 50 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 5a9dc6b3c2..a3d2dc8df2 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -167,33 +167,33 @@ fn test_date_from_ymd() { #[test] fn test_date_from_yo() { - let yo_opt = NaiveDate::from_yo_opt; + let from_yo = NaiveDate::from_yo_opt; let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - assert_eq!(yo_opt(2012, 0), None); - assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); - assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); - assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); - assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); - assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); - assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); - assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); - assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); - assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); - assert_eq!(yo_opt(2012, 367), None); - assert_eq!(yo_opt(2012, 1 << 28 | 60), None); - - assert_eq!(yo_opt(2014, 0), None); - assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); - assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); - assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); - assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); - assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); - assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); - assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); - assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); - assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); - assert_eq!(yo_opt(2014, 366), None); + assert_eq!(from_yo(2012, 0), None); + assert_eq!(from_yo(2012, 1), Some(ymd(2012, 1, 1))); + assert_eq!(from_yo(2012, 2), Some(ymd(2012, 1, 2))); + assert_eq!(from_yo(2012, 32), Some(ymd(2012, 2, 1))); + assert_eq!(from_yo(2012, 60), Some(ymd(2012, 2, 29))); + assert_eq!(from_yo(2012, 61), Some(ymd(2012, 3, 1))); + assert_eq!(from_yo(2012, 100), Some(ymd(2012, 4, 9))); + assert_eq!(from_yo(2012, 200), Some(ymd(2012, 7, 18))); + assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26))); + assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31))); + assert_eq!(from_yo(2012, 367), None); + assert_eq!(from_yo(2012, 1 << 28 | 60), None); + + assert_eq!(from_yo(2014, 0), None); + assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1))); + assert_eq!(from_yo(2014, 2), Some(ymd(2014, 1, 2))); + assert_eq!(from_yo(2014, 32), Some(ymd(2014, 2, 1))); + assert_eq!(from_yo(2014, 59), Some(ymd(2014, 2, 28))); + assert_eq!(from_yo(2014, 60), Some(ymd(2014, 3, 1))); + assert_eq!(from_yo(2014, 100), Some(ymd(2014, 4, 10))); + assert_eq!(from_yo(2014, 200), Some(ymd(2014, 7, 19))); + assert_eq!(from_yo(2014, 300), Some(ymd(2014, 10, 27))); + assert_eq!(from_yo(2014, 365), Some(ymd(2014, 12, 31))); + assert_eq!(from_yo(2014, 366), None); } #[test] From 02c68d69a1ff8e6461384a770b87737b5096eae3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 13 Feb 2024 15:29:01 +0100 Subject: [PATCH 748/999] Rename `isoywd_opt` to `from_isoywd` (in tests) --- src/naive/date/tests.rs | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index a3d2dc8df2..361a88ef59 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -198,31 +198,31 @@ fn test_date_from_yo() { #[test] fn test_date_from_isoywd() { - let isoywd_opt = NaiveDate::from_isoywd_opt; + let from_isoywd = NaiveDate::from_isoywd_opt; let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); - assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); - assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); - - assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); - assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); - - assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); - assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); - assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); + assert_eq!(from_isoywd(2004, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); + assert_eq!(from_isoywd(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); + assert_eq!(from_isoywd(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); + assert_eq!(from_isoywd(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); + assert_eq!(from_isoywd(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); + assert_eq!(from_isoywd(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); + assert_eq!(from_isoywd(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); + assert_eq!(from_isoywd(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); + assert_eq!(from_isoywd(2004, 54, Weekday::Mon), None); + + assert_eq!(from_isoywd(2011, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); + assert_eq!(from_isoywd(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); + assert_eq!(from_isoywd(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); + assert_eq!(from_isoywd(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); + + assert_eq!(from_isoywd(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); + assert_eq!(from_isoywd(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); + assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); + assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); + assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None); } #[test] From 81d58e05f7c8a6c5ba66d173c14868d47001cdb7 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:18:18 +0000 Subject: [PATCH 749/999] Improve docs for crate features The `chrono` crate makes use of several Cargo features: https://github.com/chronotope/chrono/blob/02c68d69a1ff8e6461384a770b87737b5096eae3/Cargo.toml#L19-L46 These features are documented in both `README.md` and the rustdocs in `lib.rs, however, the lists in each place were missing some features and also inconsistent in their descriptions. I've also removed the unused `[wasm-bindgen]` link definition. Fixes #1434. --- README.md | 20 +++++++++++++++----- src/lib.rs | 27 ++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0558d88343..3e4266fb77 100644 --- a/README.md +++ b/README.md @@ -50,21 +50,31 @@ See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference. Default features: -* `alloc`: Enable features that depend on allocation (primarily string formatting) +* `alloc`: Enable features that depend on allocation (primarily string formatting). * `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` and adds interoperation with standard library types and traits. * `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. -* `now`: Enables reading the system time (`now`) +* `now`: Enables reading the system time (`now`). * `wasmbind`: Interface with the JS Date API for the `wasm32` target. Optional features: -* `serde`: Enable serialization/deserialization via serde. -* `rkyv`: Enable serialization/deserialization via rkyv. +* `serde`: Enable serialization/deserialization via [serde]. +* `rkyv`: Deprecated, use the `rkyv-*` features. +* `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types. +* `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types. +* `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types. +* `rkyv-validation`: Enable rkyv validation support using `bytecheck`. * `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). -* `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate. +* `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. * `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. The implementation and API may change or even be removed in a patch release. Feedback welcome. +* `oldtime`: This feature no longer has any effect; it used to offer compatibility with the `time` 0.1 crate. + +Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. + +[serde]: https://github.com/serde-rs/serde +[rkyv]: https://github.com/rkyv/rkyv ## Rust version requirements diff --git a/src/lib.rs b/src/lib.rs index 39f310dd22..1cc1a36519 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,30 +24,35 @@ //! //! Default features: //! -//! - `alloc`: Enable features that depend on allocation (primarily string formatting) +//! - `alloc`: Enable features that depend on allocation (primarily string formatting). //! - `std`: Enables functionality that depends on the standard library. This //! is a superset of `alloc` and adds interoperation with standard library types //! and traits. -//! - `clock`: Enables reading the system time (`now`) that depends on the standard library for -//! UNIX-like operating systems and the Windows API (`winapi`) for Windows. +//! - `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +//! - `now`: Enables reading the system time (`now`). //! - `wasmbind`: Interface with the JS Date API for the `wasm32` target. //! //! Optional features: //! -//! - [`serde`][]: Enable serialization/deserialization via serde. -//! - `rkyv`: Enable serialization/deserialization via rkyv. -//! - `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate. +//! - `serde`: Enable serialization/deserialization via [serde]. +//! - `rkyv`: Deprecated, use the `rkyv-*` features. +//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types. +//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types. +//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types. +//! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. +//! - `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). +//! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. //! - `unstable-locales`: Enable localization. This adds various methods with a //! `_localized` suffix. The implementation and API may change or even be //! removed in a patch release. Feedback welcome. -//! - `oldtime`: this feature no langer has a function, but once offered compatibility with the -//! `time` 0.1 crate. +//! - `oldtime`: This feature no longer has any effect; it used to offer compatibility with the `time` 0.1 crate. //! -//! [`serde`]: https://github.com/serde-rs/serde -//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen +//! Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. //! -//! See the [cargo docs][] for examples of specifying features. +//! See the [cargo docs] for examples of specifying features. //! +//! [serde]: https://github.com/serde-rs/serde +//! [rkyv]: https://github.com/rkyv/rkyv //! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features //! //! ## Overview From 8fcad1fda9f3231548b2384cd8dcec60056fcac7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Feb 2024 08:22:42 +0100 Subject: [PATCH 750/999] Update windows-bindgen to 0.53 --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 04e427f393..6269903203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.52" } +windows-bindgen = { version = "0.53" } # The MSRV of its windows-metatada 0.53 dependency is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 7574fb3317..9bb8ae6365 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,4 +1,4 @@ -// Bindings generated by `windows-bindgen` 0.52.0 +// Bindings generated by `windows-bindgen` 0.53.0 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] ::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); From b882f58fa48b5f832b2a106c46ad1b3406c2e6d6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 25 Feb 2024 18:31:16 +0100 Subject: [PATCH 751/999] Simplify error value of `parse_internal` --- src/format/parse.rs | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 26340d3d13..ee26e66334 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -250,7 +250,7 @@ where match parse_internal(parsed, s, items) { Ok("") => Ok(()), Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error - Err((_, e)) => Err(e), + Err(e) => Err(e), } } @@ -277,14 +277,14 @@ where I: Iterator, B: Borrow>, { - parse_internal(parsed, s, items).map_err(|(_s, e)| e) + parse_internal(parsed, s, items) } fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, items: I, -) -> Result<&'b str, (&'b str, ParseError)> +) -> Result<&'b str, ParseError> where I: Iterator, B: Borrow>, @@ -296,7 +296,7 @@ where s = s_; v } - Err(e) => return Err((s, e)), + Err(e) => return Err(e), } }}; } @@ -305,10 +305,10 @@ where match *item.borrow() { Item::Literal(prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(prefix) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } @@ -316,10 +316,10 @@ where #[cfg(feature = "alloc")] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(&prefix[..]) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } @@ -367,7 +367,7 @@ where let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); - 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))? + 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? } else if s.starts_with('+') { try_consume!(scan::number(&s[1..], 1, usize::MAX)) } else { @@ -377,7 +377,7 @@ where } else { try_consume!(scan::number(s, 1, width)) }; - set(parsed, v).map_err(|e| (s, e))?; + set(parsed, v)?; } Item::Fixed(ref spec) => { @@ -386,66 +386,66 @@ where match spec { &ShortMonthName => { let month0 = try_consume!(scan::short_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &LongMonthName => { let month0 = try_consume!(scan::short_or_long_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &ShortWeekdayName => { let weekday = try_consume!(scan::short_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LongWeekdayName => { let weekday = try_consume!(scan::short_or_long_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LowerAmPm | &UpperAmPm => { if s.len() < 2 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { (b'a', b'm') => false, (b'p', b'm') => true, - _ => return Err((s, INVALID)), + _ => return Err(INVALID), }; - parsed.set_ampm(ampm).map_err(|e| (s, e))?; + parsed.set_ampm(ampm)?; s = &s[2..]; } &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } } &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 3)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { if s.len() < 6 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { if s.len() < 9 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &TimezoneName => { @@ -463,7 +463,7 @@ where false, true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { @@ -474,7 +474,7 @@ where false, true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, @@ -486,7 +486,7 @@ where true, true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), @@ -501,7 +501,7 @@ where } Item::Error => { - return Err((s, BAD_FORMAT)); + return Err(BAD_FORMAT); } } } @@ -564,7 +564,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult Item::Space(""), ]; - s = parse_internal(parsed, s, DATE_ITEMS.iter()).map_err(|(_s, e)| e)?; + s = parse_internal(parsed, s, DATE_ITEMS.iter())?; s = match s.as_bytes().first() { Some(&b't' | &b'T' | &b' ') => &s[1..], @@ -572,7 +572,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult None => return Err(TOO_SHORT), }; - s = parse_internal(parsed, s, TIME_ITEMS.iter()).map_err(|(_s, e)| e)?; + s = parse_internal(parsed, s, TIME_ITEMS.iter())?; s = s.trim_start(); let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { (&s[3..], 0) From 9efa85d9b62c19f8eb4f1f4c588015755b3f0e3d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Feb 2024 12:06:55 +0100 Subject: [PATCH 752/999] Return `IMPOSSIBLE` if year fields don't match --- src/format/parsed.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 19f7f81615..efd9019a28 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -309,7 +309,7 @@ impl Parsed { // we should filter a negative full year first. (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { if y < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } let q_ = y / 100; let r_ = y % 100; @@ -324,7 +324,7 @@ impl Parsed { // reconstruct the full year. make sure that the result is always positive. (None, Some(q), Some(r @ 0..=99)) => { if q < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); Ok(Some(y.ok_or(OUT_OF_RANGE)?)) @@ -828,7 +828,7 @@ mod tests { ); assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); - assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(IMPOSSIBLE)); let max_year = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, @@ -864,10 +864,10 @@ mod tests { ); assert_eq!( parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), - Err(OUT_OF_RANGE) + Err(IMPOSSIBLE) ); - assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE)); // weekdates assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); From a8908b44284f775829d8841c46918c80adaaa392 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Feb 2024 18:02:04 +0100 Subject: [PATCH 753/999] Expand `Parsed` documentation --- src/format/parsed.rs | 112 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index efd9019a28..820728ffff 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -9,13 +9,117 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; -/// Parsed parts of date and time. There are two classes of methods: +/// A type to hold parsed fields of date and time that can check all fields are consistent. /// -/// - `set_*` methods try to set given field(s) while checking for the consistency. -/// It may or may not check for the range constraint immediately (for efficiency reasons). +/// There are two classes of methods: +/// +/// - `set_*` methods to set fields you have available. They do a basic range check, and if the +/// same field is set more than once it is checked for consistency. /// /// - `to_*` methods try to make a concrete date and time value out of set fields. -/// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. +/// They fully check that all fields are consistent and whether the date/datetime exists. +/// +/// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it +/// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the +/// results of a string parsed with chrono without converting it to concrete types. +/// +/// # Resolving algorithm +/// +/// Resolving date/time parts is littered with lots of corner cases, which is why common date/time +/// parsers do not implement it correctly. +/// +/// Chrono provides a complete resolution algorithm that checks all fields for consistency via the +/// `Parsed` type. +/// +/// As an easy example, consider RFC 2822. The [RFC 2822 date and time format] has a day of the week +/// part, which should be consistent with the other date parts. But a `strptime`-based parse would +/// happily accept inconsistent input: +/// +/// ```python +/// >>> import time +/// >>> time.strptime('Wed, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=2, tm_yday=365, tm_isdst=-1) +/// >>> time.strptime('Thu, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=3, tm_yday=365, tm_isdst=-1) +/// ``` +/// +/// [RFC 2822 date and time format]: https://tools.ietf.org/html/rfc2822#section-3.3 +/// +/// # Example +/// +/// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent. +/// +#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] +#[cfg_attr(feature = "alloc", doc = "```rust")] +/// use chrono::format::{ParseErrorKind, Parsed}; +/// use chrono::Weekday; +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Wed)?; +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let dt = parsed.to_datetime()?; +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if let Err(error) = result { +/// assert_eq!(error.kind(), ParseErrorKind::Impossible); +/// } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +/// +/// The same using chrono's build-in parser for RFC 2822 (the [RFC2822 formatting item]) and +/// [`format::parse()`] showing how to inspect a field on failure. +/// +/// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 +/// [`format::parse()`]: crate::format::parse() +/// +#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] +#[cfg_attr(feature = "alloc", doc = "```rust")] +/// use chrono::format::{parse, Fixed, Item, Parsed}; +/// use chrono::Weekday; +/// +/// let rfc_2822 = [Item::Fixed(Fixed::RFC2822)]; +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let dt = parsed.to_datetime()?; +/// +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if result.is_err() { +/// // What is the weekday? +/// assert_eq!(parsed.weekday, Some(Weekday::Thu)); +/// } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` #[allow(clippy::manual_non_exhaustive)] #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { From e9e9adf62256d5af32c79edd62001feb6a66af7c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 14 Feb 2024 07:07:48 +0100 Subject: [PATCH 754/999] Improve documentation of `Parsed::set_*` --- src/format/parsed.rs | 226 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 200 insertions(+), 26 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 820728ffff..6381be2027 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -125,7 +125,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; pub struct Parsed { /// Year. /// - /// This can be negative unlike [`year_div_100`](#structfield.year_div_100) + /// This can be negative, unlike [`year_div_100`](#structfield.year_div_100) /// and [`year_mod_100`](#structfield.year_mod_100) fields. pub year: Option, @@ -141,7 +141,7 @@ pub struct Parsed { /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). /// - /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and + /// This can be negative, unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. pub isoyear: Option, @@ -232,13 +232,27 @@ impl Parsed { Parsed::default() } - /// Tries to set the [`year`](#structfield.year) field from given value. + /// Set the 'year' field to the given value. + /// + /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value. + /// Set the 'year divided by 100' field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { @@ -247,7 +261,20 @@ impl Parsed { set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value. + /// Set the 'year modulo 100' field to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the 'year divided by 100' field is missing (and the full 'year' + /// field is also not set), it assumes a default value for the 'year divided by 100' field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// Currently only checks the value is positive and not out of range for an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { @@ -256,13 +283,32 @@ impl Parsed { set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value. + /// Set the 'year' field that is part of an [ISO 8601 week date] to the given value. + /// + /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. + /// Set the 'year divided by 100' field that is part of an [ISO 8601 week date] to the given + /// value. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { @@ -274,7 +320,22 @@ impl Parsed { ) } - /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value. + /// Set the 'year modulo 100' that is part of an [ISO 8601 week date] field to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the 'year divided by 100' field is missing (and the full `year` + /// field is also not set), it assumes a default value for the 'year divided by 100' field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// Currently only checks the value is positive and not out of range for an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { if value < 0 { @@ -286,57 +347,122 @@ impl Parsed { ) } - /// Tries to set the [`month`](#structfield.month) field from given value. + /// Set the 'month' field to the given value. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_month(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value. + /// Set the 'week number starting with Sunday' field to the given value. + /// + /// Week 1 starts at the first Sunday of January. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value. + /// Set the 'week number starting with Monday' field to the given value. + /// + /// Week 1 starts at the first Monday of January. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value. + /// Set the '[ISO 8601 week number]' field to the given value. + /// + /// [ISO 8601 week number]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 1-53. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`weekday`](#structfield.weekday) field from given value. + /// Set the 'day of the week' field to the given value. + /// + /// # Errors + /// + /// May return `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { set_if_consistent(&mut self.weekday, value) } - /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. + /// Set the 'ordinal' (day of the year) field to the given value. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 1-366. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`day`](#structfield.day) field from given value. + /// Set the 'day of the month' field to the given value. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 1-31. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_day(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value. - /// (`false` for AM, `true` for PM) + /// Set the 'am/pm' field to the given value. + /// + /// `false` indicates AM and `true` indicates PM. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { set_if_consistent(&mut self.hour_div_12, u32::from(value)) } - /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from - /// given hour number in 12-hour clocks. + /// Set the 'hour number in 12-hour clocks' field to the given value. + /// + /// Value must be in the canonical range of 1-12. + /// It will internally be stored as 0-11 (`value % 12`). + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { if !(1..=12).contains(&value) { @@ -345,8 +471,16 @@ impl Parsed { set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) } - /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and - /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. + /// Set the 'hour' field to the given value. + /// + /// Internally this sets the 'hour modulo 12' and 'am/pm' fields. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-23. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` one of the fields was already set to a different value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?; @@ -355,31 +489,71 @@ impl Parsed { Ok(()) } - /// Tries to set the [`minute`](#structfield.minute) field from given value. + /// Set the 'minute' field to the given value. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-59. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`second`](#structfield.second) field from given value. + /// Set the 'second' field to the given value. + /// + /// The value can be 60 in the case of a leap second. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-60. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_second(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. + /// Set the 'nanosecond' field to the given value. + /// + /// This is the number of nanoseconds since the whole second. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. + /// Set the 'timestamp' field to the given value. + /// + /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on + /// January 1, 1970. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) } - /// Tries to set the [`offset`](#structfield.offset) field from given value. + /// Set the 'offset from local time to UTC' field to the given value. + /// + /// The offset is in seconds. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is ouside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) From 48f05499d65abe8cbd93eedd98bd12f93dfa22b8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Feb 2024 18:44:10 +0100 Subject: [PATCH 755/999] Return correct error in `Parsed::to_fixed_offset` --- src/format/parsed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 6381be2027..95d45f1a88 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -892,7 +892,7 @@ impl Parsed { /// Returns a parsed fixed time zone offset out of given fields. pub fn to_fixed_offset(&self) -> ParseResult { - self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) + FixedOffset::east_opt(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE) } /// Returns a parsed timezone-aware date and time out of given fields. From 85c70ffad931557d1ad8d4e49150c8d230541d25 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Feb 2024 22:31:54 +0100 Subject: [PATCH 756/999] Improve documentation of `Parset::to_*` --- src/format/parsed.rs | 102 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 95d45f1a88..0f5005129d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -570,6 +570,18 @@ impl Parsed { /// /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, /// the two-digit year is used to guess the century number then. + /// + /// It checks all given date fields are consistent with each other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete date. + /// - `OUT_OF_RANGE` + /// - if any of the date fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the value would be outside the range of a [`NaiveDate`]. + /// - if the date does not exist. pub fn to_naive_date(&self) -> ParseResult { fn resolve_year( y: Option, @@ -776,6 +788,14 @@ impl Parsed { /// - Hour, minute, second, nanosecond. /// /// It is able to handle leap seconds when given second is 60. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if any of the time fields of `Parsed` are set to a value beyond + /// their acceptable range. + /// - `NOT_ENOUGH` if an hour field is missing, if AM/PM is missing in a 12-hour clock, + /// if minutes are missing, or if seconds are missing while the nanosecond field is present. pub fn to_naive_time(&self) -> ParseResult { let hour_div_12 = match self.hour_div_12 { Some(v @ 0..=1) => v, @@ -811,13 +831,25 @@ impl Parsed { NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) } - /// Returns a parsed naive date and time out of given fields, - /// except for the [`offset`](#structfield.offset) field (assumed to have a given value). - /// This is required for parsing a local time or other known-timezone inputs. + /// Returns a parsed naive date and time out of given fields, except for the offset field. + /// + /// The offset is assumed to have a given value. It is not compared against the offset field set + /// in the `Parsed` type, so it is allowed to be inconsistent. + /// + /// This method is able to determine the combined date and time from date and time fields or + /// from a single timestamp field. It checks all fields are consistent with each other. + /// + /// # Errors /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field. - /// Either way those fields have to be consistent to each other. + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime. + /// - `OUT_OF_RANGE` + /// - if any of the date or time fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`]. + /// - if the date does not exist. pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult { let date = self.to_naive_date(); let time = self.to_naive_time(); @@ -891,16 +923,34 @@ impl Parsed { } /// Returns a parsed fixed time zone offset out of given fields. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if the offset is out of range for a `FixedOffset`. + /// - `NOT_ENOUGH` if the offset field is not set. pub fn to_fixed_offset(&self) -> ParseResult { FixedOffset::east_opt(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE) } /// Returns a parsed timezone-aware date and time out of given fields. /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. + /// This method is able to determine the combined date and time from date, time and offset + /// fields, and/or from a single timestamp field. It checks all fields are consistent with each + /// other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime + /// including offset from UTC. + /// - `OUT_OF_RANGE` + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if the date does not exist. pub fn to_datetime(&self) -> ParseResult> { // If there is no explicit offset, consider a timestamp value as indication of a UTC value. let offset = match (self.offset, self.timestamp) { @@ -919,14 +969,30 @@ impl Parsed { } /// Returns a parsed timezone-aware date and time out of given fields, - /// with an additional `TimeZone` used to interpret and validate the local date. - /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. - /// If parsed fields include an UTC offset, it also has to be consistent to - /// [`offset`](#structfield.offset). + /// with an additional [`TimeZone`] used to interpret and validate the local date. + /// + /// This method is able to determine the combined date and time from date and time, and/or from + /// a single timestamp field. It checks all fields are consistent with each other. + /// + /// If the parsed fields include an UTC offset, it also has to be consistent with the offset in + /// the provided `tz` time zone for that datetime. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` + /// - if any of the date fields conflict, if a timestamp conflicts with any of the other + /// fields, or if the offset field is set but differs from the offset at that time in the + /// `tz` time zone. + /// - if the local datetime does not exists in the provided time zone (because it falls in a + /// transition due to for example DST). + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime, or if + /// the local time in the provided time zone is ambiguous (because it falls in a transition + /// due to for example DST) while there is no offset field or timestamp field set. + /// - `OUT_OF_RANGE` + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the date does not exist. pub fn to_datetime_with_timezone(&self, tz: &Tz) -> ParseResult> { // if we have `timestamp` specified, guess an offset from that. let mut guessed_offset = 0; From 0b68d19a3ea5e4907e0a2a78f8b1a58568f4500a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Feb 2024 14:01:49 +0100 Subject: [PATCH 757/999] Move `serde_from` and `SerdeError` to `crate::serde` inline module --- src/datetime/serde.rs | 25 +++++++++------- src/lib.rs | 52 +++++++++++++++++++++++++++++++++ src/naive/datetime/serde.rs | 57 +++---------------------------------- 3 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index e9f5d7e3ef..0d6deba471 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -3,7 +3,6 @@ use serde::{de, ser}; use super::DateTime; use crate::format::{write_rfc3339, SecondsFormat}; -use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; @@ -148,10 +147,10 @@ pub mod ts_nanoseconds { use core::fmt; use serde::{de, ser}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; + use crate::serde::serde_from; + use crate::{DateTime, TimeZone, Utc}; - use super::{serde_from, NanoSecondsTimestampVisitor}; + use super::NanoSecondsTimestampVisitor; /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch /// @@ -448,9 +447,10 @@ pub mod ts_microseconds { use core::fmt; use serde::{de, ser}; - use super::{serde_from, MicroSecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; + use crate::serde::serde_from; + use crate::{DateTime, TimeZone, Utc}; + + use super::MicroSecondsTimestampVisitor; /// Serialize a UTC datetime into an integer number of microseconds since the epoch /// @@ -726,9 +726,10 @@ pub mod ts_milliseconds { use core::fmt; use serde::{de, ser}; - use super::{serde_from, MilliSecondsTimestampVisitor}; - use crate::offset::TimeZone; - use crate::{DateTime, Utc}; + use crate::serde::serde_from; + use crate::{DateTime, TimeZone, Utc}; + + use super::MilliSecondsTimestampVisitor; /// Serialize a UTC datetime into an integer number of milliseconds since the epoch /// @@ -1005,9 +1006,11 @@ pub mod ts_seconds { use core::fmt; use serde::{de, ser}; - use super::{serde_from, SecondsTimestampVisitor}; + use crate::serde::serde_from; use crate::{DateTime, LocalResult, TimeZone, Utc}; + use super::SecondsTimestampVisitor; + /// Serialize a UTC datetime into an integer number of seconds since the epoch /// /// Intended for use with `serde`s `serialize_with` attribute. diff --git a/src/lib.rs b/src/lib.rs index 1cc1a36519..5366c56076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -629,7 +629,59 @@ pub use naive::__BenchYearFlags; /// [2]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] pub mod serde { + use crate::offset::LocalResult; + use core::fmt; + use serde::de; + pub use super::datetime::serde::*; + + // lik? function to convert a LocalResult into a serde-ish Result + pub(crate) fn serde_from(me: LocalResult, ts: &V) -> Result + where + E: de::Error, + V: fmt::Display, + T: fmt::Display, + { + match me { + LocalResult::None => Err(E::custom(ne_timestamp(ts))), + LocalResult::Ambiguous(min, max) => { + Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) + } + LocalResult::Single(val) => Ok(val), + } + } + + pub(crate) enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, + } + + /// Construct a [`SerdeError::NonExistent`] + pub(crate) fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } + } + + impl fmt::Debug for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChronoSerdeError({})", self) + } + } + + // impl core::error::Error for SerdeError {} + impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SerdeError::NonExistent { timestamp } => { + write!(f, "value is not a legal timestamp: {}", timestamp) + } + SerdeError::Ambiguous { timestamp, min, max } => write!( + f, + "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max + ), + } + } + } } /// Zero-copy serialization/deserialization with rkyv. diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 16088e368c..0eb562ea89 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -2,7 +2,6 @@ use core::fmt; use serde::{de, ser}; use super::NaiveDateTime; -use crate::offset::LocalResult; /// Serialize a `NaiveDateTime` as an RFC 3339 string /// @@ -83,7 +82,7 @@ pub mod ts_nanoseconds { use core::fmt; use serde::{de, ser}; - use super::ne_timestamp; + use crate::serde::ne_timestamp; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of nanoseconds since the epoch @@ -372,7 +371,7 @@ pub mod ts_microseconds { use core::fmt; use serde::{de, ser}; - use super::ne_timestamp; + use crate::serde::ne_timestamp; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of microseconds since the epoch @@ -636,7 +635,7 @@ pub mod ts_milliseconds { use core::fmt; use serde::{de, ser}; - use super::ne_timestamp; + use crate::serde::ne_timestamp; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of milliseconds since the epoch @@ -896,7 +895,7 @@ pub mod ts_seconds { use core::fmt; use serde::{de, ser}; - use super::ne_timestamp; + use crate::serde::ne_timestamp; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of seconds since the epoch @@ -1109,54 +1108,6 @@ pub mod ts_seconds_option { } } -// lik? function to convert a LocalResult into a serde-ish Result -pub(crate) fn serde_from(me: LocalResult, ts: &V) -> Result -where - E: de::Error, - V: fmt::Display, - T: fmt::Display, -{ - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) - } - LocalResult::Single(val) => Ok(val), - } -} - -enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, -} - -/// Construct a [`SerdeError::NonExistent`] -fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } -} - -impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) - } -} - -// impl core::error::Error for SerdeError {} -impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SerdeError::NonExistent { timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) - } - SerdeError::Ambiguous { timestamp, min, max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), - } - } -} - #[cfg(test)] mod tests { use crate::naive::datetime::{test_decodable_json, test_encodable_json}; From bbdc332958f7a130d521af71eb7a293c300fb725 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 24 Feb 2024 20:52:45 +0100 Subject: [PATCH 758/999] Simplify `SerdeError` --- src/datetime/serde.rs | 72 ++++++++++++++++--------------------- src/lib.rs | 42 +++++----------------- src/naive/datetime/serde.rs | 28 +++++++-------- 3 files changed, 51 insertions(+), 91 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 0d6deba471..4ef9f95515 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -147,8 +147,8 @@ pub mod ts_nanoseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::serde_from; - use crate::{DateTime, TimeZone, Utc}; + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; use super::NanoSecondsTimestampVisitor; @@ -239,13 +239,11 @@ pub mod ts_nanoseconds { where E: de::Error, { - serde_from( - Utc.timestamp_opt( - value.div_euclid(1_000_000_000), - (value.rem_euclid(1_000_000_000)) as u32, - ), - &value, + DateTime::from_timestamp( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, ) + .ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in nanoseconds since the epoch @@ -253,10 +251,8 @@ pub mod ts_nanoseconds { where E: de::Error, { - serde_from( - Utc.timestamp_opt((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32), - &value, - ) + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) } } } @@ -447,8 +443,8 @@ pub mod ts_microseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::serde_from; - use crate::{DateTime, TimeZone, Utc}; + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; use super::MicroSecondsTimestampVisitor; @@ -529,13 +525,11 @@ pub mod ts_microseconds { where E: de::Error, { - serde_from( - Utc.timestamp_opt( - value.div_euclid(1_000_000), - (value.rem_euclid(1_000_000) * 1_000) as u32, - ), - &value, + DateTime::from_timestamp( + value.div_euclid(1_000_000), + (value.rem_euclid(1_000_000) * 1000) as u32, ) + .ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in milliseconds since the epoch @@ -543,10 +537,11 @@ pub mod ts_microseconds { where E: de::Error, { - serde_from( - Utc.timestamp_opt((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32), - &value, + DateTime::from_timestamp( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, ) + .ok_or_else(|| invalid_ts(value)) } } } @@ -726,8 +721,8 @@ pub mod ts_milliseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::serde_from; - use crate::{DateTime, TimeZone, Utc}; + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; use super::MilliSecondsTimestampVisitor; @@ -808,7 +803,7 @@ pub mod ts_milliseconds { where E: de::Error, { - serde_from(Utc.timestamp_millis_opt(value), &value) + DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in milliseconds since the epoch @@ -816,10 +811,8 @@ pub mod ts_milliseconds { where E: de::Error, { - serde_from( - Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), - &value, - ) + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) } } } @@ -1006,8 +999,8 @@ pub mod ts_seconds { use core::fmt; use serde::{de, ser}; - use crate::serde::serde_from; - use crate::{DateTime, LocalResult, TimeZone, Utc}; + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; use super::SecondsTimestampVisitor; @@ -1078,7 +1071,7 @@ pub mod ts_seconds { where E: de::Error, { - serde_from(Utc.timestamp_opt(value, 0), &value) + DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in seconds since the epoch @@ -1086,14 +1079,11 @@ pub mod ts_seconds { where E: de::Error, { - serde_from( - if value > i64::MAX as u64 { - LocalResult::None - } else { - Utc.timestamp_opt(value as i64, 0) - }, - &value, - ) + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value)) + } } } } diff --git a/src/lib.rs b/src/lib.rs index 5366c56076..ad2312b877 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -629,56 +629,30 @@ pub use naive::__BenchYearFlags; /// [2]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] pub mod serde { - use crate::offset::LocalResult; use core::fmt; use serde::de; pub use super::datetime::serde::*; - // lik? function to convert a LocalResult into a serde-ish Result - pub(crate) fn serde_from(me: LocalResult, ts: &V) -> Result + /// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`. + pub(crate) fn invalid_ts(value: T) -> E where E: de::Error, - V: fmt::Display, T: fmt::Display, { - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max })) - } - LocalResult::Single(val) => Ok(val), - } - } - - pub(crate) enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, + E::custom(SerdeError::InvalidTimestamp(value)) } - /// Construct a [`SerdeError::NonExistent`] - pub(crate) fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } - } - - impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) - } + enum SerdeError { + InvalidTimestamp(T), } - // impl core::error::Error for SerdeError {} - impl fmt::Display for SerdeError { + impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SerdeError::NonExistent { timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) + SerdeError::InvalidTimestamp(ts) => { + write!(f, "value is not a legal timestamp: {}", ts) } - SerdeError::Ambiguous { timestamp, min, max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), } } } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 0eb562ea89..a72676a498 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -82,7 +82,7 @@ pub mod ts_nanoseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::ne_timestamp; + use crate::serde::invalid_ts; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of nanoseconds since the epoch @@ -175,7 +175,7 @@ pub mod ts_nanoseconds { value.div_euclid(1_000_000_000), (value.rem_euclid(1_000_000_000)) as u32, ) - .ok_or_else(|| E::custom(ne_timestamp(value))) + .ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result @@ -186,7 +186,7 @@ pub mod ts_nanoseconds { (value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32, ) - .ok_or_else(|| E::custom(ne_timestamp(value))) + .ok_or_else(|| invalid_ts(value)) } } } @@ -371,7 +371,7 @@ pub mod ts_microseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::ne_timestamp; + use crate::serde::invalid_ts; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of microseconds since the epoch @@ -450,8 +450,7 @@ pub mod ts_microseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_micros(value) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result @@ -462,7 +461,7 @@ pub mod ts_microseconds { (value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32, ) - .ok_or_else(|| E::custom(ne_timestamp(value))) + .ok_or_else(|| invalid_ts(value)) } } } @@ -635,7 +634,7 @@ pub mod ts_milliseconds { use core::fmt; use serde::{de, ser}; - use crate::serde::ne_timestamp; + use crate::serde::invalid_ts; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of milliseconds since the epoch @@ -714,8 +713,7 @@ pub mod ts_milliseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_millis(value) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result @@ -726,7 +724,7 @@ pub mod ts_milliseconds { (value / 1000) as i64, ((value % 1000) * 1_000_000) as u32, ) - .ok_or_else(|| E::custom(ne_timestamp(value))) + .ok_or_else(|| invalid_ts(value)) } } } @@ -895,7 +893,7 @@ pub mod ts_seconds { use core::fmt; use serde::{de, ser}; - use crate::serde::ne_timestamp; + use crate::serde::invalid_ts; use crate::NaiveDateTime; /// Serialize a datetime into an integer number of seconds since the epoch @@ -967,16 +965,14 @@ pub mod ts_seconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_opt(value, 0).ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value as i64, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) + NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value)) } } } From a6c0c6425cf3b1c2add43ff588121ac658cf672a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 24 Feb 2024 22:02:19 +0100 Subject: [PATCH 759/999] Check for invalid cast --- src/naive/datetime/serde.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index a72676a498..da4f416017 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -972,7 +972,11 @@ pub mod ts_seconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value)) + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value)) + } } } } From e292d9bc90b317fc5e7fef013bc3c7fed63dbed3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Feb 2024 11:30:40 +0100 Subject: [PATCH 760/999] Simplify `from_isoywd` a bit --- src/naive/date/mod.rs | 46 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index f26dba9e6e..cd07f0a1b5 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -314,33 +314,29 @@ impl NaiveDate { pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { let flags = YearFlags::from_year(year); let nweeks = flags.nisoweeks(); - if 1 <= week && week <= nweeks { - // ordinal = week ordinal - delta - let weekord = week * 7 + weekday as u32; - let delta = flags.isoweek_delta(); - if weekord <= delta { - // ordinal < 1, previous year - let prevflags = YearFlags::from_year(year - 1); - NaiveDate::from_ordinal_and_flags( - year - 1, - weekord + prevflags.ndays() - delta, - prevflags, - ) + if week == 0 || week > nweeks { + return None; + } + // ordinal = week ordinal - delta + let weekord = week * 7 + weekday as u32; + let delta = flags.isoweek_delta(); + let (year, ordinal, flags) = if weekord <= delta { + // ordinal < 1, previous year + let prevflags = YearFlags::from_year(year - 1); + (year - 1, weekord + prevflags.ndays() - delta, prevflags) + } else { + let ordinal = weekord - delta; + let ndays = flags.ndays(); + if ordinal <= ndays { + // this year + (year, ordinal, flags) } else { - let ordinal = weekord - delta; - let ndays = flags.ndays(); - if ordinal <= ndays { - // this year - NaiveDate::from_ordinal_and_flags(year, ordinal, flags) - } else { - // ordinal > ndays, next year - let nextflags = YearFlags::from_year(year + 1); - NaiveDate::from_ordinal_and_flags(year + 1, ordinal - ndays, nextflags) - } + // ordinal > ndays, next year + let nextflags = YearFlags::from_year(year + 1); + (year + 1, ordinal - ndays, nextflags) } - } else { - None - } + }; + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) } /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with From dd201649b586fe03aae196167d7338e077db026b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 28 Sep 2023 18:16:47 +0200 Subject: [PATCH 761/999] Use `overflowing_naive_local` in `checked_(add|sub)_months` --- src/datetime/mod.rs | 24 ++++++++++++++++-------- src/datetime/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 059f97d816..bb0f2e3871 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -436,13 +436,17 @@ impl DateTime { /// # 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. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). #[must_use] - pub fn checked_add_months(self, rhs: Months) -> Option> { - self.naive_local() - .checked_add_months(rhs)? + pub fn checked_add_months(self, months: Months) -> Option> { + // `NaiveDate::checked_add_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_add_months(months)? .and_local_timezone(Tz::from_offset(&self.offset)) .single() } @@ -469,13 +473,17 @@ impl DateTime { /// # 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. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). #[must_use] - pub fn checked_sub_months(self, rhs: Months) -> Option> { - self.naive_local() - .checked_sub_months(rhs)? + pub fn checked_sub_months(self, months: Months) -> Option> { + // `NaiveDate::checked_sub_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_sub_months(months)? .and_local_timezone(Tz::from_offset(&self.offset)) .single() } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index be83955ee7..fcf6f6c404 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1461,6 +1461,31 @@ fn test_min_max_setters() { assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); } +#[test] +fn test_min_max_add_months() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_months(Months::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_months(Months::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Months(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_months(Months::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_months(Months::new(1)), None); + + assert_eq!(beyond_max.checked_add_months(Months::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_months(Months::new(1)), None); + assert_eq!(beyond_max.checked_sub_months(Months::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_months(Months::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Months(1)).and_time(max_time))) + ); +} + #[test] #[should_panic] fn test_local_beyond_min_datetime() { From 06bd82cccf9c7e14cd48aec27aabe8d8d9eeb490 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 12:36:55 +0200 Subject: [PATCH 762/999] Extend fast path in `NaiveDate::add_days` --- src/naive/date/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index cd07f0a1b5..a3fd2ca22e 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -695,10 +695,14 @@ impl NaiveDate { /// Add a duration of `i32` days to the date. pub(crate) const fn add_days(self, days: i32) -> Option { - // fast path if the result is within the same year + // Fast path if the result is within the same year. + // Also `DateTime::checked_(add|sub)_days` relies on this path, because if the value remains + // within the year it doesn't do a check if the year is in range. + // This way `DateTime:checked_(add|sub)_days(Days::new(0))` can be a no-op on dates were the + // local datetime is beyond `NaiveDate::{MIN, MAX}. const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; if let Some(ordinal) = ((self.yof() & ORDINAL_MASK) >> 4).checked_add(days) { - if ordinal > 0 && ordinal <= 365 { + if ordinal > 0 && ordinal <= (365 + self.leap_year() as i32) { let year_and_flags = self.yof() & !ORDINAL_MASK; return Some(NaiveDate::from_yof(year_and_flags | (ordinal << 4))); } From 43675f162c5015467323df4014e1f4bb77ea2e65 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 28 Sep 2023 17:45:13 +0200 Subject: [PATCH 763/999] Use `overflowing_naive_local` in `checked_(add|sub)_days` --- src/datetime/mod.rs | 31 +++++++++++++++++++++---------- src/datetime/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index bb0f2e3871..8f8b8f3561 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -493,15 +493,22 @@ impl DateTime { /// # 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. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). #[must_use] pub fn checked_add_days(self, days: Days) -> Option { - self.naive_local() - .checked_add_days(days)? - .and_local_timezone(TimeZone::from_offset(&self.offset)) - .single() + if days == Days::new(0) { + return Some(self); + } + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_add_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt <= &DateTime::::MAX_UTC) } /// Subtract a duration in [`Days`] from the date part of the `DateTime`. @@ -509,15 +516,19 @@ impl DateTime { /// # 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. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { - self.naive_local() - .checked_sub_days(days)? - .and_local_timezone(TimeZone::from_offset(&self.offset)) - .single() + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_sub_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC) } /// Subtracts another `DateTime` from the current date and time. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index fcf6f6c404..d465797329 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1461,6 +1461,31 @@ fn test_min_max_setters() { assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); } +#[test] +fn test_min_max_add_days() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_days(Days::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_days(Days::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Days(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_days(Days::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_days(Days::new(1)), None); + + assert_eq!(beyond_max.checked_add_days(Days::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_days(Days::new(1)), None); + assert_eq!(beyond_max.checked_sub_days(Days::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_days(Days::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Days(1)).and_time(max_time))) + ); +} + #[test] fn test_min_max_add_months() { let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); From ca51f694bbc8eee312884815f9cfa0f70067d6ee Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 28 Sep 2023 18:25:37 +0200 Subject: [PATCH 764/999] Make `with_year` return `Some` if it is a no-op --- src/datetime/mod.rs | 9 ++++++--- src/datetime/tests.rs | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8f8b8f3561..5ade1fca34 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1099,12 +1099,15 @@ impl Datelike for DateTime { /// # 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. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless the year remains the same). fn with_year(&self, year: i32) -> Option> { - map_local(self, |datetime| datetime.with_year(year)) + map_local(self, |dt| match dt.year() == year { + true => Some(dt), + false => dt.with_year(year), + }) } /// Makes a new `DateTime` with the month number (starting from 1) changed. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index d465797329..425618de04 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1421,6 +1421,7 @@ fn test_min_max_setters() { let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); assert_eq!(beyond_min.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_min.with_year(beyond_min.year()), Some(beyond_min)); assert_eq!(beyond_min.with_month(beyond_min.month()), Some(beyond_min)); assert_eq!(beyond_min.with_month(3), None); assert_eq!(beyond_min.with_month0(beyond_min.month0()), Some(beyond_min)); @@ -1441,6 +1442,7 @@ fn test_min_max_setters() { assert_eq!(beyond_min.with_nanosecond(0), Some(beyond_min)); assert_eq!(beyond_max.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_max.with_year(beyond_max.year()), Some(beyond_max)); assert_eq!(beyond_max.with_month(beyond_max.month()), Some(beyond_max)); assert_eq!(beyond_max.with_month(3), None); assert_eq!(beyond_max.with_month0(beyond_max.month0()), Some(beyond_max)); From 774951ff3df4b96a5d8c00929817968c149eb289 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 29 Feb 2024 07:13:34 +0100 Subject: [PATCH 765/999] Add `DateTime::{from_timestamp_micros, from_timestamp_nanos}` --- src/datetime/mod.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5ade1fca34..833c0127f7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -741,6 +741,78 @@ impl DateTime { Some(try_opt!(NaiveDateTime::from_timestamp_millis(millis)).and_utc()) } + /// Creates a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_micros`](DateTime::timestamp_micros). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_micros`] or [`DateTime::with_timezone`]. + /// + /// # 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 + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_micros: i64 = 1662921288000000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_micros: i64 = -2208936075000000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_micros(micros: i64) -> Option { + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + Self::from_timestamp(secs, nsecs) + } + + /// Creates a new [`DateTime`] from the number of non-leap microseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_nanos`](DateTime::timestamp_nanos). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_nanos`] or [`DateTime::with_timezone`]. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. Because all values can + /// be represented as a `DateTime` this method never fails. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_nanos: i64 = 1662921288_000_000_000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Self { + let secs = nanos.div_euclid(1_000_000_000); + let nsecs = nanos.rem_euclid(1_000_000_000) as u32; + expect!(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range") + } + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; } From 1c1feca6faa45761f174ac045c935ed916d38887 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 28 Feb 2024 21:57:40 +0100 Subject: [PATCH 766/999] Use `UNIX_EPOCH` in `NaiveDateTime::default()` --- src/naive/datetime/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 3c323cb352..3af4900dd3 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -2353,12 +2353,11 @@ impl str::FromStr for NaiveDateTime { /// ```rust /// use chrono::NaiveDateTime; /// -/// let default_date = NaiveDateTime::default(); -/// assert_eq!(Some(default_date), NaiveDateTime::from_timestamp_opt(0, 0)); +/// assert_eq!(NaiveDateTime::default(), NaiveDateTime::UNIX_EPOCH); /// ``` impl Default for NaiveDateTime { fn default() -> Self { - NaiveDateTime::from_timestamp_opt(0, 0).unwrap() + Self::UNIX_EPOCH } } From 0c23fd4df538876bf4d043ab8fddbf9490c40d4d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 28 Feb 2024 18:38:41 +0100 Subject: [PATCH 767/999] Move implementation of `timestamp*` to `DateTime` --- src/datetime/mod.rs | 39 +++++++++++++++++++++++++--------- src/naive/datetime/mod.rs | 44 ++++++++------------------------------- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 833c0127f7..b44c2cf5fd 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -26,9 +26,9 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; -use crate::try_opt; #[allow(deprecated)] use crate::Date; +use crate::{expect, try_opt}; use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] @@ -201,7 +201,10 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp(&self) -> i64 { - self.datetime.timestamp() + const UNIX_EPOCH_DAY: i64 = 719_163; + let gregorian_day = self.datetime.date().num_days_from_ce() as i64; + let seconds_from_midnight = self.datetime.time().num_seconds_from_midnight() as i64; + (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight } /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC. @@ -230,7 +233,8 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_millis(&self) -> i64 { - self.datetime.timestamp_millis() + let as_ms = self.timestamp() * 1000; + as_ms + self.timestamp_subsec_millis() as i64 } /// Returns the number of non-leap-microseconds since January 1, 1970 UTC. @@ -259,7 +263,8 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_micros(&self) -> i64 { - self.datetime.timestamp_micros() + let as_us = self.timestamp() * 1_000_000; + as_us + self.timestamp_subsec_micros() as i64 } /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. @@ -274,9 +279,11 @@ impl DateTime { #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] - #[allow(deprecated)] pub const fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() + expect!( + self.timestamp_nanos_opt(), + "value can not be represented in a timestamp with nanosecond precision." + ) } /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. @@ -345,7 +352,19 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_nanos_opt(&self) -> Option { - self.datetime.timestamp_nanos_opt() + let mut timestamp = self.timestamp(); + let mut subsec_nanos = self.timestamp_subsec_nanos() as i64; + // `(timestamp * 1_000_000_000) + subsec_nanos` may create a temporary that underflows while + // the final value can be represented as an `i64`. + // As workaround we converting the negative case to: + // `((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000)`` + // + // Also see . + if timestamp < 0 { + subsec_nanos -= 1_000_000_000; + timestamp += 1; + } + try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(subsec_nanos) } /// Returns the number of milliseconds since the last second boundary. @@ -354,7 +373,7 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_subsec_millis(&self) -> u32 { - self.datetime.timestamp_subsec_millis() + self.timestamp_subsec_nanos() / 1_000_000 } /// Returns the number of microseconds since the last second boundary. @@ -363,7 +382,7 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_subsec_micros(&self) -> u32 { - self.datetime.timestamp_subsec_micros() + self.timestamp_subsec_nanos() / 1_000 } /// Returns the number of nanoseconds since the last second boundary @@ -372,7 +391,7 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_subsec_nanos(&self) -> u32 { - self.datetime.timestamp_subsec_nanos() + self.datetime.time().nanosecond() } /// Retrieves an associated offset from UTC. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 3af4900dd3..96b8ab30c6 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -454,10 +454,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp(&self) -> i64 { - const UNIX_EPOCH_DAY: i64 = 719_163; - let gregorian_day = self.date.num_days_from_ce() as i64; - let seconds_from_midnight = self.time.num_seconds_from_midnight() as i64; - (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight + self.and_utc().timestamp() } /// Returns the number of non-leap *milliseconds* since midnight on January 1, 1970. @@ -484,8 +481,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_millis(&self) -> i64 { - let as_ms = self.timestamp() * 1000; - as_ms + self.timestamp_subsec_millis() as i64 + self.and_utc().timestamp_millis() } /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. @@ -508,8 +504,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_micros(&self) -> i64 { - let as_us = self.timestamp() * 1_000_000; - as_us + self.timestamp_subsec_micros() as i64 + self.and_utc().timestamp_micros() } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -527,11 +522,9 @@ impl NaiveDateTime { #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] + #[allow(deprecated)] pub const fn timestamp_nanos(&self) -> i64 { - expect!( - self.timestamp_nanos_opt(), - "value can not be represented in a timestamp with nanosecond precision." - ) + self.and_utc().timestamp_nanos() } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -568,26 +561,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_nanos_opt(&self) -> Option { - let mut timestamp = self.timestamp(); - let mut timestamp_subsec_nanos = self.timestamp_subsec_nanos() as i64; - - // subsec nanos are always non-negative, however the timestamp itself (both in seconds and in nanos) can be - // negative. Now i64::MIN is NOT dividable by 1_000_000_000, so - // - // (timestamp * 1_000_000_000) + nanos - // - // may underflow (even when in theory we COULD represent the datetime as i64) because we add the non-negative - // nanos AFTER the multiplication. This is fixed by converting the negative case to - // - // ((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000) - // - // Also see . - if timestamp < 0 && timestamp_subsec_nanos > 0 { - timestamp_subsec_nanos -= 1_000_000_000; - timestamp += 1; - } - - try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(timestamp_subsec_nanos) + self.and_utc().timestamp_nanos_opt() } /// Returns the number of milliseconds since the last whole non-leap second. @@ -615,7 +589,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_subsec_millis(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000_000 + self.and_utc().timestamp_subsec_millis() } /// Returns the number of microseconds since the last whole non-leap second. @@ -643,7 +617,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_subsec_micros(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000 + self.and_utc().timestamp_subsec_micros() } /// Returns the number of nanoseconds since the last whole non-leap second. @@ -671,7 +645,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn timestamp_subsec_nanos(&self) -> u32 { - self.time.nanosecond() + self.and_utc().timestamp_subsec_nanos() } /// Adds given `TimeDelta` to the current date and time. From 27a1ee4cdc401c260a218103ff915200261179c7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 28 Feb 2024 21:59:59 +0100 Subject: [PATCH 768/999] Move implementation of `from_timestamp*` to `DateTime` --- src/datetime/mod.rs | 35 ++++++++++++++++++++--------------- src/naive/datetime/mod.rs | 25 ++++++------------------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b44c2cf5fd..e7d6a06c71 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -201,7 +201,6 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp(&self) -> i64 { - const UNIX_EPOCH_DAY: i64 = 719_163; let gregorian_day = self.datetime.date().num_days_from_ce() as i64; let seconds_from_midnight = self.datetime.time().num_seconds_from_midnight() as i64; (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight @@ -692,7 +691,7 @@ impl DateTime { } impl DateTime { - /// Makes a new [`DateTime`] from the number of non-leap seconds + /// Makes a new `DateTime` from the number of non-leap seconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// @@ -714,10 +713,9 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{DateTime, Utc}; + /// use chrono::DateTime; /// - /// let dt: DateTime = - /// DateTime::::from_timestamp(1431648000, 0).expect("invalid timestamp"); + /// let dt = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp"); /// /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); @@ -725,16 +723,20 @@ impl DateTime { #[inline] #[must_use] pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option { - Some(DateTime { - datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)), - offset: Utc, - }) + let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY; + let secs = secs.rem_euclid(86_400); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + let date = try_opt!(NaiveDate::from_num_days_from_ce_opt(days as i32)); + let time = try_opt!(NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs)); + Some(date.and_time(time).and_utc()) } - /// Makes a new [`DateTime`] from the number of non-leap milliseconds + /// Makes a new `DateTime` from the number of non-leap milliseconds /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). /// - /// This is guaranteed to round-trip with regard to [`timestamp_millis`](DateTime::timestamp_millis). + /// This is guaranteed to round-trip with [`timestamp_millis`](DateTime::timestamp_millis). /// /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use /// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`]. @@ -746,10 +748,9 @@ impl DateTime { /// # Example /// /// ``` - /// use chrono::{DateTime, Utc}; + /// use chrono::DateTime; /// - /// let dt: DateTime = - /// DateTime::::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// let dt = DateTime::from_timestamp_millis(947638923004).expect("invalid timestamp"); /// /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); @@ -757,7 +758,9 @@ impl DateTime { #[inline] #[must_use] pub const fn from_timestamp_millis(millis: i64) -> Option { - Some(try_opt!(NaiveDateTime::from_timestamp_millis(millis)).and_utc()) + let secs = millis.div_euclid(1000); + let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; + Self::from_timestamp(secs, nsecs) } /// Creates a new `DateTime` from the number of non-leap microseconds @@ -1900,6 +1903,8 @@ where } } +const UNIX_EPOCH_DAY: i64 = 719_163; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 96b8ab30c6..78cf91a4d4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -130,8 +130,9 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { - let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); - expect!(datetime, "invalid or out-of-range datetime") + let datetime = + expect!(DateTime::from_timestamp(secs, nsecs), "invalid or out-of-range datetime"); + datetime.naive_utc() } /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. @@ -161,9 +162,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn from_timestamp_millis(millis: i64) -> Option { - let secs = millis.div_euclid(1000); - let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; - NaiveDateTime::from_timestamp_opt(secs, nsecs) + Some(try_opt!(DateTime::from_timestamp_millis(millis)).naive_utc()) } /// Creates a new [NaiveDateTime] from microseconds since the UNIX epoch. @@ -227,8 +226,7 @@ impl NaiveDateTime { pub const fn from_timestamp_nanos(nanos: i64) -> Option { let secs = nanos.div_euclid(NANOS_PER_SEC as i64); let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; - - NaiveDateTime::from_timestamp_opt(secs, nsecs) + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) } /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, @@ -264,18 +262,7 @@ impl NaiveDateTime { #[inline] #[must_use] pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { - let days = secs.div_euclid(86_400); - let secs = secs.rem_euclid(86_400); - if days < i32::MIN as i64 || days > i32::MAX as i64 { - return None; - } - let date = - NaiveDate::from_num_days_from_ce_opt(try_opt!((days as i32).checked_add(719_163))); - let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); - match (date, time) { - (Some(date), Some(time)) => Some(NaiveDateTime { date, time }), - (_, _) => None, - } + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) } /// Parses a string with the specified format string and returns a new `NaiveDateTime`. From 39f9a97b9b386109ae0921c6a8667476e0acc113 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 28 Feb 2024 20:57:22 +0100 Subject: [PATCH 769/999] Translate timestamp tests to `DateTime` --- src/datetime/tests.rs | 200 +++++++++++++++++++++++++++++++++--- src/naive/datetime/tests.rs | 185 --------------------------------- 2 files changed, 188 insertions(+), 197 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 425618de04..4adee3d20d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -123,6 +123,194 @@ impl TimeZone for DstTester { } } +#[test] +fn test_datetime_from_timestamp_millis() { + let valid_map = [ + (1662921288000, "2022-09-11 18:34:48.000000000"), + (1662921288123, "2022-09-11 18:34:48.123000000"), + (1662921287890, "2022-09-11 18:34:47.890000000"), + (-2208936075000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000, "1973-10-17 18:36:57.000000000"), + (1234567890000, "2009-02-13 23:31:30.000000000"), + (2034061609000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_millis, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis).unwrap(); + assert_eq!(timestamp_millis, datetime.timestamp_millis()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_millis in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis); + assert!(datetime.is_none()); + } + + // Test that the result of `from_timestamp_millis` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().cloned() { + assert_eq!(DateTime::from_timestamp_millis(secs * 1000), DateTime::from_timestamp(secs, 0)); + } +} + +#[test] +fn test_datetime_from_timestamp_micros() { + let valid_map = [ + (1662921288000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456, "2022-09-11 18:34:48.123456000"), + (1662921287890000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_micros, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros).unwrap(); + assert_eq!(timestamp_micros, datetime.timestamp_micros()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_micros in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros); + assert!(datetime.is_none()); + } + + // Test that the result of `TimeZone::timestamp_micros` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + DateTime::from_timestamp_micros(secs * 1_000_000), + DateTime::from_timestamp(secs, 0) + ); + } +} + +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_nanos(timestamp_nanos); + assert_eq!(timestamp_nanos, datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + + // Test that the result of `TimeZone::timestamp_nanos` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + Some(DateTime::from_timestamp_nanos(secs * 1_000_000_000)), + DateTime::from_timestamp(secs, 0) + ); + } +} + +#[test] +fn test_datetime_from_timestamp() { + let from_timestamp = |secs| DateTime::from_timestamp(secs, 0); + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().and_utc() + }; + assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(from_timestamp(i64::MIN), None); + assert_eq!(from_timestamp(i64::MAX), None); +} + +#[test] +fn test_datetime_timestamp() { + let to_timestamp = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_opt(h, n, s) + .unwrap() + .and_utc() + .timestamp() + }; + assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); + assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); + assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); +} + +#[test] +fn test_nanosecond_range() { + const A_BILLION: i64 = 1_000_000_000; + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Just beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::milliseconds(300); + assert!(beyond_max.timestamp_nanos_opt().is_none()); + + // Far beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::days(365); + assert!(beyond_max.timestamp_nanos_opt().is_none()); +} + #[test] fn test_datetime_add_days() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -1252,18 +1440,6 @@ fn test_datetime_from_local() { assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); } -#[test] -fn test_datetime_from_timestamp_millis() { - // 2000-01-12T01:02:03:004Z - let naive_dt = - NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_milli_opt(1, 2, 3, 4).unwrap(); - let datetime_utc = DateTime::::from_naive_utc_and_offset(naive_dt, Utc); - assert_eq!( - datetime_utc, - DateTime::::from_timestamp_millis(datetime_utc.timestamp_millis()).unwrap() - ); -} - #[test] #[cfg(feature = "clock")] fn test_datetime_before_windows_api_limits() { diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 4d36208c39..751ff7e696 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,147 +1,6 @@ use super::NaiveDateTime; use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc}; -#[test] -fn test_datetime_from_timestamp_millis() { - let valid_map = [ - (1662921288000, "2022-09-11 18:34:48.000000000"), - (1662921288123, "2022-09-11 18:34:48.123000000"), - (1662921287890, "2022-09-11 18:34:47.890000000"), - (-2208936075000, "1900-01-01 14:38:45.000000000"), - (0, "1970-01-01 00:00:00.000000000"), - (119731017000, "1973-10-17 18:36:57.000000000"), - (1234567890000, "2009-02-13 23:31:30.000000000"), - (2034061609000, "2034-06-16 09:06:49.000000000"), - ]; - - for (timestamp_millis, _formatted) in valid_map.iter().copied() { - let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); - assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - #[cfg(feature = "alloc")] - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); - } - - let invalid = [i64::MAX, i64::MIN]; - - for timestamp_millis in invalid.iter().copied() { - let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); - assert!(naive_datetime.is_none()); - } - - // Test that the result of `from_timestamp_millis` compares equal to - // that of `from_timestamp_opt`. - let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; - for secs in secs_test.iter().cloned() { - assert_eq!( - NaiveDateTime::from_timestamp_millis(secs * 1000), - NaiveDateTime::from_timestamp_opt(secs, 0) - ); - } -} - -#[test] -fn test_datetime_from_timestamp_micros() { - let valid_map = [ - (1662921288000000, "2022-09-11 18:34:48.000000000"), - (1662921288123456, "2022-09-11 18:34:48.123456000"), - (1662921287890000, "2022-09-11 18:34:47.890000000"), - (-2208936075000000, "1900-01-01 14:38:45.000000000"), - (0, "1970-01-01 00:00:00.000000000"), - (119731017000000, "1973-10-17 18:36:57.000000000"), - (1234567890000000, "2009-02-13 23:31:30.000000000"), - (2034061609000000, "2034-06-16 09:06:49.000000000"), - ]; - - for (timestamp_micros, _formatted) in valid_map.iter().copied() { - let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); - assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - #[cfg(feature = "alloc")] - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); - } - - let invalid = [i64::MAX, i64::MIN]; - - for timestamp_micros in invalid.iter().copied() { - let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); - assert!(naive_datetime.is_none()); - } - - // Test that the result of `from_timestamp_micros` compares equal to - // that of `from_timestamp_opt`. - let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; - for secs in secs_test.iter().copied() { - assert_eq!( - NaiveDateTime::from_timestamp_micros(secs * 1_000_000), - NaiveDateTime::from_timestamp_opt(secs, 0) - ); - } -} - -#[test] -fn test_datetime_from_timestamp_nanos() { - let valid_map = [ - (1662921288000000000, "2022-09-11 18:34:48.000000000"), - (1662921288123456000, "2022-09-11 18:34:48.123456000"), - (1662921288123456789, "2022-09-11 18:34:48.123456789"), - (1662921287890000000, "2022-09-11 18:34:47.890000000"), - (-2208936075000000000, "1900-01-01 14:38:45.000000000"), - (-5337182663000000000, "1800-11-15 01:15:37.000000000"), - (0, "1970-01-01 00:00:00.000000000"), - (119731017000000000, "1973-10-17 18:36:57.000000000"), - (1234567890000000000, "2009-02-13 23:31:30.000000000"), - (2034061609000000000, "2034-06-16 09:06:49.000000000"), - ]; - - for (timestamp_nanos, _formatted) in valid_map.iter().copied() { - let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos).unwrap(); - assert_eq!(timestamp_nanos, naive_datetime.timestamp_nanos_opt().unwrap()); - #[cfg(feature = "alloc")] - assert_eq!(naive_datetime.format("%F %T%.9f").to_string(), _formatted); - } - - const A_BILLION: i64 = 1_000_000_000; - // Maximum datetime in nanoseconds - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos_opt().unwrap(); - assert_eq!( - NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), - NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() - ); - // Minimum datetime in nanoseconds - let minimum = "1677-09-21T00:12:44.000000000"; - let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos_opt().unwrap(); - assert_eq!( - NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), - NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() - ); - - // Test that the result of `from_timestamp_nanos` compares equal to - // that of `from_timestamp_opt`. - let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; - for secs in secs_test.iter().copied() { - assert_eq!( - NaiveDateTime::from_timestamp_nanos(secs * 1_000_000_000), - NaiveDateTime::from_timestamp_opt(secs, 0) - ); - } -} - -#[test] -fn test_datetime_from_timestamp() { - let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); - let ymdhms = - |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); - assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); - assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); - assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); - assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); - assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); - assert_eq!(from_timestamp(i64::MIN), None); - assert_eq!(from_timestamp(i64::MAX), None); -} - #[test] fn test_datetime_add() { fn check( @@ -252,18 +111,6 @@ fn test_core_duration_max() { utc_dt += Duration::MAX; } -#[test] -fn test_datetime_timestamp() { - let to_timestamp = |y, m, d, h, n, s| { - NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().timestamp() - }; - assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); - assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); - assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); -} - #[test] fn test_datetime_from_str() { // valid cases @@ -423,38 +270,6 @@ fn test_datetime_add_sub_invariant() { assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); } -#[test] -fn test_nanosecond_range() { - const A_BILLION: i64 = 1_000_000_000; - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos_opt().unwrap(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() - ); - - let minimum = "1677-09-21T00:12:44.000000000"; - let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos_opt().unwrap(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() - ); - - // Just beyond range - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + TimeDelta::milliseconds(300); - assert!(beyond_max.timestamp_nanos_opt().is_none()); - - // Far beyond range - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let beyond_max = parsed + TimeDelta::days(365); - assert!(beyond_max.timestamp_nanos_opt().is_none()); -} - #[test] fn test_and_local_timezone() { let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); From 392e0dd460d1712d01718db09feaba0e74fb9e1e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 29 Feb 2024 07:22:38 +0100 Subject: [PATCH 770/999] Don't use timestamp methods from `NaiveDateTime` --- src/datetime/tests.rs | 2 +- src/format/formatting.rs | 8 ++++---- src/format/parsed.rs | 9 ++++----- src/offset/local/unix.rs | 4 ++-- src/offset/mod.rs | 19 +++++++------------ src/offset/utc.rs | 8 +++----- src/round.rs | 36 ++++++++++++++++++++++-------------- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 4adee3d20d..f53b3c953a 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1103,7 +1103,7 @@ fn test_parse_from_str() { .is_err()); assert_eq!( DateTime::parse_from_str("0", "%s").unwrap(), - NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() + DateTime::from_timestamp(0, 0).unwrap().fixed_offset() ); assert_eq!( diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 9b055289ce..f3448f94fe 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -231,10 +231,10 @@ fn format_inner( Timestamp => ( 1, match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some(d.and_time(*t).timestamp() - i64::from(off.local_minus_utc())) - } + (Some(d), Some(t), None) => Some(d.and_time(*t).and_utc().timestamp()), + (Some(d), Some(t), Some(&(_, off))) => Some( + d.and_time(*t).and_utc().timestamp() - i64::from(off.local_minus_utc()), + ), (_, _, _) => None, }, ), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 0f5005129d..be8db51788 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -858,7 +858,7 @@ impl Parsed { // verify the timestamp field if any // the following is safe, `timestamp` is very limited in range - let timestamp = datetime.timestamp() - i64::from(offset); + let timestamp = datetime.and_utc().timestamp() - i64::from(offset); if let Some(given_timestamp) = self.timestamp { // if `datetime` represents a leap second, it might be off by one second. if given_timestamp != timestamp @@ -883,8 +883,7 @@ impl Parsed { // reconstruct date and time fields from timestamp let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; - let datetime = NaiveDateTime::from_timestamp_opt(ts, 0); - let mut datetime = datetime.ok_or(OUT_OF_RANGE)?; + let mut datetime = DateTime::from_timestamp(ts, 0).ok_or(OUT_OF_RANGE)?.naive_utc(); // fill year, ordinal, hour, minute and second fields from timestamp. // if existing fields are consistent, this will allow the full date/time reconstruction. @@ -1000,8 +999,8 @@ impl Parsed { // make a naive `DateTime` from given timestamp and (if any) nanosecond. // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. let nanosecond = self.nanosecond.unwrap_or(0); - let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond); - let dt = dt.ok_or(OUT_OF_RANGE)?; + let dt = + DateTime::from_timestamp(timestamp, nanosecond).ok_or(OUT_OF_RANGE)?.naive_utc(); guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index ce96a6e3bb..0b5bfefe70 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -151,7 +151,7 @@ impl Cache { if !local { let offset = self .zone - .find_local_time_type(d.timestamp()) + .find_local_time_type(d.and_utc().timestamp()) .expect("unable to select local time type") .offset(); @@ -164,7 +164,7 @@ impl Cache { // we pass through the year as the year of a local point in time must either be valid in that locale, or // the entire time was skipped in which case we will return LocalResult::None anyway. self.zone - .find_local_time_type_from_local(d.timestamp(), d.year()) + .find_local_time_type_from_local(d.and_utc().timestamp(), d.year()) .expect("unable to select local time type") .map(|o| FixedOffset::east_opt(o.offset()).unwrap()) } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 26566ad020..005c6bdd9a 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -380,8 +380,8 @@ pub trait TimeZone: Sized + Clone { /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); /// ``` fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { - match NaiveDateTime::from_timestamp_opt(secs, nsecs) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), + match DateTime::from_timestamp(secs, nsecs) { + Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), None => LocalResult::None, } } @@ -414,8 +414,8 @@ pub trait TimeZone: Sized + Clone { /// }; /// ``` fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { - match NaiveDateTime::from_timestamp_millis(millis) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), + match DateTime::from_timestamp_millis(millis) { + Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), None => LocalResult::None, } } @@ -433,12 +433,7 @@ pub trait TimeZone: Sized + Clone { /// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648); /// ``` fn timestamp_nanos(&self, nanos: i64) -> DateTime { - let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000); - if nanos < 0 { - secs -= 1; - nanos += 1_000_000_000; - } - self.timestamp_opt(secs, nanos as u32).unwrap() + self.from_utc_datetime(&DateTime::from_timestamp_nanos(nanos).naive_utc()) } /// Makes a new `DateTime` from the number of non-leap microseconds @@ -452,8 +447,8 @@ pub trait TimeZone: Sized + Clone { /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); /// ``` fn timestamp_micros(&self, micros: i64) -> LocalResult> { - match NaiveDateTime::from_timestamp_micros(micros) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), + match DateTime::from_timestamp_micros(micros) { + Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), None => LocalResult::None, } } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index be88e37eaf..5bfbe69e9c 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -33,9 +33,9 @@ use crate::{Date, DateTime}; /// # Example /// /// ``` -/// use chrono::{NaiveDateTime, TimeZone, Utc}; +/// use chrono::{DateTime, TimeZone, Utc}; /// -/// let dt = Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_opt(61, 0).unwrap()); +/// let dt = DateTime::from_timestamp(61, 0).unwrap(); /// /// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); @@ -95,9 +95,7 @@ impl Utc { pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - let naive = - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap(); - Utc.from_utc_datetime(&naive) + DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()).unwrap() } /// Returns a `DateTime` which corresponds to the current date and time. diff --git a/src/round.rs b/src/round.rs index c70eea6a00..ef3e39d2ba 100644 --- a/src/round.rs +++ b/src/round.rs @@ -196,7 +196,8 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; if span == 0 { return Ok(original); } @@ -232,7 +233,8 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), @@ -312,7 +314,7 @@ mod tests { use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Timelike; - use crate::{NaiveDate, NaiveDateTime}; + use crate::{DateTime, NaiveDate}; #[test] fn test_round_subsecs() { @@ -768,15 +770,15 @@ mod tests { #[test] fn issue1010() { - let dt = NaiveDateTime::from_timestamp_opt(-4_227_854_320, 678_774_288).unwrap(); + let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap(); let span = TimeDelta::microseconds(-7_019_067_213_869_040); assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(320_041_586, 920_103_021).unwrap(); + let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap(); let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(-2_621_440, 0).unwrap(); + let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap(); let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } @@ -807,16 +809,22 @@ mod tests { fn test_duration_round_close_to_min_max() { let span = TimeDelta::nanoseconds(i64::MAX); - let dt = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 - 1).unwrap(); - assert_eq!(dt.duration_round(span).unwrap().to_string(), "1677-09-21 00:12:43.145224193"); + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "1677-09-21 00:12:43.145224193 UTC" + ); - let dt = NaiveDateTime::from_timestamp_nanos(i64::MIN / 2 + 1).unwrap(); - assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); - let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 + 1).unwrap(); - assert_eq!(dt.duration_round(span).unwrap().to_string(), "2262-04-11 23:47:16.854775807"); + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); - let dt = NaiveDateTime::from_timestamp_nanos(i64::MAX / 2 - 1).unwrap(); - assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); } } From 585d48be38805b139187ee9247e8e2684a3fd913 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 29 Feb 2024 20:52:16 +0100 Subject: [PATCH 771/999] Don't use timestamp methods from `NaiveDateTime` in `serde` module --- src/naive/datetime/serde.rs | 126 +++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 53 deletions(-) diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index da4f416017..de8abd7dc5 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -83,7 +83,7 @@ pub mod ts_nanoseconds { use serde::{de, ser}; use crate::serde::invalid_ts; - use crate::NaiveDateTime; + use crate::{DateTime, NaiveDateTime}; /// Serialize a datetime into an integer number of nanoseconds since the epoch /// @@ -123,7 +123,7 @@ pub mod ts_nanoseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + serializer.serialize_i64(dt.and_utc().timestamp_nanos_opt().ok_or(ser::Error::custom( "value out of range for a timestamp with nanosecond precision", ))?) } @@ -135,7 +135,7 @@ pub mod ts_nanoseconds { /// # Example: /// /// ```rust - /// # use chrono::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; /// #[derive(Debug, PartialEq, Deserialize)] @@ -145,10 +145,12 @@ pub mod ts_nanoseconds { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() }); + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999).unwrap() }); + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -171,10 +173,11 @@ pub mod ts_nanoseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt( + DateTime::from_timestamp( value.div_euclid(1_000_000_000), (value.rem_euclid(1_000_000_000)) as u32, ) + .map(|dt| dt.naive_utc()) .ok_or_else(|| invalid_ts(value)) } @@ -182,11 +185,9 @@ pub mod ts_nanoseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt( - (value / 1_000_000_000) as i64, - (value % 1_000_000_000) as u32, - ) - .ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } } } @@ -269,7 +270,7 @@ pub mod ts_nanoseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_nanos_opt().ok_or( ser::Error::custom("value out of range for a timestamp with nanosecond precision"), )?), None => serializer.serialize_none(), @@ -283,7 +284,7 @@ pub mod ts_nanoseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] @@ -293,10 +294,12 @@ pub mod ts_nanoseconds_option { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) }); + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_999) }); + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -372,7 +375,7 @@ pub mod ts_microseconds { use serde::{de, ser}; use crate::serde::invalid_ts; - use crate::NaiveDateTime; + use crate::{DateTime, NaiveDateTime}; /// Serialize a datetime into an integer number of microseconds since the epoch /// @@ -404,7 +407,7 @@ pub mod ts_microseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_micros()) + serializer.serialize_i64(dt.and_utc().timestamp_micros()) } /// Deserialize a `NaiveDateTime` from a microseconds timestamp @@ -414,7 +417,7 @@ pub mod ts_microseconds { /// # Example: /// /// ```rust - /// # use chrono::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; /// #[derive(Debug, PartialEq, Deserialize)] @@ -424,10 +427,12 @@ pub mod ts_microseconds { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() }); + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000).unwrap() }); + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -450,17 +455,20 @@ pub mod ts_microseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp_micros(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { - NaiveDateTime::from_timestamp_opt( + DateTime::from_timestamp( (value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32, ) + .map(|dt| dt.naive_utc()) .ok_or_else(|| invalid_ts(value)) } } @@ -536,7 +544,7 @@ pub mod ts_microseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_micros()), None => serializer.serialize_none(), } } @@ -548,7 +556,7 @@ pub mod ts_microseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] @@ -558,10 +566,12 @@ pub mod ts_microseconds_option { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) }); + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_999_000) }); + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -635,7 +645,7 @@ pub mod ts_milliseconds { use serde::{de, ser}; use crate::serde::invalid_ts; - use crate::NaiveDateTime; + use crate::{DateTime, NaiveDateTime}; /// Serialize a datetime into an integer number of milliseconds since the epoch /// @@ -667,7 +677,7 @@ pub mod ts_milliseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_millis()) + serializer.serialize_i64(dt.and_utc().timestamp_millis()) } /// Deserialize a `NaiveDateTime` from a milliseconds timestamp @@ -677,7 +687,7 @@ pub mod ts_milliseconds { /// # Example: /// /// ```rust - /// # use chrono::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; /// #[derive(Debug, PartialEq, Deserialize)] @@ -687,10 +697,12 @@ pub mod ts_milliseconds { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() }); + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000).unwrap() }); + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -713,18 +725,18 @@ pub mod ts_milliseconds { where E: de::Error, { - NaiveDateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp_millis(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { - NaiveDateTime::from_timestamp_opt( - (value / 1000) as i64, - ((value % 1000) * 1_000_000) as u32, - ) - .ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } } } @@ -796,7 +808,7 @@ pub mod ts_milliseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_millis()), None => serializer.serialize_none(), } } @@ -808,7 +820,7 @@ pub mod ts_milliseconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] @@ -818,10 +830,12 @@ pub mod ts_milliseconds_option { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) }); + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(-1, 999_000_000) }); + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> @@ -894,7 +908,7 @@ pub mod ts_seconds { use serde::{de, ser}; use crate::serde::invalid_ts; - use crate::NaiveDateTime; + use crate::{DateTime, NaiveDateTime}; /// Serialize a datetime into an integer number of seconds since the epoch /// @@ -922,7 +936,7 @@ pub mod ts_seconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp()) + serializer.serialize_i64(dt.and_utc().timestamp()) } /// Deserialize a `NaiveDateTime` from a seconds timestamp @@ -932,7 +946,7 @@ pub mod ts_seconds { /// # Example: /// /// ```rust - /// # use chrono::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; /// #[derive(Debug, PartialEq, Deserialize)] @@ -942,7 +956,8 @@ pub mod ts_seconds { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0).unwrap() }); + /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result @@ -965,7 +980,9 @@ pub mod ts_seconds { where E: de::Error, { - NaiveDateTime::from_timestamp_opt(value, 0).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp(value, 0) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } fn visit_u64(self, value: u64) -> Result @@ -975,7 +992,9 @@ pub mod ts_seconds { if value > i64::MAX as u64 { Err(invalid_ts(value)) } else { - NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp(value as i64, 0) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) } } } @@ -1029,9 +1048,9 @@ pub mod ts_seconds_option { /// time: Option, /// } /// - /// let my_s = S { - /// time: Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()), - /// }; + /// let expected = + /// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap(); + /// let my_s = S { time: Some(expected) }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); /// # Ok::<(), serde_json::Error>(()) @@ -1041,7 +1060,7 @@ pub mod ts_seconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp()), + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp()), None => serializer.serialize_none(), } } @@ -1053,7 +1072,7 @@ pub mod ts_seconds_option { /// # Example: /// /// ```rust - /// # use chrono::naive::NaiveDateTime; + /// # use chrono::{DateTime, NaiveDateTime}; /// # use serde_derive::Deserialize; /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; /// #[derive(Debug, PartialEq, Deserialize)] @@ -1063,7 +1082,8 @@ pub mod ts_seconds_option { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0) }); + /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); /// # Ok::<(), serde_json::Error>(()) /// ``` pub fn deserialize<'de, D>(d: D) -> Result, D::Error> From 83573a8b30f29f9ae8a4fa9b4568ab1eb6e61b0d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 28 Feb 2024 18:58:45 +0100 Subject: [PATCH 772/999] Deprecate timestamp methods on `NaiveDateTime` --- src/naive/datetime/mod.rs | 203 +++----------------------------------- 1 file changed, 13 insertions(+), 190 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 78cf91a4d4..cf31bfb4f4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -126,7 +126,7 @@ impl NaiveDateTime { /// 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")] + #[deprecated(since = "0.4.23", note = "use `DateTime::from_timestamp` instead")] #[inline] #[must_use] pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { @@ -143,22 +143,7 @@ impl NaiveDateTime { /// /// 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 - /// - /// ``` - /// use chrono::NaiveDateTime; - /// let timestamp_millis: i64 = 1662921288000; //Sunday, September 11, 2022 6:34:48 PM - /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - /// - /// // Negative timestamps (before the UNIX epoch) are supported as well. - /// let timestamp_millis: i64 = -2208936075000; //Mon Jan 01 1900 14:38:45 GMT+0000 - /// let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - /// ``` + #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_millis` instead")] #[inline] #[must_use] pub const fn from_timestamp_millis(millis: i64) -> Option { @@ -173,28 +158,13 @@ impl NaiveDateTime { /// /// 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 - /// - /// ``` - /// use chrono::NaiveDateTime; - /// let timestamp_micros: i64 = 1662921288000000; //Sunday, September 11, 2022 6:34:48 PM - /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - /// - /// // Negative timestamps (before the UNIX epoch) are supported as well. - /// let timestamp_micros: i64 = -2208936075000000; //Mon Jan 01 1900 14:38:45 GMT+0000 - /// let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - /// ``` + #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_micros` instead")] #[inline] #[must_use] pub const fn from_timestamp_micros(micros: i64) -> Option { let secs = micros.div_euclid(1_000_000); let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; - NaiveDateTime::from_timestamp_opt(secs, nsecs) + Some(try_opt!(DateTime::::from_timestamp(secs, nsecs)).naive_utc()) } /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. @@ -205,22 +175,7 @@ impl NaiveDateTime { /// /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` /// (more than ca. 262,000 years away from common era) - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDateTime; - /// let timestamp_nanos: i64 = 1662921288_000_000_000; //Sunday, September 11, 2022 6:34:48 PM - /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); - /// - /// // Negative timestamps (before the UNIX epoch) are supported as well. - /// let timestamp_nanos: i64 = -2208936075_000_000_000; //Mon Jan 01 1900 14:38:45 GMT+0000 - /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); - /// assert!(naive_datetime.is_some()); - /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); - /// ``` + #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_nanos` instead")] #[inline] #[must_use] pub const fn from_timestamp_nanos(nanos: i64) -> Option { @@ -243,22 +198,7 @@ impl NaiveDateTime { /// 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 - /// - /// ``` - /// use chrono::NaiveDateTime; - /// use std::i64; - /// - /// let from_timestamp_opt = NaiveDateTime::from_timestamp_opt; - /// - /// assert!(from_timestamp_opt(0, 0).is_some()); - /// assert!(from_timestamp_opt(0, 999_999_999).is_some()); - /// assert!(from_timestamp_opt(0, 1_500_000_000).is_none()); // invalid leap second - /// assert!(from_timestamp_opt(59, 1_500_000_000).is_some()); // leap second - /// assert!(from_timestamp_opt(59, 2_000_000_000).is_none()); - /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); - /// ``` + #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp` instead")] #[inline] #[must_use] pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { @@ -420,24 +360,7 @@ 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. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 980).unwrap(); - /// assert_eq!(dt.timestamp(), 1); - /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap(); - /// assert_eq!(dt.timestamp(), 1_000_000_000); - /// - /// let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(); - /// assert_eq!(dt.timestamp(), -1); - /// - /// let dt = NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); - /// assert_eq!(dt.timestamp(), -62198755200); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp()` instead")] #[inline] #[must_use] pub const fn timestamp(&self) -> i64 { @@ -448,23 +371,7 @@ 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. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_milli_opt(0, 0, 1, 444).unwrap(); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = - /// NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_milli_opt(1, 46, 40, 555).unwrap(); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// - /// let dt = - /// NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_milli_opt(23, 59, 59, 100).unwrap(); - /// assert_eq!(dt.timestamp_millis(), -900); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_millis()` instead")] #[inline] #[must_use] pub const fn timestamp_millis(&self) -> i64 { @@ -475,19 +382,7 @@ 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. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_micro_opt(0, 0, 1, 444).unwrap(); - /// assert_eq!(dt.timestamp_micros(), 1_000_444); - /// - /// let dt = - /// NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_micro_opt(1, 46, 40, 555).unwrap(); - /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_micros()` instead")] #[inline] #[must_use] pub const fn timestamp_micros(&self) -> i64 { @@ -506,7 +401,7 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 /// and 2262-04-11T23:47:16.854775807. - #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] + #[deprecated(since = "0.4.31", note = "use `.and_utc().timestamp_nanos_opt()` instead")] #[inline] #[must_use] #[allow(deprecated)] @@ -526,25 +421,7 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 /// and 2262-04-11T23:47:16.854775807. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, NaiveDateTime}; - /// - /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); - /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); - /// - /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); - /// - /// const A_BILLION: i64 = 1_000_000_000; - /// let nanos = dt.timestamp_nanos_opt().unwrap(); - /// assert_eq!(nanos, 1_000_000_000_000_000_555); - /// assert_eq!( - /// Some(dt), - /// NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32) - /// ); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub const fn timestamp_nanos_opt(&self) -> Option { @@ -555,24 +432,7 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) - /// .unwrap() - /// .and_hms_nano_opt(9, 10, 11, 123_456_789) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_millis(), 123); - /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) - /// .unwrap() - /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_subsec_millis()` instead")] #[inline] #[must_use] pub const fn timestamp_subsec_millis(&self) -> u32 { @@ -583,24 +443,7 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999,999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) - /// .unwrap() - /// .and_hms_nano_opt(9, 10, 11, 123_456_789) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_micros(), 123_456); - /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) - /// .unwrap() - /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); - /// ``` + #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_subsec_micros()` instead")] #[inline] #[must_use] pub const fn timestamp_subsec_micros(&self) -> u32 { @@ -611,26 +454,6 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999,999,999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. - /// - /// # Example - /// - /// ``` - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8) - /// .unwrap() - /// .and_hms_nano_opt(9, 10, 11, 123_456_789) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789); - /// - /// let dt = NaiveDate::from_ymd_opt(2015, 7, 1) - /// .unwrap() - /// .and_hms_nano_opt(8, 59, 59, 1_234_567_890) - /// .unwrap(); - /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); - /// ``` - #[inline] - #[must_use] pub const fn timestamp_subsec_nanos(&self) -> u32 { self.and_utc().timestamp_subsec_nanos() } From 1d3da21ae24224e627ec7640b65694ba9b6393b5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 29 Feb 2024 21:23:57 +0100 Subject: [PATCH 773/999] Add comment to calculate `UNIX_EPOCH_DAY` --- src/datetime/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e7d6a06c71..2aa762fee2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1903,6 +1903,14 @@ where } } +/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0. +/// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388 +/// 1 day until January 1, 1601 1 +/// 369 years until Januari 1, 1970 369 * 365 = 134685 +/// of which floor(369 / 4) are leap years floor(369 / 4) = 92 +/// except for 1700, 1800 and 1900 -3 + +/// -------- +/// 719163 const UNIX_EPOCH_DAY: i64 = 719_163; #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] From 0aa46ddbf021df6aae12da729e0df6f486947768 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 1 Mar 2024 10:01:11 +0100 Subject: [PATCH 774/999] Sync `Datelike` docs --- src/datetime/mod.rs | 15 +++++--- src/naive/date/mod.rs | 81 +++++++++++++++++++++++++++++---------- src/naive/datetime/mod.rs | 42 ++++++++++++-------- 3 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2aa762fee2..bb157f836b 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1193,6 +1193,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. /// - The resulting UTC datetime would be out of range. @@ -1206,12 +1207,14 @@ impl Datelike for DateTime { /// Makes a new `DateTime` with the month number (starting from 1) changed. /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// /// See also the [`NaiveDate::with_month`] method. /// /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). /// - 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. @@ -1227,7 +1230,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). /// - 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. @@ -1243,7 +1246,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (for example `day(31)` in April). /// - 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. @@ -1259,7 +1262,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (for example `day(30)` in April). /// - 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. @@ -1275,7 +1278,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). /// - 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. @@ -1291,7 +1294,7 @@ impl Datelike for DateTime { /// # Errors /// /// Returns `None` if: - /// - The resulting date does not exist. + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). /// - 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. diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index a3fd2ca22e..d0ac747a6b 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1631,12 +1631,17 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or when the `NaiveDate` would be - /// out of range. + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. /// - /// # Example + /// # Examples /// /// ``` /// use chrono::{Datelike, NaiveDate}; @@ -1651,13 +1656,23 @@ impl Datelike for NaiveDate { /// ); /// ``` /// - /// A leap day (February 29) is a good example that this method can return `None`. + /// A leap day (February 29) is a case where this method can return `None`. /// /// ``` /// # use chrono::{NaiveDate, Datelike}; /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2015).is_none()); /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2020).is_some()); /// ``` + /// + /// Don't use `with_year` if you want the ordinal date to stay the same: + /// + /// ``` + /// # use chrono::{Datelike, NaiveDate}; + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` #[inline] fn with_year(&self, year: i32) -> Option { // we need to operate with `mdf` since we should keep the month and day number as is @@ -1674,9 +1689,11 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. /// - /// # Example + /// # Examples /// /// ``` /// use chrono::{Datelike, NaiveDate}; @@ -1685,9 +1702,27 @@ impl Datelike for NaiveDate { /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) /// ); - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); - /// // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // No month 13 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); // No Feb 30 + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); /// ``` #[inline] fn with_month(&self, month: u32) -> Option { @@ -1698,8 +1733,9 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `month0` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. /// /// # Example /// @@ -1710,9 +1746,8 @@ impl Datelike for NaiveDate { /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) /// ); - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); - /// // no February 30 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // No month 12 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // No Feb 30 /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { @@ -1724,7 +1759,9 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. /// /// # Example /// @@ -1747,7 +1784,9 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. /// /// # Example /// @@ -1771,8 +1810,9 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. /// /// # Example /// @@ -1805,8 +1845,9 @@ impl Datelike for NaiveDate { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. /// /// # Example /// diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index cf31bfb4f4..181576d76c 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1142,8 +1142,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or when the `NaiveDateTime` would be - /// out of range. + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. /// /// # Example /// @@ -1168,11 +1169,15 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// /// 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. + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. /// /// # Example /// @@ -1185,8 +1190,8 @@ impl Datelike for NaiveDateTime { /// dt.with_month(10), /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) /// ); - /// assert_eq!(dt.with_month(13), None); // no month 13 - /// assert_eq!(dt.with_month(2), None); // no February 30 + /// assert_eq!(dt.with_month(13), None); // No month 13 + /// assert_eq!(dt.with_month(2), None); // No February 30 /// ``` #[inline] fn with_month(&self, month: u32) -> Option { @@ -1199,8 +1204,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `month0` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. /// /// # Example /// @@ -1213,8 +1219,8 @@ impl Datelike for NaiveDateTime { /// dt.with_month0(9), /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) /// ); - /// assert_eq!(dt.with_month0(12), None); // no month 13 - /// assert_eq!(dt.with_month0(1), None); // no February 30 + /// assert_eq!(dt.with_month0(12), None); // No month 13 + /// assert_eq!(dt.with_month0(1), None); // No February 30 /// ``` #[inline] fn with_month0(&self, month0: u32) -> Option { @@ -1227,7 +1233,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. /// /// # Example /// @@ -1253,7 +1261,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid. + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. /// /// # Example /// @@ -1279,8 +1289,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. /// /// # Example /// @@ -1317,8 +1328,9 @@ impl Datelike for NaiveDateTime { /// /// # Errors /// - /// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is - /// invalid. + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. /// /// # Example /// From f4adc28dce9a139ef17c7044e2adafde029cb17d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:44:59 +0000 Subject: [PATCH 775/999] Update windows-bindgen requirement from 0.53 to 0.54 Updates the requirements on [windows-bindgen](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.53.0...0.54.0) --- updated-dependencies: - dependency-name: windows-bindgen dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6269903203..1a3aef330b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.53" } # The MSRV of its windows-metatada 0.53 dependency is 1.70 +windows-bindgen = { version = "0.54" } # The MSRV of its windows-metatada 0.54 dependency is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 9bb8ae6365..39060adadd 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,4 +1,4 @@ -// Bindings generated by `windows-bindgen` 0.53.0 +// Bindings generated by `windows-bindgen` 0.54.0 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] ::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); From 40724008b22b393676bf7e3b7b37143ae07d1a55 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 15:11:23 +0100 Subject: [PATCH 776/999] Add range checks to the `Parsed::set_*` methods --- src/format/parsed.rs | 218 ++++++++++++++++++++++++++++++++----------- 1 file changed, 165 insertions(+), 53 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index be8db51788..5c25b92dcd 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -255,10 +255,10 @@ impl Parsed { /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_div_100, value as i32) } /// Set the 'year modulo 100' field to the given value. @@ -271,16 +271,15 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. - /// Currently only checks the value is positive and not out of range for an `i32`. + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_mod_100, value as i32) } /// Set the 'year' field that is part of an [ISO 8601 week date] to the given value. @@ -311,13 +310,10 @@ impl Parsed { /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent( - &mut self.isoyear_div_100, - i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, - ) + set_if_consistent(&mut self.isoyear_div_100, value as i32) } /// Set the 'year modulo 100' that is part of an [ISO 8601 week date] field to the given value. @@ -332,32 +328,30 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. - /// Currently only checks the value is positive and not out of range for an `i32`. + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent( - &mut self.isoyear_mod_100, - i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, - ) + set_if_consistent(&mut self.isoyear_mod_100, value as i32) } /// Set the 'month' field to the given value. /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 1-12. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_month(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(1..=12).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.month, value as u32) } /// Set the 'week number starting with Sunday' field to the given value. @@ -366,13 +360,15 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_sun, value as u32) } /// Set the 'week number starting with Monday' field to the given value. @@ -381,13 +377,15 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 0-53. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_mon, value as u32) } /// Set the '[ISO 8601 week number]' field to the given value. @@ -396,20 +394,22 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 1-53. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-53. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(1..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.isoweek, value as u32) } /// Set the 'day of the week' field to the given value. /// /// # Errors /// - /// May return `IMPOSSIBLE` if this field was already set to a different value. + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { set_if_consistent(&mut self.weekday, value) @@ -419,26 +419,30 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 1-366. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-366. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(1..=366).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.ordinal, value as u32) } /// Set the 'day of the month' field to the given value. /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 1-31. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-31. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_day(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(1..=31).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.day, value as u32) } /// Set the 'am/pm' field to the given value. @@ -450,7 +454,7 @@ impl Parsed { /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { - set_if_consistent(&mut self.hour_div_12, u32::from(value)) + set_if_consistent(&mut self.hour_div_12, value as u32) } /// Set the 'hour number in 12-hour clocks' field to the given value. @@ -464,11 +468,14 @@ impl Parsed { /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] - pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { + pub fn set_hour12(&mut self, mut value: i64) -> ParseResult<()> { if !(1..=12).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) + if value == 12 { + value = 0 + } + set_if_consistent(&mut self.hour_mod_12, value as u32) } /// Set the 'hour' field to the given value. @@ -483,23 +490,28 @@ impl Parsed { /// Returns `IMPOSSIBLE` one of the fields was already set to a different value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { - let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?; - set_if_consistent(&mut self.hour_div_12, v / 12)?; - set_if_consistent(&mut self.hour_mod_12, v % 12)?; - Ok(()) + let (hour_div_12, hour_mod_12) = match value { + hour @ 0..=11 => (0, hour as u32), + hour @ 12..=23 => (1, hour as u32 - 12), + _ => return Err(OUT_OF_RANGE), + }; + set_if_consistent(&mut self.hour_div_12, hour_div_12)?; + set_if_consistent(&mut self.hour_mod_12, hour_mod_12) } /// Set the 'minute' field to the given value. /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 0-59. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-59. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(0..=59).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.minute, value as u32) } /// Set the 'second' field to the given value. @@ -508,13 +520,15 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 0-60. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-60. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_second(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(0..=60).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.second, value as u32) } /// Set the 'nanosecond' field to the given value. @@ -523,13 +537,15 @@ impl Parsed { /// /// # Errors /// - /// May return `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. - /// Currently only checks the value is not out of range for a `u32`. + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) + if !(0..=999_999_999).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.nanosecond, value as u32) } /// Set the 'timestamp' field to the given value. @@ -1118,6 +1134,102 @@ mod tests { assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); } + #[test] + fn test_parsed_set_range() { + assert_eq!(Parsed::new().set_year(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_year(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_div_100(0).is_ok()); + assert!(Parsed::new().set_year_div_100(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_mod_100(0).is_ok()); + assert!(Parsed::new().set_year_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_year_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_isoyear(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_isoyear(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_div_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_div_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_mod_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_month(1).is_ok()); + assert!(Parsed::new().set_month(12).is_ok()); + assert_eq!(Parsed::new().set_month(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_sun(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_sun(0).is_ok()); + assert!(Parsed::new().set_week_from_sun(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_sun(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_mon(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_mon(0).is_ok()); + assert!(Parsed::new().set_week_from_mon(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_mon(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoweek(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoweek(1).is_ok()); + assert!(Parsed::new().set_isoweek(53).is_ok()); + assert_eq!(Parsed::new().set_isoweek(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_ordinal(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_ordinal(1).is_ok()); + assert!(Parsed::new().set_ordinal(366).is_ok()); + assert_eq!(Parsed::new().set_ordinal(367), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_day(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_day(1).is_ok()); + assert!(Parsed::new().set_day(31).is_ok()); + assert_eq!(Parsed::new().set_day(32), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour12(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour12(1).is_ok()); + assert!(Parsed::new().set_hour12(12).is_ok()); + assert_eq!(Parsed::new().set_hour12(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour(0).is_ok()); + assert!(Parsed::new().set_hour(23).is_ok()); + assert_eq!(Parsed::new().set_hour(24), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_minute(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_minute(0).is_ok()); + assert!(Parsed::new().set_minute(59).is_ok()); + assert_eq!(Parsed::new().set_minute(60), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_second(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_second(0).is_ok()); + assert!(Parsed::new().set_second(60).is_ok()); + assert_eq!(Parsed::new().set_second(61), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_nanosecond(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_nanosecond(0).is_ok()); + assert!(Parsed::new().set_nanosecond(999_999_999).is_ok()); + assert_eq!(Parsed::new().set_nanosecond(1_000_000_000), Err(OUT_OF_RANGE)); + + assert!(Parsed::new().set_timestamp(i64::MIN).is_ok()); + assert!(Parsed::new().set_timestamp(i64::MAX).is_ok()); + + assert_eq!(Parsed::new().set_offset(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_offset(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_offset(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_offset(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + } + #[test] fn test_parsed_to_naive_date() { macro_rules! parse { From ba4e50c8f2ace93abc22345ec73f762ef433a23f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 6 Feb 2024 15:55:14 +0100 Subject: [PATCH 777/999] Add getters to `Parsed` --- src/format/parsed.rs | 244 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 211 insertions(+), 33 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 5c25b92dcd..94f9cdcfca 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -11,7 +11,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// A type to hold parsed fields of date and time that can check all fields are consistent. /// -/// There are two classes of methods: +/// There are three classes of methods: /// /// - `set_*` methods to set fields you have available. They do a basic range check, and if the /// same field is set more than once it is checked for consistency. @@ -19,6 +19,8 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// - `to_*` methods try to make a concrete date and time value out of set fields. /// They fully check that all fields are consistent and whether the date/datetime exists. /// +/// - Methods to inspect the parsed fields. +/// /// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it /// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the /// results of a string parsed with chrono without converting it to concrete types. @@ -116,7 +118,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// assert!(result.is_err()); /// if result.is_err() { /// // What is the weekday? -/// assert_eq!(parsed.weekday, Some(Weekday::Thu)); +/// assert_eq!(parsed.weekday(), Some(Weekday::Thu)); /// } /// # Ok::<(), chrono::ParseError>(()) /// ``` @@ -232,9 +234,10 @@ impl Parsed { Parsed::default() } - /// Set the 'year' field to the given value. + /// Set the [`year`](Parsed::year) field to the given value. /// - /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields. + /// The value can be negative, unlike the [`year_div_100`](Parsed::year_div_100) and + /// [`year_mod_100`](Parsed::year_mod_100) fields. /// /// # Errors /// @@ -246,7 +249,7 @@ impl Parsed { set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Set the 'year divided by 100' field to the given value. + /// Set the [`year_div_100`](Parsed::year_div_100) field to the given value. /// /// # Errors /// @@ -261,12 +264,13 @@ impl Parsed { set_if_consistent(&mut self.year_div_100, value as i32) } - /// Set the 'year modulo 100' field to the given value. + /// Set the [`year_mod_100`](Parsed::year_mod_100) field to the given value. /// /// When set it implies that the year is not negative. /// - /// If this field is set while the 'year divided by 100' field is missing (and the full 'year' - /// field is also not set), it assumes a default value for the 'year divided by 100' field. + /// If this field is set while the [`year_div_100`](Parsed::year_div_100) field is missing (and + /// the full [`year`](Parsed::year) field is also not set), it assumes a default value for the + /// [`year_div_100`](Parsed::year_div_100) field. /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. /// /// # Errors @@ -282,9 +286,11 @@ impl Parsed { set_if_consistent(&mut self.year_mod_100, value as i32) } - /// Set the 'year' field that is part of an [ISO 8601 week date] to the given value. + /// Set the [`isoyear`](Parsed::isoyear) field, that is part of an [ISO 8601 week date], to the + /// given value. /// - /// The value can be negative, unlike the 'year divided by 100' and 'year modulo 100' fields. + /// The value can be negative, unlike the [`isoyear_div_100`](Parsed::isoyear_div_100) and + /// [`isoyear_mod_100`](Parsed::isoyear_mod_100) fields. /// /// [ISO 8601 week date]: crate::NaiveDate#week-date /// @@ -298,8 +304,8 @@ impl Parsed { set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Set the 'year divided by 100' field that is part of an [ISO 8601 week date] to the given - /// value. + /// Set the [`isoyear_div_100`](Parsed::isoyear_div_100) field, that is part of an + /// [ISO 8601 week date], to the given value. /// /// [ISO 8601 week date]: crate::NaiveDate#week-date /// @@ -316,12 +322,14 @@ impl Parsed { set_if_consistent(&mut self.isoyear_div_100, value as i32) } - /// Set the 'year modulo 100' that is part of an [ISO 8601 week date] field to the given value. + /// Set the [`isoyear_mod_100`](Parsed::isoyear_mod_100) field, that is part of an + /// [ISO 8601 week date], to the given value. /// /// When set it implies that the year is not negative. /// - /// If this field is set while the 'year divided by 100' field is missing (and the full `year` - /// field is also not set), it assumes a default value for the 'year divided by 100' field. + /// If this field is set while the [`isoyear_div_100`](Parsed::isoyear_div_100) field is missing + /// (and the full [`isoyear`](Parsed::isoyear) field is also not set), it assumes a default + /// value for the [`isoyear_div_100`](Parsed::isoyear_div_100) field. /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. /// /// [ISO 8601 week date]: crate::NaiveDate#week-date @@ -339,7 +347,7 @@ impl Parsed { set_if_consistent(&mut self.isoyear_mod_100, value as i32) } - /// Set the 'month' field to the given value. + /// Set the [`month`](Parsed::month) field to the given value. /// /// # Errors /// @@ -354,7 +362,7 @@ impl Parsed { set_if_consistent(&mut self.month, value as u32) } - /// Set the 'week number starting with Sunday' field to the given value. + /// Set the [`week_from_sun`](Parsed::week_from_sun) week number field to the given value. /// /// Week 1 starts at the first Sunday of January. /// @@ -371,6 +379,7 @@ impl Parsed { set_if_consistent(&mut self.week_from_sun, value as u32) } + /// Set the [`week_from_mon`](Parsed::week_from_mon) week number field to the given value. /// Set the 'week number starting with Monday' field to the given value. /// /// Week 1 starts at the first Monday of January. @@ -388,9 +397,9 @@ impl Parsed { set_if_consistent(&mut self.week_from_mon, value as u32) } - /// Set the '[ISO 8601 week number]' field to the given value. + /// Set the [`isoweek`](Parsed::isoweek) field for an [ISO 8601 week date] to the given value. /// - /// [ISO 8601 week number]: crate::NaiveDate#week-date + /// [ISO 8601 week date]: crate::NaiveDate#week-date /// /// # Errors /// @@ -405,7 +414,7 @@ impl Parsed { set_if_consistent(&mut self.isoweek, value as u32) } - /// Set the 'day of the week' field to the given value. + /// Set the [`weekday`](Parsed::weekday) field to the given value. /// /// # Errors /// @@ -415,7 +424,7 @@ impl Parsed { set_if_consistent(&mut self.weekday, value) } - /// Set the 'ordinal' (day of the year) field to the given value. + /// Set the [`ordinal`](Parsed::ordinal) (day of the year) field to the given value. /// /// # Errors /// @@ -430,7 +439,7 @@ impl Parsed { set_if_consistent(&mut self.ordinal, value as u32) } - /// Set the 'day of the month' field to the given value. + /// Set the [`day`](Parsed::day) of the month field to the given value. /// /// # Errors /// @@ -445,7 +454,7 @@ impl Parsed { set_if_consistent(&mut self.day, value as u32) } - /// Set the 'am/pm' field to the given value. + /// Set the [`hour_div_12`](Parsed::hour_div_12) am/pm field to the given value. /// /// `false` indicates AM and `true` indicates PM. /// @@ -457,7 +466,8 @@ impl Parsed { set_if_consistent(&mut self.hour_div_12, value as u32) } - /// Set the 'hour number in 12-hour clocks' field to the given value. + /// Set the [`hour_mod_12`](Parsed::hour_mod_12) field, for the hour number in 12-hour clocks, + /// to the given value. /// /// Value must be in the canonical range of 1-12. /// It will internally be stored as 0-11 (`value % 12`). @@ -478,9 +488,8 @@ impl Parsed { set_if_consistent(&mut self.hour_mod_12, value as u32) } - /// Set the 'hour' field to the given value. - /// - /// Internally this sets the 'hour modulo 12' and 'am/pm' fields. + /// Set the [`hour_div_12`](Parsed::hour_div_12) and [`hour_mod_12`](Parsed::hour_mod_12) + /// fields to the given value for a 24-hour clock. /// /// # Errors /// @@ -499,7 +508,7 @@ impl Parsed { set_if_consistent(&mut self.hour_mod_12, hour_mod_12) } - /// Set the 'minute' field to the given value. + /// Set the [`minute`](Parsed::minute) field to the given value. /// /// # Errors /// @@ -514,7 +523,7 @@ impl Parsed { set_if_consistent(&mut self.minute, value as u32) } - /// Set the 'second' field to the given value. + /// Set the [`second`](Parsed::second) field to the given value. /// /// The value can be 60 in the case of a leap second. /// @@ -531,7 +540,7 @@ impl Parsed { set_if_consistent(&mut self.second, value as u32) } - /// Set the 'nanosecond' field to the given value. + /// Set the [`nanosecond`](Parsed::nanosecond) field to the given value. /// /// This is the number of nanoseconds since the whole second. /// @@ -548,7 +557,7 @@ impl Parsed { set_if_consistent(&mut self.nanosecond, value as u32) } - /// Set the 'timestamp' field to the given value. + /// Set the [`timestamp`](Parsed::timestamp) field to the given value. /// /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on /// January 1, 1970. @@ -561,9 +570,9 @@ impl Parsed { set_if_consistent(&mut self.timestamp, value) } - /// Set the 'offset from local time to UTC' field to the given value. + /// Set the [`offset`](Parsed::offset) field to the given value. /// - /// The offset is in seconds. + /// The offset is in seconds from local time to UTC. /// /// # Errors /// @@ -1052,6 +1061,175 @@ impl Parsed { } } } + + /// Get the `year` field if set. + /// + /// See also [`set_year()`](Parsed::set_year). + #[inline] + pub fn year(&self) -> Option { + self.year + } + + /// Get the `year_div_100` field if set. + /// + /// See also [`set_year_div_100()`](Parsed::set_year_div_100). + #[inline] + pub fn year_div_100(&self) -> Option { + self.year_div_100 + } + + /// Get the `year_mod_100` field if set. + /// + /// See also [`set_year_mod_100()`](Parsed::set_year_mod_100). + #[inline] + pub fn year_mod_100(&self) -> Option { + self.year_mod_100 + } + + /// Get the `isoyear` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear()`](Parsed::set_isoyear). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear(&self) -> Option { + self.isoyear + } + + /// Get the `isoyear_div_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_div_100()`](Parsed::set_isoyear_div_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_div_100(&self) -> Option { + self.isoyear_div_100 + } + + /// Get the `isoyear_mod_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_mod_100()`](Parsed::set_isoyear_mod_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_mod_100(&self) -> Option { + self.isoyear_mod_100 + } + + /// Get the `month` field if set. + /// + /// See also [`set_month()`](Parsed::set_month). + #[inline] + pub fn month(&self) -> Option { + self.month + } + + /// Get the `week_from_sun` field if set. + /// + /// See also [`set_week_from_sun()`](Parsed::set_week_from_sun). + #[inline] + pub fn week_from_sun(&self) -> Option { + self.week_from_sun + } + + /// Get the `week_from_mon` field if set. + /// + /// See also [`set_week_from_mon()`](Parsed::set_week_from_mon). + #[inline] + pub fn week_from_mon(&self) -> Option { + self.week_from_mon + } + + /// Get the `isoweek` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoweek()`](Parsed::set_isoweek). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoweek(&self) -> Option { + self.isoweek + } + + /// Get the `weekday` field if set. + /// + /// See also [`set_weekday()`](Parsed::set_weekday). + #[inline] + pub fn weekday(&self) -> Option { + self.weekday + } + + /// Get the `ordinal` (day of the year) field if set. + /// + /// See also [`set_ordinal()`](Parsed::set_ordinal). + #[inline] + pub fn ordinal(&self) -> Option { + self.ordinal + } + + /// Get the `day` of the month field if set. + /// + /// See also [`set_day()`](Parsed::set_day). + #[inline] + pub fn day(&self) -> Option { + self.day + } + + /// Get the `hour_div_12` field (am/pm) if set. + /// + /// 0 indicates AM and 1 indicates PM. + /// + /// See also [`set_ampm()`](Parsed::set_ampm) and [`set_hour()`](Parsed::set_hour). + #[inline] + pub fn hour_div_12(&self) -> Option { + self.hour_div_12 + } + + /// Get the `hour_mod_12` field if set. + /// + /// See also [`set_hour12()`](Parsed::set_hour12) and [`set_hour()`](Parsed::set_hour). + pub fn hour_mod_12(&self) -> Option { + self.hour_mod_12 + } + + /// Get the `minute` field if set. + /// + /// See also [`set_minute()`](Parsed::set_minute). + #[inline] + pub fn minute(&self) -> Option { + self.minute + } + + /// Get the `second` field if set. + /// + /// See also [`set_second()`](Parsed::set_second). + #[inline] + pub fn second(&self) -> Option { + self.second + } + + /// Get the `nanosecond` field if set. + /// + /// See also [`set_nanosecond()`](Parsed::set_nanosecond). + #[inline] + pub fn nanosecond(&self) -> Option { + self.nanosecond + } + + /// Get the `timestamp` field if set. + /// + /// See also [`set_timestamp()`](Parsed::set_timestamp). + #[inline] + pub fn timestamp(&self) -> Option { + self.timestamp + } + + /// Get the `offset` field if set. + /// + /// See also [`set_offset()`](Parsed::set_offset). + #[inline] + pub fn offset(&self) -> Option { + self.offset + } } #[cfg(test)] From 90dbd75d0d24c636e60f80f31be5c9feaff01593 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Feb 2024 16:15:39 +0100 Subject: [PATCH 778/999] Hide fields on `Parsed` in preparation of making them private with 0.5 --- src/format/parsed.rs | 84 +++++++++++--------------------------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 94f9cdcfca..0a9da7aa27 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -125,89 +125,47 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; #[allow(clippy::manual_non_exhaustive)] #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { - /// Year. - /// - /// This can be negative, unlike [`year_div_100`](#structfield.year_div_100) - /// and [`year_mod_100`](#structfield.year_mod_100) fields. + #[doc(hidden)] pub year: Option, - - /// Year divided by 100. Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`year_mod_100`](#structfield.year_mod_100) is present, - /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub year_div_100: Option, - - /// Year modulo 100. Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub year_mod_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). - /// - /// This can be negative, unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. + #[doc(hidden)] pub isoyear: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100. - /// Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present, - /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub isoyear_div_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100. - /// Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub isoyear_mod_100: Option, - - /// Month (1--12). + #[doc(hidden)] pub month: Option, - - /// Week number, where the week 1 starts at the first Sunday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_sun: Option, - - /// Week number, where the week 1 starts at the first Monday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_mon: Option, - - /// [ISO week number](../naive/struct.NaiveDate.html#week-date) - /// (1--52 or 1--53 depending on the year). + #[doc(hidden)] pub isoweek: Option, - - /// Day of the week. + #[doc(hidden)] pub weekday: Option, - - /// Day of the year (1--365 or 1--366 depending on the year). + #[doc(hidden)] pub ordinal: Option, - - /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). + #[doc(hidden)] pub day: Option, - - /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. + #[doc(hidden)] pub hour_div_12: Option, - - /// Hour number modulo 12 (0--11). + #[doc(hidden)] pub hour_mod_12: Option, - - /// Minute number (0--59). + #[doc(hidden)] pub minute: Option, - - /// Second number (0--60, accounting for leap seconds). + #[doc(hidden)] pub second: Option, - - /// The number of nanoseconds since the whole second (0--999,999,999). + #[doc(hidden)] pub nanosecond: Option, - - /// The number of non-leap seconds since the midnight UTC on January 1, 1970. - /// - /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second). + #[doc(hidden)] pub timestamp: Option, - - /// Offset from the local time to UTC, in seconds. + #[doc(hidden)] pub offset: Option, - - /// A dummy field to make this type not fully destructible (required for API stability). - // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. + #[doc(hidden)] _dummy: (), } From 5caa640f0fd10a15bdb3d2fb1f09a679456c3151 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 07:56:29 +0100 Subject: [PATCH 779/999] Documentation fixes --- src/time_delta.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index e87bfa404e..f9bdffa58e 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -108,7 +108,7 @@ impl TimeDelta { /// Makes a new `TimeDelta` with the given number of weeks. /// - /// Equivalent to `TimeDelta::seconds(weeks * 7 * 24 * 60 * 60)` with + /// Equivalent to `TimeDelta::try_seconds(weeks * 7 * 24 * 60 * 60)` with /// overflow checks. /// /// # Errors @@ -135,7 +135,7 @@ impl TimeDelta { /// Makes a new `TimeDelta` with the given number of days. /// - /// Equivalent to `TimeDelta::seconds(days * 24 * 60 * 60)` with overflow + /// Equivalent to `TimeDelta::try_seconds(days * 24 * 60 * 60)` with overflow /// checks. /// /// # Errors @@ -161,7 +161,7 @@ impl TimeDelta { /// Makes a new `TimeDelta` with the given number of hours. /// - /// Equivalent to `TimeDelta::seconds(hours * 60 * 60)` with overflow checks. + /// Equivalent to `TimeDelta::try_seconds(hours * 60 * 60)` with overflow checks. /// /// # Errors /// @@ -186,7 +186,7 @@ impl TimeDelta { /// Makes a new `TimeDelta` with the given number of minutes. /// - /// Equivalent to `TimeDelta::seconds(minutes * 60)` with overflow checks. + /// Equivalent to `TimeDelta::try_seconds(minutes * 60)` with overflow checks. /// /// # Errors /// From d7e5853949d7c7d8bd8a16ff24b734b5cd491f83 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 15:59:20 +0100 Subject: [PATCH 780/999] Use `checked_add_days` in `NaiveDateWeeksIterator` --- src/naive/date/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index d0ac747a6b..475c5c3538 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2154,7 +2154,7 @@ impl Iterator for NaiveDateWeeksIterator { fn next(&mut self) -> Option { let current = self.value; - self.value = current.checked_add_signed(TimeDelta::weeks(1))?; + self.value = current.checked_add_days(Days::new(7))?; Some(current) } @@ -2169,7 +2169,7 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { let current = self.value; - self.value = current.checked_sub_signed(TimeDelta::weeks(1))?; + self.value = current.checked_sub_days(Days::new(7))?; Some(current) } } From 383fcff56c1e485593e13a955d4d9542a27c7744 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 27 May 2023 07:42:12 +0200 Subject: [PATCH 781/999] Use simplified `resolve_week_date` --- src/format/parsed.rs | 98 +++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 64 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 0a9da7aa27..cf6bf99e47 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -677,71 +677,15 @@ impl Parsed { (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_sun: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Sunday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 0, - Weekday::Mon => 6, - Weekday::Tue => 5, - Weekday::Wed => 4, - Weekday::Thu => 3, - Weekday::Fri => 2, - Weekday::Sat => 1, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_sun > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_sun as i32 - 1) * 7 - + weekday.num_days_from_sunday() as i32; - let date = newyear - .checked_add_signed(TimeDelta::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Sun)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_mon: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Monday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 1, - Weekday::Mon => 0, - Weekday::Tue => 6, - Weekday::Wed => 5, - Weekday::Thu => 4, - Weekday::Fri => 3, - Weekday::Sat => 2, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_mon > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_mon as i32 - 1) * 7 - + weekday.num_days_from_monday() as i32; - let date = newyear - .checked_add_signed(TimeDelta::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Mon)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } @@ -1190,6 +1134,32 @@ impl Parsed { } } +/// Create a `NaiveDate` when given a year, week, weekday, and the definition at which day of the +/// week a week starts. +/// +/// Returns `IMPOSSIBLE` if `week` is `0` or `53` and the `weekday` falls outside the year. +fn resolve_week_date( + year: i32, + week: u32, + weekday: Weekday, + week_start_day: Weekday, +) -> ParseResult { + if week > 53 { + return Err(OUT_OF_RANGE); + } + + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; + // Ordinal of the day at which week 1 starts. + let first_week_start = 1 + week_start_day.num_days_from(first_day_of_year.weekday()) as i32; + // Number of the `weekday`, which is 0 for the first day of the week. + let weekday = weekday.num_days_from(week_start_day) as i32; + let ordinal = first_week_start + (week as i32 - 1) * 7 + weekday; + if ordinal <= 0 { + return Err(IMPOSSIBLE); + } + first_day_of_year.with_ordinal(ordinal as u32).ok_or(IMPOSSIBLE) +} + #[cfg(test)] mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; @@ -1464,8 +1434,8 @@ mod tests { assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); - assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); @@ -1479,9 +1449,9 @@ mod tests { assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); - assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); // weekdates: conflicting inputs From 073c3c32f0384a45bedc1e593fdab1bbf5060b3f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 16:11:39 +0100 Subject: [PATCH 782/999] Replace `TimeDelta::days` with `try_days` --- src/naive/date/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 475c5c3538..6961449783 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1140,7 +1140,10 @@ impl NaiveDate { let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); let cycle1 = yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; let cycle2 = yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; - TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2)) + let days = (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2); + // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 + // years. + expect!(TimeDelta::try_days(days), "always in range") } /// Returns the number of whole years from the given `base` until `self`. From 4251bd1eb172d30af41be1a7e8893dd84046fe3c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 16:22:21 +0100 Subject: [PATCH 783/999] Replace `TimeDelta::seconds` with `try_seconds` --- src/format/parsed.rs | 2 +- src/naive/datetime/mod.rs | 30 ++++++------------------------ 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index cf6bf99e47..6f7253de4c 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -822,7 +822,7 @@ impl Parsed { 59 => {} // `datetime` is known to be off by one second. 0 => { - datetime -= TimeDelta::seconds(1); + datetime -= TimeDelta::try_seconds(1).unwrap(); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 181576d76c..13e9a1d00b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -34,14 +34,6 @@ pub(crate) mod serde; #[cfg(test)] mod tests; -/// The tight upper bound guarantees that a time delta with `|TimeDelta| >= 2^MAX_SECS_BITS` -/// will always overflow the addition with any date and time type. -/// -/// So why is this needed? `TimeDelta::seconds(rhs)` may overflow, and we don't have -/// an alternative returning `Option` or `Result`. Thus we need some early bound to avoid -/// touching that call when we are already sure that it WILL overflow... -const MAX_SECS_BITS: usize = 44; - /// The minimum possible `NaiveDateTime`. #[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; @@ -527,14 +519,9 @@ impl NaiveDateTime { /// ``` #[must_use] pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { - let (time, rhs) = self.time.overflowing_add_signed(rhs); - - // early checking to avoid overflow in TimeDelta::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_add_signed(TimeDelta::seconds(rhs))); + let (time, remainder) = self.time.overflowing_add_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_add_signed(remainder)); Some(NaiveDateTime { date, time }) } @@ -703,14 +690,9 @@ impl NaiveDateTime { /// ``` #[must_use] pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { - let (time, rhs) = self.time.overflowing_sub_signed(rhs); - - // early checking to avoid overflow in TimeDelta::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_sub_signed(TimeDelta::seconds(rhs))); + let (time, remainder) = self.time.overflowing_sub_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_sub_signed(remainder)); Some(NaiveDateTime { date, time }) } From 51a1aa27bd27c04d914c3f01e899e55189b5197b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 21:47:30 +0100 Subject: [PATCH 784/999] Tests: use `Days` type when it is more appropriate than `TimeDelta` --- src/datetime/tests.rs | 16 ++++++---------- src/offset/local/mod.rs | 6 +++--- src/traits.rs | 4 ++-- tests/dateutils.rs | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index f53b3c953a..b25058ffe6 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -307,7 +307,7 @@ fn test_nanosecond_range() { // Far beyond range let maximum = "2262-04-11T23:47:16.854775804UTC"; let parsed: DateTime = maximum.parse().unwrap(); - let beyond_max = parsed + TimeDelta::days(365); + let beyond_max = parsed + Days::new(365); assert!(beyond_max.timestamp_nanos_opt().is_none()); } @@ -1455,20 +1455,16 @@ fn test_datetime_before_windows_api_limits() { #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { - const WEEKS_PER_YEAR: f32 = 52.1775; - - // This is always at least one year because 1 year = 52.1775 weeks. - let one_year_ago = - Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); - // A bit more than 2 years. - let two_year_ago = - Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + // A bit more than 1 year + let one_year_ago = Utc::now().date_naive() - Days::new(400); + // A bit more than 2 years + let two_year_ago = Utc::now().date_naive() - Days::new(750); assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::now().date_naive() + TimeDelta::weeks(12); + let future = Utc::now().date_naive() + Days(100); assert_eq!(Utc::now().date_naive().years_since(future), None); } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index b379b39e46..3b71a9a0ab 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -273,7 +273,7 @@ mod tests { #[cfg(windows)] use crate::offset::local::{lookup_with_dst_transitions, Transition}; use crate::offset::TimeZone; - use crate::{Datelike, TimeDelta, Utc}; + use crate::{Datelike, Days, Utc}; #[cfg(windows)] use crate::{FixedOffset, LocalResult, NaiveDate, NaiveDateTime}; @@ -292,7 +292,7 @@ mod tests { #[test] fn verify_correct_offsets_distant_past() { - let distant_past = Local::now() - TimeDelta::days(365 * 500); + let distant_past = Local::now() - Days::new(365 * 500); let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); @@ -305,7 +305,7 @@ mod tests { #[test] fn verify_correct_offsets_distant_future() { - let distant_future = Local::now() + TimeDelta::days(365 * 35000); + let distant_future = Local::now() + Days::new(365 * 35000); let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); diff --git a/src/traits.rs b/src/traits.rs index ae6cde494f..e582093b0b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -330,7 +330,7 @@ pub trait Timelike: Sized { #[cfg(test)] mod tests { use super::Datelike; - use crate::{NaiveDate, TimeDelta}; + use crate::{Days, NaiveDate}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. /// @@ -377,7 +377,7 @@ mod tests { "on {:?}", jan1_year ); - let mid_year = jan1_year + TimeDelta::days(133); + let mid_year = jan1_year + Days::new(133); assert_eq!( mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index cf3d908a4b..c833c9fe20 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -157,6 +157,6 @@ fn try_verify_against_date_command_format() { let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); while date.year() < 2008 { verify_against_date_command_format_local(DATE_PATH, date); - date += chrono::TimeDelta::days(55); + date = date + Days::new(55); } } From e8f9b5e0414eb7232d6cf916f5ecd3b46839b15c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 21:53:21 +0100 Subject: [PATCH 785/999] Tests: replace `TimeDelta::weeks` with `try_weeks` --- src/round.rs | 8 ++++---- src/time_delta.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/round.rs b/src/round.rs index ef3e39d2ba..f30422e3e9 100644 --- a/src/round.rs +++ b/src/round.rs @@ -517,7 +517,7 @@ mod tests { "2020-10-28 00:00:00 +01:00" ); assert_eq!( - dt.duration_round(TimeDelta::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-29 00:00:00 +01:00" ); @@ -529,7 +529,7 @@ mod tests { "2020-10-28 00:00:00 -01:00" ); assert_eq!( - dt.duration_round(TimeDelta::weeks(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-29 00:00:00 -01:00" ); } @@ -679,7 +679,7 @@ mod tests { "2020-10-27 00:00:00 +01:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-22 00:00:00 +01:00" ); @@ -691,7 +691,7 @@ mod tests { "2020-10-27 00:00:00 -01:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::weeks(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), "2020-10-22 00:00:00 -01:00" ); } diff --git a/src/time_delta.rs b/src/time_delta.rs index f9bdffa58e..9f5b7b6a65 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -616,6 +616,7 @@ impl arbitrary::Arbitrary<'_> for TimeDelta { mod tests { use super::OutOfRangeError; use super::{TimeDelta, MAX, MIN}; + use crate::expect; use core::time::Duration; #[test] @@ -1188,7 +1189,7 @@ mod tests { #[test] fn test_duration_const() { - const ONE_WEEK: TimeDelta = TimeDelta::weeks(1); + const ONE_WEEK: TimeDelta = expect!(TimeDelta::try_weeks(1), ""); const ONE_DAY: TimeDelta = TimeDelta::days(1); const ONE_HOUR: TimeDelta = TimeDelta::hours(1); const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1); From 9f23c08e10241f03637b0a52591c65705dda9b19 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 22:02:59 +0100 Subject: [PATCH 786/999] Tests: replace `TimeDelta::days` with `try_days` --- src/datetime/tests.rs | 8 +-- src/naive/date/mod.rs | 97 +++++++++++++++++++++++++------------ src/naive/date/tests.rs | 50 +++++++++++-------- src/naive/datetime/mod.rs | 16 +++--- src/naive/datetime/tests.rs | 4 +- src/naive/time/mod.rs | 8 +-- src/naive/time/tests.rs | 10 ++-- src/round.rs | 27 ++++++----- src/time_delta.rs | 73 ++++++++++++++++------------ tests/dateutils.rs | 4 +- 10 files changed, 175 insertions(+), 122 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b25058ffe6..76c544daba 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1500,8 +1500,8 @@ fn test_datetime_add_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_add += TimeDelta::days(1); - assert_eq!(datetime_add, datetime + TimeDelta::days(i)) + datetime_add += TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_days(i).unwrap()) } } @@ -1709,8 +1709,8 @@ fn test_datetime_sub_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_sub -= TimeDelta::days(1); - assert_eq!(datetime_sub, datetime - TimeDelta::days(i)) + datetime_sub -= TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_days(i).unwrap()) } } diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 6961449783..1ca2cd0b80 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1057,16 +1057,16 @@ impl NaiveDate { /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!( - /// d.checked_add_signed(TimeDelta::days(40)), + /// d.checked_add_signed(TimeDelta::try_days(40).unwrap()), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) /// ); /// assert_eq!( - /// d.checked_add_signed(TimeDelta::days(-40)), + /// d.checked_add_signed(TimeDelta::try_days(-40).unwrap()), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) /// ); - /// assert_eq!(d.checked_add_signed(TimeDelta::days(1_000_000_000)), None); - /// assert_eq!(d.checked_add_signed(TimeDelta::days(-1_000_000_000)), None); - /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::days(1)), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::try_days(1).unwrap()), None); /// ``` #[must_use] pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { @@ -1090,16 +1090,16 @@ impl NaiveDate { /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!( - /// d.checked_sub_signed(TimeDelta::days(40)), + /// d.checked_sub_signed(TimeDelta::try_days(40).unwrap()), /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) /// ); /// assert_eq!( - /// d.checked_sub_signed(TimeDelta::days(-40)), + /// d.checked_sub_signed(TimeDelta::try_days(-40).unwrap()), /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) /// ); - /// assert_eq!(d.checked_sub_signed(TimeDelta::days(1_000_000_000)), None); - /// assert_eq!(d.checked_sub_signed(TimeDelta::days(-1_000_000_000)), None); - /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::days(1)), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::try_days(1).unwrap()), None); /// ``` #[must_use] pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { @@ -1125,12 +1125,27 @@ impl NaiveDate { /// let since = NaiveDate::signed_duration_since; /// /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), TimeDelta::days(1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::days(-1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), TimeDelta::days(100)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), TimeDelta::days(365)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), TimeDelta::days(365 * 4 + 1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), TimeDelta::days(365 * 400 + 97)); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), + /// TimeDelta::try_days(1).unwrap() + /// ); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::try_days(-1).unwrap()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), + /// TimeDelta::try_days(100).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), + /// TimeDelta::try_days(365).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), + /// TimeDelta::try_days(365 * 4 + 1).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), + /// TimeDelta::try_days(365 * 400 + 97).unwrap() + /// ); /// ``` #[must_use] pub const fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { @@ -1894,11 +1909,17 @@ impl Datelike for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(-1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(364), from_ymd(2014, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365 * 4 + 1), from_ymd(2018, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::days(365 * 400 + 97), from_ymd(2414, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(-1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(364).unwrap(), from_ymd(2014, 12, 31)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2018, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(2414, 1, 1) +/// ); /// ``` /// /// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed @@ -2037,11 +2058,17 @@ impl Sub for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(-1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(364), from_ymd(2013, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365 * 4 + 1), from_ymd(2010, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::days(365 * 400 + 97), from_ymd(1614, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(-1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(364).unwrap(), from_ymd(2013, 1, 2)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2010, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(1614, 1, 1) +/// ); /// ``` /// /// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed @@ -2088,12 +2115,18 @@ impl SubAssign for NaiveDate { /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), TimeDelta::zero()); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), TimeDelta::days(1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::days(-1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::days(100)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), TimeDelta::days(365 * 4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), TimeDelta::days(365 * 400 + 97)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), TimeDelta::try_days(1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::try_days(-1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::try_days(100).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::try_days(365).unwrap()); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), +/// TimeDelta::try_days(365 * 4 + 1).unwrap() +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), +/// TimeDelta::try_days(365 * 400 + 97).unwrap() +/// ); /// ``` impl Sub for NaiveDate { type Output = TimeDelta; diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 361a88ef59..cde973daf4 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -457,20 +457,28 @@ fn test_date_add() { check((2014, 1, 1), TimeDelta::seconds(86399), Some((2014, 1, 1))); // always round towards zero check((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::days(1), Some((2014, 1, 2))); - check((2014, 1, 1), TimeDelta::days(-1), Some((2013, 12, 31))); - check((2014, 1, 1), TimeDelta::days(364), Some((2014, 12, 31))); - check((2014, 1, 1), TimeDelta::days(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), TimeDelta::days(365 * 400 + 97), Some((2414, 1, 1))); + check((2014, 1, 1), TimeDelta::try_days(1).unwrap(), Some((2014, 1, 2))); + check((2014, 1, 1), TimeDelta::try_days(-1).unwrap(), Some((2013, 12, 31))); + check((2014, 1, 1), TimeDelta::try_days(364).unwrap(), Some((2014, 12, 31))); + check((2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), Some((2018, 1, 1))); + check((2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), Some((2414, 1, 1))); - check((-7, 1, 1), TimeDelta::days(365 * 12 + 3), Some((5, 1, 1))); + check((-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), Some((5, 1, 1))); // overflow check - check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); + check( + (0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + Some((MAX_YEAR, 12, 31)), + ); + check((0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); check((0, 1, 1), TimeDelta::max_value(), None); - check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); - check((0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); + check( + (0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + Some((MIN_YEAR, 1, 1)), + ); + check((0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); check((0, 1, 1), TimeDelta::min_value(), None); } @@ -484,14 +492,14 @@ fn test_date_sub() { } check((2014, 1, 1), (2014, 1, 1), TimeDelta::zero()); - check((2014, 1, 2), (2014, 1, 1), TimeDelta::days(1)); - check((2014, 12, 31), (2014, 1, 1), TimeDelta::days(364)); - check((2015, 1, 3), (2014, 1, 1), TimeDelta::days(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), TimeDelta::days(365 * 400 + 97)); + check((2014, 1, 2), (2014, 1, 1), TimeDelta::try_days(1).unwrap()); + check((2014, 12, 31), (2014, 1, 1), TimeDelta::try_days(364).unwrap()); + check((2015, 1, 3), (2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap()); + check((2018, 1, 1), (2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); + check((2414, 1, 1), (2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); - check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::days(MAX_DAYS_FROM_YEAR_0 as i64)); - check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::days(MIN_DAYS_FROM_YEAR_0 as i64)); + check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap()); + check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap()); } #[test] @@ -539,9 +547,9 @@ fn test_date_sub_days() { fn test_date_addassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 1); - date += TimeDelta::days(10); + date += TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymd(2016, 10, 11)); - date += TimeDelta::days(30); + date += TimeDelta::try_days(30).unwrap(); assert_eq!(date, ymd(2016, 11, 10)); } @@ -549,9 +557,9 @@ fn test_date_addassignment() { fn test_date_subassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); let mut date = ymd(2016, 10, 11); - date -= TimeDelta::days(10); + date -= TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymd(2016, 10, 1)); - date -= TimeDelta::days(2); + date -= TimeDelta::try_days(2).unwrap(); assert_eq!(date, ymd(2016, 9, 29)); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 13e9a1d00b..8fac7fed5b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -491,7 +491,7 @@ impl NaiveDateTime { /// ``` /// # use chrono::{TimeDelta, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::days(1_000_000_000)), None); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); /// ``` /// /// Leap seconds are handled, @@ -514,7 +514,7 @@ impl NaiveDateTime { /// Some(hmsm(3, 6, 9, 300))); /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(-10)), /// Some(hmsm(3, 5, 50, 300))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::days(1)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_days(1).unwrap()), /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` #[must_use] @@ -666,7 +666,7 @@ impl NaiveDateTime { /// ``` /// # use chrono::{TimeDelta, NaiveDate}; /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::days(1_000_000_000)), None); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); /// ``` /// /// Leap seconds are handled, @@ -685,7 +685,7 @@ impl NaiveDateTime { /// Some(hmsm(3, 5, 59, 800))); /// assert_eq!(leap.checked_sub_signed(TimeDelta::seconds(60)), /// Some(hmsm(3, 5, 0, 300))); - /// assert_eq!(leap.checked_sub_signed(TimeDelta::days(1)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_days(1).unwrap()), /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` #[must_use] @@ -1586,7 +1586,7 @@ impl Timelike for NaiveDateTime { /// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap() /// ); /// assert_eq!( -/// hms(3, 5, 7) + TimeDelta::days(365), +/// hms(3, 5, 7) + TimeDelta::try_days(365).unwrap(), /// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap() /// ); /// @@ -1608,7 +1608,7 @@ impl Timelike for NaiveDateTime { /// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); /// assert_eq!(leap + TimeDelta::seconds(10), hmsm(3, 6, 9, 300)); /// assert_eq!(leap + TimeDelta::seconds(-10), hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + TimeDelta::days(1), +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), /// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` /// @@ -1776,7 +1776,7 @@ impl Add for NaiveDateTime { /// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap() /// ); /// assert_eq!( -/// hms(3, 5, 7) - TimeDelta::days(365), +/// hms(3, 5, 7) - TimeDelta::try_days(365).unwrap(), /// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap() /// ); /// @@ -1796,7 +1796,7 @@ impl Add for NaiveDateTime { /// assert_eq!(leap - TimeDelta::milliseconds(200), hmsm(3, 5, 59, 1_100)); /// assert_eq!(leap - TimeDelta::milliseconds(500), hmsm(3, 5, 59, 800)); /// assert_eq!(leap - TimeDelta::seconds(60), hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - TimeDelta::days(1), +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` /// diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 751ff7e696..f350ce0122 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -75,7 +75,7 @@ fn test_datetime_addassignment() { let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date += TimeDelta::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += TimeDelta::days(10); + date += TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); } @@ -86,7 +86,7 @@ fn test_datetime_subassignment() { let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date -= TimeDelta::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= TimeDelta::days(10); + date -= TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 57c4f5ca03..a6a7bd7e86 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1166,7 +1166,7 @@ impl Timelike for NaiveTime { /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::days(800), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); /// ``` /// /// Leap seconds are handled, but the addition assumes that it is the only leap second happened. @@ -1181,7 +1181,7 @@ impl Timelike for NaiveTime { /// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100)); /// assert_eq!(leap + TimeDelta::seconds(10), from_hmsm(3, 6, 9, 300)); /// assert_eq!(leap + TimeDelta::seconds(-10), from_hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + TimeDelta::days(1), from_hmsm(3, 5, 59, 300)); +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), from_hmsm(3, 5, 59, 300)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling @@ -1281,7 +1281,7 @@ impl Add for NaiveTime { /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::days(800), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); /// ``` /// /// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. @@ -1294,7 +1294,7 @@ impl Add for NaiveTime { /// assert_eq!(leap - TimeDelta::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); /// assert_eq!(leap - TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 800)); /// assert_eq!(leap - TimeDelta::seconds(60), from_hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - TimeDelta::days(1), from_hmsm(3, 6, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), from_hmsm(3, 6, 0, 300)); /// ``` /// /// [leap second handling]: crate::NaiveTime#leap-second-handling diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index a991117a7f..84acca5adf 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -103,9 +103,9 @@ fn test_time_add() { check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(1800), hmsm(3, 6, 1, 100)); check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(-86399), hmsm(3, 6, 0, 900)); - check!(hmsm(3, 5, 59, 900), TimeDelta::days(12345), hmsm(3, 5, 59, 900)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(1), hmsm(3, 5, 59, 300)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::days(-1), hmsm(3, 6, 0, 300)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300)); // regression tests for #37 check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-990), hmsm(23, 59, 59, 10)); @@ -131,11 +131,11 @@ fn test_time_overflowing_add() { // overflowing_add_signed with leap seconds may be counter-intuitive assert_eq!( - hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::days(1)), + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(1).unwrap()), (hmsm(3, 4, 59, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::days(-1)), + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(-1).unwrap()), (hmsm(3, 5, 0, 678), -86_400) ); } diff --git a/src/round.rs b/src/round.rs index f30422e3e9..53e7d06234 100644 --- a/src/round.rs +++ b/src/round.rs @@ -131,7 +131,7 @@ pub trait DurationRound: Sized { /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` @@ -153,7 +153,7 @@ pub trait DurationRound: Sized { /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` @@ -268,7 +268,7 @@ pub enum RoundingError { /// .unwrap(); /// /// assert_eq!( - /// dt.duration_round(TimeDelta::days(300 * 365)), + /// dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()), /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` @@ -280,7 +280,10 @@ pub enum RoundingError { /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// - /// assert_eq!(dt.duration_round(TimeDelta::days(1)), Err(RoundingError::TimestampExceedsLimit),); + /// assert_eq!( + /// dt.duration_round(TimeDelta::try_days(1).unwrap()), + /// Err(RoundingError::TimestampExceedsLimit) + /// ); /// ``` TimestampExceedsLimit, } @@ -505,7 +508,7 @@ mod tests { "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-13 00:00:00 UTC" ); @@ -513,7 +516,7 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" ); assert_eq!( @@ -525,7 +528,7 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" ); assert_eq!( @@ -598,7 +601,7 @@ mod tests { "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_round(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-13 00:00:00" ); } @@ -667,7 +670,7 @@ mod tests { "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-12 00:00:00 UTC" ); @@ -675,7 +678,7 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" ); assert_eq!( @@ -687,7 +690,7 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" ); assert_eq!( @@ -754,7 +757,7 @@ mod tests { "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-12 00:00:00" ); } diff --git a/src/time_delta.rs b/src/time_delta.rs index 9f5b7b6a65..fdf2050db2 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -621,26 +621,22 @@ mod tests { #[test] fn test_duration() { + let days = |d| TimeDelta::try_days(d).unwrap(); + assert!(TimeDelta::seconds(1) != TimeDelta::zero()); assert_eq!(TimeDelta::seconds(1) + TimeDelta::seconds(2), TimeDelta::seconds(3)); assert_eq!( TimeDelta::seconds(86_399) + TimeDelta::seconds(4), - TimeDelta::days(1) + TimeDelta::seconds(3) - ); - assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000)); - assert_eq!( - TimeDelta::days(10) - TimeDelta::seconds(1_000_000), - TimeDelta::seconds(-136_000) + days(1) + TimeDelta::seconds(3) ); + assert_eq!(days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000)); + assert_eq!(days(10) - TimeDelta::seconds(1_000_000), TimeDelta::seconds(-136_000)); assert_eq!( - TimeDelta::days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), - TimeDelta::days(3) + TimeDelta::nanoseconds(234_567_890) - ); - assert_eq!(-TimeDelta::days(3), TimeDelta::days(-3)); - assert_eq!( - -(TimeDelta::days(3) + TimeDelta::seconds(70)), - TimeDelta::days(-4) + TimeDelta::seconds(86_400 - 70) + days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), + days(3) + TimeDelta::nanoseconds(234_567_890) ); + assert_eq!(-days(3), days(-3)); + assert_eq!(-(days(3) + TimeDelta::seconds(70)), days(-4) + TimeDelta::seconds(86_400 - 70)); let mut d = TimeDelta::default(); d += TimeDelta::minutes(1); @@ -651,14 +647,14 @@ mod tests { #[test] fn test_duration_num_days() { assert_eq!(TimeDelta::zero().num_days(), 0); - assert_eq!(TimeDelta::days(1).num_days(), 1); - assert_eq!(TimeDelta::days(-1).num_days(), -1); + assert_eq!(TimeDelta::try_days(1).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_days(-1).unwrap().num_days(), -1); assert_eq!(TimeDelta::seconds(86_399).num_days(), 0); assert_eq!(TimeDelta::seconds(86_401).num_days(), 1); assert_eq!(TimeDelta::seconds(-86_399).num_days(), 0); assert_eq!(TimeDelta::seconds(-86_401).num_days(), -1); - assert_eq!(TimeDelta::days(i32::MAX as i64).num_days(), i32::MAX as i64); - assert_eq!(TimeDelta::days(i32::MIN as i64).num_days(), i32::MIN as i64); + assert_eq!(TimeDelta::try_days(i32::MAX as i64).unwrap().num_days(), i32::MAX as i64); + assert_eq!(TimeDelta::try_days(i32::MIN as i64).unwrap().num_days(), i32::MIN as i64); } #[test] @@ -791,15 +787,21 @@ mod tests { // overflow checks const MICROS_PER_DAY: i64 = 86_400_000_000; assert_eq!( - TimeDelta::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!( - TimeDelta::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(), + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); - assert_eq!(TimeDelta::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(TimeDelta::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); + assert_eq!( + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY + 1).unwrap().num_microseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY - 1).unwrap().num_microseconds(), + None + ); } #[test] fn test_duration_microseconds_max_allowed() { @@ -887,15 +889,21 @@ mod tests { // overflow checks const NANOS_PER_DAY: i64 = 86_400_000_000_000; assert_eq!( - TimeDelta::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!( - TimeDelta::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); - assert_eq!(TimeDelta::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(TimeDelta::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); + assert_eq!( + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY + 1).unwrap().num_nanoseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY - 1).unwrap().num_nanoseconds(), + None + ); } #[test] fn test_duration_nanoseconds_max_allowed() { @@ -1079,8 +1087,9 @@ mod tests { TimeDelta::seconds(10) - TimeDelta::nanoseconds(10) ); assert_eq!( - (TimeDelta::nanoseconds(1) + TimeDelta::seconds(1) + TimeDelta::days(1)) * 3, - TimeDelta::nanoseconds(3) + TimeDelta::seconds(3) + TimeDelta::days(3) + (TimeDelta::nanoseconds(1) + TimeDelta::seconds(1) + TimeDelta::try_days(1).unwrap()) + * 3, + TimeDelta::nanoseconds(3) + TimeDelta::seconds(3) + TimeDelta::try_days(3).unwrap() ); assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::seconds(-3)); assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::seconds(-3)); @@ -1131,14 +1140,14 @@ mod tests { #[test] fn test_duration_fmt() { assert_eq!(TimeDelta::zero().to_string(), "P0D"); - assert_eq!(TimeDelta::days(42).to_string(), "PT3628800S"); - assert_eq!(TimeDelta::days(-42).to_string(), "-PT3628800S"); + assert_eq!(TimeDelta::try_days(42).unwrap().to_string(), "PT3628800S"); + assert_eq!(TimeDelta::try_days(-42).unwrap().to_string(), "-PT3628800S"); assert_eq!(TimeDelta::seconds(42).to_string(), "PT42S"); assert_eq!(TimeDelta::milliseconds(42).to_string(), "PT0.042S"); assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); assert_eq!( - (TimeDelta::days(7) + TimeDelta::milliseconds(6543)).to_string(), + (TimeDelta::try_days(7).unwrap() + TimeDelta::milliseconds(6543)).to_string(), "PT604806.543S" ); assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-PT86401S"); @@ -1146,7 +1155,7 @@ mod tests { // the format specifier should have no effect on `TimeDelta` assert_eq!( - format!("{:30}", TimeDelta::days(1) + TimeDelta::milliseconds(2345)), + format!("{:30}", TimeDelta::try_days(1).unwrap() + TimeDelta::milliseconds(2345)), "PT86402.345S" ); } @@ -1190,7 +1199,7 @@ mod tests { #[test] fn test_duration_const() { const ONE_WEEK: TimeDelta = expect!(TimeDelta::try_weeks(1), ""); - const ONE_DAY: TimeDelta = TimeDelta::days(1); + const ONE_DAY: TimeDelta = expect!(TimeDelta::try_days(1), ""); const ONE_HOUR: TimeDelta = TimeDelta::hours(1); const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1); const ONE_SECOND: TimeDelta = TimeDelta::seconds(1); diff --git a/tests/dateutils.rs b/tests/dateutils.rs index c833c9fe20..34f4ac7e3a 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -1,6 +1,6 @@ #![cfg(all(unix, feature = "clock", feature = "std"))] -use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; +use chrono::{Datelike, Days, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; use std::{path, process, thread}; fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { @@ -94,7 +94,7 @@ fn try_verify_against_date_command() { let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); while date <= end { verify_against_date_command_local(DATE_PATH, date); - date += chrono::TimeDelta::hours(1); + date += chrono::TimeDelta::try_hours(1).unwrap(); } })); } From 9fc931a394054f183200f1c6bdc2bdfd475937d0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 20 Feb 2024 18:24:42 +0100 Subject: [PATCH 787/999] Tests: replace `TimeDelta::hours` with `try_hours` --- src/datetime/tests.rs | 14 ++++++++++---- src/naive/time/mod.rs | 12 ++++++------ src/naive/time/tests.rs | 14 +++++++------- src/offset/local/windows.rs | 2 +- src/round.rs | 8 ++++---- src/time_delta.rs | 2 +- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 76c544daba..0f4516b16b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -53,7 +53,7 @@ impl TimeZone for DstTester { DstTester::TO_WINTER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() - TimeDelta::hours(1)); + .and_time(DstTester::transition_start_local() - TimeDelta::try_hours(1).unwrap()); let local_to_summer_transition_start = NaiveDate::from_ymd_opt( local.year(), @@ -69,7 +69,7 @@ impl TimeZone for DstTester { DstTester::TO_SUMMER_MONTH_DAY.1, ) .unwrap() - .and_time(DstTester::transition_start_local() + TimeDelta::hours(1)); + .and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap()); if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { LocalResult::Single(DstTester::summer_offset()) @@ -1607,7 +1607,10 @@ fn test_min_max_setters() { assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); assert_eq!(beyond_min.with_ordinal0(200), None); assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); - assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(TimeDelta::hours(1))); + assert_eq!( + beyond_min.with_hour(23), + beyond_min.checked_add_signed(TimeDelta::try_hours(1).unwrap()) + ); assert_eq!(beyond_min.with_hour(5), None); assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); assert_eq!(beyond_min.with_second(0), Some(beyond_min)); @@ -1628,7 +1631,10 @@ fn test_min_max_setters() { assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); assert_eq!(beyond_max.with_ordinal0(200), None); assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); - assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(TimeDelta::hours(1))); + assert_eq!( + beyond_max.with_hour(0), + beyond_max.checked_sub_signed(TimeDelta::try_hours(1).unwrap()) + ); assert_eq!(beyond_max.with_hour(5), None); assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index a6a7bd7e86..61a97e1128 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -594,15 +594,15 @@ impl NaiveTime { /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); /// /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(11)), + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), /// (from_hms(14, 4, 5), 0) /// ); /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(23)), + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), /// (from_hms(2, 4, 5), 86_400) /// ); /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)), + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), /// (from_hms(20, 4, 5), -86_400) /// ); /// ``` @@ -656,15 +656,15 @@ impl NaiveTime { /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); /// /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(2)), + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(2).unwrap()), /// (from_hms(1, 4, 5), 0) /// ); /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(17)), + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(17).unwrap()), /// (from_hms(10, 4, 5), 86_400) /// ); /// assert_eq!( - /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::hours(-22)), + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(-22).unwrap()), /// (from_hms(1, 4, 5), -86_400) /// ); /// ``` diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 84acca5adf..94d30264bf 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -117,15 +117,15 @@ fn test_time_overflowing_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(11)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), (hmsm(14, 4, 5, 678), 0) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(23)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), (hmsm(2, 4, 5, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::hours(-7)), + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), (hmsm(20, 4, 5, 678), -86_400) ); @@ -144,9 +144,9 @@ fn test_time_overflowing_add() { fn test_time_addassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time += TimeDelta::hours(10); + time += TimeDelta::try_hours(10).unwrap(); assert_eq!(time, hms(22, 12, 12)); - time += TimeDelta::hours(10); + time += TimeDelta::try_hours(10).unwrap(); assert_eq!(time, hms(8, 12, 12)); } @@ -154,9 +154,9 @@ fn test_time_addassignment() { fn test_time_subassignment() { let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); let mut time = hms(12, 12, 12); - time -= TimeDelta::hours(10); + time -= TimeDelta::try_hours(10).unwrap(); assert_eq!(time, hms(2, 12, 12)); - time -= TimeDelta::hours(10); + time -= TimeDelta::try_hours(10).unwrap(); assert_eq!(time, hms(16, 12, 12)); } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index de48f0653c..e1ec682eee 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -256,7 +256,7 @@ mod tests { if let Some(our_result) = Local.from_local_datetime(&date).earliest() { assert_eq!(from_local_time(&date), our_result); } - date += TimeDelta::hours(1); + date += TimeDelta::try_hours(1).unwrap(); } } } diff --git a/src/round.rs b/src/round.rs index 53e7d06234..9625080158 100644 --- a/src/round.rs +++ b/src/round.rs @@ -504,7 +504,7 @@ mod tests { "2012-12-12 18:30:00 UTC" ); assert_eq!( - dt.duration_round(TimeDelta::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( @@ -597,7 +597,7 @@ mod tests { "2012-12-12 18:30:00" ); assert_eq!( - dt.duration_round(TimeDelta::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( @@ -666,7 +666,7 @@ mod tests { "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(TimeDelta::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( @@ -753,7 +753,7 @@ mod tests { "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( diff --git a/src/time_delta.rs b/src/time_delta.rs index fdf2050db2..2e64535256 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -1200,7 +1200,7 @@ mod tests { fn test_duration_const() { const ONE_WEEK: TimeDelta = expect!(TimeDelta::try_weeks(1), ""); const ONE_DAY: TimeDelta = expect!(TimeDelta::try_days(1), ""); - const ONE_HOUR: TimeDelta = TimeDelta::hours(1); + const ONE_HOUR: TimeDelta = expect!(TimeDelta::try_hours(1), ""); const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1); const ONE_SECOND: TimeDelta = TimeDelta::seconds(1); const ONE_MILLI: TimeDelta = TimeDelta::milliseconds(1); From f93508ffd9bff4c0eac1332b4c2c25b03955985b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 07:48:38 +0100 Subject: [PATCH 788/999] Tests: replace `TimeDelta::minutes` with `try_minutes` --- src/datetime/tests.rs | 8 ++++---- src/naive/datetime/tests.rs | 4 ++-- src/round.rs | 40 ++++++++++++++++++------------------- src/time_delta.rs | 4 ++-- tests/wasm.rs | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 0f4516b16b..9f37050648 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1511,20 +1511,20 @@ fn test_datetime_sub_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_sub = datetime; - datetime_sub -= TimeDelta::minutes(90); - assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); + datetime_sub -= TimeDelta::try_minutes(90).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90)); + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index f350ce0122..38509f4549 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -73,7 +73,7 @@ fn test_datetime_addassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += TimeDelta::minutes(10_000_000); + date += TimeDelta::try_minutes(10_000_000).unwrap(); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); date += TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); @@ -84,7 +84,7 @@ fn test_datetime_subassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= TimeDelta::minutes(10_000_000); + date -= TimeDelta::try_minutes(10_000_000).unwrap(); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); date -= TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); diff --git a/src/round.rs b/src/round.rs index 9625080158..9d9d2311bb 100644 --- a/src/round.rs +++ b/src/round.rs @@ -478,7 +478,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down @@ -491,16 +491,16 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(TimeDelta::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( @@ -570,7 +570,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:25:00" ); // round down @@ -584,16 +584,16 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(TimeDelta::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:30:00" ); assert_eq!( @@ -610,7 +610,7 @@ mod tests { fn test_duration_round_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_round(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } @@ -641,7 +641,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down @@ -654,15 +654,15 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( @@ -727,7 +727,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); // would round down @@ -741,15 +741,15 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( @@ -766,7 +766,7 @@ mod tests { fn test_duration_trunc_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } @@ -788,7 +788,7 @@ mod tests { #[test] fn test_duration_trunc_close_to_epoch() { - let span = TimeDelta::minutes(15); + let span = TimeDelta::try_minutes(15).unwrap(); let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); @@ -799,7 +799,7 @@ mod tests { #[test] fn test_duration_round_close_to_epoch() { - let span = TimeDelta::minutes(15); + let span = TimeDelta::try_minutes(15).unwrap(); let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); diff --git a/src/time_delta.rs b/src/time_delta.rs index 2e64535256..988850804e 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -639,7 +639,7 @@ mod tests { assert_eq!(-(days(3) + TimeDelta::seconds(70)), days(-4) + TimeDelta::seconds(86_400 - 70)); let mut d = TimeDelta::default(); - d += TimeDelta::minutes(1); + d += TimeDelta::try_minutes(1).unwrap(); d -= TimeDelta::seconds(30); assert_eq!(d, TimeDelta::seconds(30)); } @@ -1201,7 +1201,7 @@ mod tests { const ONE_WEEK: TimeDelta = expect!(TimeDelta::try_weeks(1), ""); const ONE_DAY: TimeDelta = expect!(TimeDelta::try_days(1), ""); const ONE_HOUR: TimeDelta = expect!(TimeDelta::try_hours(1), ""); - const ONE_MINUTE: TimeDelta = TimeDelta::minutes(1); + const ONE_MINUTE: TimeDelta = expect!(TimeDelta::try_minutes(1), ""); const ONE_SECOND: TimeDelta = TimeDelta::seconds(1); const ONE_MILLI: TimeDelta = TimeDelta::milliseconds(1); const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); diff --git a/tests/wasm.rs b/tests/wasm.rs index 6937da9f71..ceb9b3de97 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -25,7 +25,7 @@ fn now() { let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc(); let diff = utc - actual; assert!( - diff < chrono::TimeDelta::minutes(5), + diff < chrono::TimeDelta::try_minutes(5).unwrap(), "expected {} - {} == {} < 5m (env var: {})", utc, actual, From 2bf3302ce58aadaae06a8a1fb6342295292863da Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 21 Feb 2024 08:09:32 +0100 Subject: [PATCH 789/999] Tests: replace `TimeDelta::seconds` with `try_seconds` --- src/datetime/tests.rs | 12 ++-- src/lib.rs | 16 ++--- src/naive/date/mod.rs | 14 +++-- src/naive/date/tests.rs | 4 +- src/naive/datetime/mod.rs | 86 +++++++++++++++----------- src/naive/datetime/tests.rs | 27 +++++---- src/naive/time/mod.rs | 97 ++++++++++++++++++----------- src/naive/time/tests.rs | 8 +-- src/time_delta.rs | 118 +++++++++++++++++++----------------- 9 files changed, 219 insertions(+), 163 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 9f37050648..5adf16f33c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -580,12 +580,12 @@ fn test_datetime_offset() { let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( - dt + TimeDelta::seconds(3600 + 60 + 1), + dt + TimeDelta::try_seconds(3600 + 60 + 1).unwrap(), Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() ); assert_eq!( dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), - TimeDelta::seconds(-7 * 3600 - 3 * 60 - 3) + TimeDelta::try_seconds(-7 * 3600 - 3 * 60 - 3).unwrap() ); assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); @@ -1474,20 +1474,20 @@ fn test_datetime_add_assign() { let datetime = naivedatetime.and_utc(); let mut datetime_add = datetime; - datetime_add += TimeDelta::seconds(60); - assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); + datetime_add += TimeDelta::try_seconds(60).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + TimeDelta::seconds(60)); + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index ad2312b877..917b39691b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,16 +257,12 @@ //! // arithmetic operations //! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); //! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); -//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::seconds(-2 * 3600 + 2)); -//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2)); -//! assert_eq!( -//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000), -//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap() -//! ); -//! assert_eq!( -//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::seconds(1_000_000_000), -//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap() -//! ); +//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::try_seconds(-2 * 3600 + 2).unwrap()); +//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::try_seconds(2 * 3600 - 2).unwrap()); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); //! ``` //! //! ### Formatting and Parsing diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 1ca2cd0b80..9c6d04b480 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1907,8 +1907,11 @@ impl Datelike for NaiveDate { /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(1).unwrap(), from_ymd(2014, 1, 2)); /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(-1).unwrap(), from_ymd(2013, 12, 31)); /// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(364).unwrap(), from_ymd(2014, 12, 31)); @@ -2056,8 +2059,11 @@ impl Sub for NaiveDate { /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::seconds(-86399), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(1).unwrap(), from_ymd(2013, 12, 31)); /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(-1).unwrap(), from_ymd(2014, 1, 2)); /// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(364).unwrap(), from_ymd(2013, 1, 2)); diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index cde973daf4..6d8c299896 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -454,9 +454,9 @@ fn test_date_add() { } check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::seconds(86399), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), Some((2014, 1, 1))); // always round towards zero - check((2014, 1, 1), TimeDelta::seconds(-86399), Some((2014, 1, 1))); + check((2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), Some((2014, 1, 1))); check((2014, 1, 1), TimeDelta::try_days(1).unwrap(), Some((2014, 1, 2))); check((2014, 1, 1), TimeDelta::try_days(-1).unwrap(), Some((2013, 12, 31))); check((2014, 1, 1), TimeDelta::try_days(364).unwrap(), Some((2014, 12, 31))); diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 8fac7fed5b..b310fdcb24 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -471,11 +471,20 @@ impl NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(1)), Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1)), Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60)), Some(hms(4, 6, 7))); /// assert_eq!( - /// hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400)), + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(4, 6, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(86_400).unwrap()), /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()) /// ); /// @@ -510,9 +519,9 @@ impl NaiveDateTime { /// Some(hmsm(3, 5, 59, 1_800))); /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(800)), /// Some(hmsm(3, 6, 0, 100))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(10)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(10).unwrap()), /// Some(hmsm(3, 6, 9, 300))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(-10)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(-10).unwrap()), /// Some(hmsm(3, 5, 50, 300))); /// assert_eq!(leap.checked_add_signed(TimeDelta::try_days(1).unwrap()), /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); @@ -646,11 +655,20 @@ impl NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(1)), Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1)), Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60)), Some(hms(2, 4, 7))); /// assert_eq!( - /// hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400)), + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(2, 4, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(86_400).unwrap()), /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()) /// ); /// @@ -683,7 +701,7 @@ impl NaiveDateTime { /// Some(hmsm(3, 5, 59, 1_100))); /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(500)), /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_sub_signed(TimeDelta::seconds(60)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_seconds(60).unwrap()), /// Some(hmsm(3, 5, 0, 300))); /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_days(1).unwrap()), /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); @@ -767,7 +785,7 @@ impl NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// assert_eq!( /// d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), - /// TimeDelta::seconds(3600 + 60 + 1) + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() /// ); /// /// // July 8 is 190th day in the year 2016 @@ -776,7 +794,7 @@ impl NaiveDateTime { /// d.and_hms_milli_opt(0, 7, 6, 500) /// .unwrap() /// .signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), - /// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + TimeDelta::milliseconds(500) /// ); /// ``` /// @@ -789,11 +807,11 @@ impl NaiveDateTime { /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!( /// leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), - /// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::milliseconds(500) /// ); /// assert_eq!( /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), - /// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::milliseconds(500) /// ); /// ``` #[must_use] @@ -1578,11 +1596,11 @@ impl Timelike for NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(-1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) + TimeDelta::seconds(3600 + 60), hms(4, 6, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(3600 + 60).unwrap(), hms(4, 6, 7)); /// assert_eq!( -/// hms(3, 5, 7) + TimeDelta::seconds(86_400), +/// hms(3, 5, 7) + TimeDelta::try_seconds(86_400).unwrap(), /// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap() /// ); /// assert_eq!( @@ -1602,12 +1620,12 @@ impl Timelike for NaiveDateTime { /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap + TimeDelta::milliseconds(-500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + TimeDelta::milliseconds(500), hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + TimeDelta::seconds(10), hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + TimeDelta::seconds(-10), hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::milliseconds(500), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), hmsm(3, 5, 50, 300)); /// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), /// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` @@ -1768,11 +1786,11 @@ impl Add for NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); /// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(-1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) - TimeDelta::seconds(3600 + 60), hms(2, 4, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(3600 + 60).unwrap(), hms(2, 4, 7)); /// assert_eq!( -/// hms(3, 5, 7) - TimeDelta::seconds(86_400), +/// hms(3, 5, 7) - TimeDelta::try_seconds(86_400).unwrap(), /// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap() /// ); /// assert_eq!( @@ -1792,10 +1810,10 @@ impl Add for NaiveDateTime { /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap - TimeDelta::milliseconds(200), hmsm(3, 5, 59, 1_100)); /// assert_eq!(leap - TimeDelta::milliseconds(500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - TimeDelta::seconds(60), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), hmsm(3, 5, 0, 300)); /// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` @@ -1942,14 +1960,14 @@ impl Sub for NaiveDateTime { /// let d = from_ymd(2016, 7, 8); /// assert_eq!( /// d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), -/// TimeDelta::seconds(3600 + 60 + 1) +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() /// ); /// /// // July 8 is 190th day in the year 2016 /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!( /// d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), -/// TimeDelta::seconds(189 * 86_400 + 7 * 60 + 6) + TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + TimeDelta::milliseconds(500) /// ); /// ``` /// @@ -1962,11 +1980,11 @@ impl Sub for NaiveDateTime { /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!( /// leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), -/// TimeDelta::seconds(3600) + TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::milliseconds(500) /// ); /// assert_eq!( /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, -/// TimeDelta::seconds(3600) - TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::milliseconds(500) /// ); /// ``` impl Sub for NaiveDateTime { diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 38509f4549..27e884c036 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -15,13 +15,14 @@ fn test_datetime_add() { assert_eq!(lhs.checked_add_signed(rhs), sum); assert_eq!(lhs.checked_sub_signed(-rhs), sum); } + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), TimeDelta::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. @@ -31,16 +32,16 @@ fn test_datetime_add() { check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + TimeDelta::seconds(86399), + max_days_from_year_0 + seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + TimeDelta::seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None); check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), None); let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - TimeDelta::seconds(1), None); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None); check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), None); } @@ -52,19 +53,19 @@ fn test_datetime_sub() { assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero()); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - TimeDelta::seconds(1) + TimeDelta::try_seconds(1).unwrap() ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - TimeDelta::seconds(-1) + TimeDelta::try_seconds(-1).unwrap() ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - TimeDelta::seconds(86399) + TimeDelta::try_seconds(86399).unwrap() ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - TimeDelta::seconds(999_999_999) + TimeDelta::try_seconds(999_999_999).unwrap() ); } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 61a97e1128..778d415e7b 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -702,16 +702,25 @@ impl NaiveTime { /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), /// TimeDelta::milliseconds(975) /// ); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), TimeDelta::seconds(7)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), TimeDelta::seconds(5 * 60)); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), + /// TimeDelta::try_seconds(7).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), + /// TimeDelta::try_seconds(5 * 60).unwrap() + /// ); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), - /// TimeDelta::seconds(3 * 3600) + /// TimeDelta::try_seconds(3 * 3600).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), + /// TimeDelta::try_seconds(-3600).unwrap() /// ); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), TimeDelta::seconds(-3600)); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100) + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::milliseconds(100) /// ); /// ``` /// @@ -723,15 +732,15 @@ impl NaiveTime { /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// # let since = NaiveTime::signed_duration_since; /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), - /// TimeDelta::seconds(1)); + /// TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), /// TimeDelta::milliseconds(1500)); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), - /// TimeDelta::seconds(60)); + /// TimeDelta::try_seconds(60).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), - /// TimeDelta::seconds(1)); + /// TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), - /// TimeDelta::seconds(61)); + /// TimeDelta::try_seconds(61).unwrap()); /// ``` #[must_use] pub const fn signed_duration_since(self, rhs: NaiveTime) -> TimeDelta { @@ -1147,11 +1156,14 @@ impl Timelike for NaiveTime { /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); /// /// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(1), from_hmsm(3, 5, 8, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(60 + 4).unwrap(), +/// from_hmsm(3, 6, 11, 0) +/// ); /// assert_eq!( -/// from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(7 * 60 * 60 - 6 * 60), +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(7 * 60 * 60 - 6 * 60).unwrap(), /// from_hmsm(9, 59, 7, 0) /// ); /// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80)); @@ -1164,8 +1176,8 @@ impl Timelike for NaiveTime { /// ``` /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(22*60*60).unwrap(), from_hmsm(1, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); /// ``` /// @@ -1175,12 +1187,12 @@ impl Timelike for NaiveTime { /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap + TimeDelta::milliseconds(-500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + TimeDelta::seconds(10), from_hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + TimeDelta::seconds(-10), from_hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), from_hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), from_hmsm(3, 5, 50, 300)); /// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), from_hmsm(3, 5, 59, 300)); /// ``` /// @@ -1265,10 +1277,13 @@ impl Add for NaiveTime { /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); /// /// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(60 + 5).unwrap(), +/// from_hmsm(3, 4, 2, 0) +/// ); /// assert_eq!( -/// from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(2 * 60 * 60 + 6 * 60), +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(2 * 60 * 60 + 6 * 60).unwrap(), /// from_hmsm(0, 59, 7, 0) /// ); /// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920)); @@ -1280,7 +1295,7 @@ impl Add for NaiveTime { /// ``` /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); /// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); /// ``` /// @@ -1290,10 +1305,10 @@ impl Add for NaiveTime { /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); /// assert_eq!(leap - TimeDelta::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); /// assert_eq!(leap - TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - TimeDelta::seconds(60), from_hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), from_hmsm(3, 5, 0, 300)); /// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), from_hmsm(3, 6, 0, 300)); /// ``` /// @@ -1383,13 +1398,25 @@ impl Sub for NaiveTime { /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), TimeDelta::milliseconds(25)); /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), TimeDelta::milliseconds(975)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), TimeDelta::seconds(7)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), TimeDelta::seconds(5 * 60)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), TimeDelta::seconds(3 * 3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), TimeDelta::seconds(-3600)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), +/// TimeDelta::try_seconds(7).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), +/// TimeDelta::try_seconds(5 * 60).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), +/// TimeDelta::try_seconds(3 * 3600).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), +/// TimeDelta::try_seconds(-3600).unwrap() +/// ); /// assert_eq!( /// from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// TimeDelta::seconds(3600 + 60 + 1) + TimeDelta::milliseconds(100) +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::milliseconds(100) /// ); /// ``` /// @@ -1399,13 +1426,13 @@ impl Sub for NaiveTime { /// ``` /// # use chrono::{TimeDelta, NaiveTime}; /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::seconds(1)); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), /// TimeDelta::milliseconds(1500)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::seconds(60)); -/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::seconds(1)); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::try_seconds(60).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), -/// TimeDelta::seconds(61)); +/// TimeDelta::try_seconds(61).unwrap()); /// ``` impl Sub for NaiveTime { type Output = TimeDelta; diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 94d30264bf..6ecdaed94e 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -101,8 +101,8 @@ fn test_time_add() { check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(100), hmsm(3, 5, 59, 1_400)); check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(1800), hmsm(3, 6, 1, 100)); - check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap - check!(hmsm(3, 5, 59, 900), TimeDelta::seconds(-86399), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900)); check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900)); check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300)); check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300)); @@ -174,11 +174,11 @@ fn test_time_sub() { check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero()); check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::seconds(3600 + 60 + 1)); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap()); check!( hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 300), - TimeDelta::seconds(3600 + 60) + TimeDelta::milliseconds(900) + TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::milliseconds(900) ); // treats the leap second as if it coincides with the prior non-leap second, diff --git a/src/time_delta.rs b/src/time_delta.rs index 988850804e..1258c475cc 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -622,26 +622,24 @@ mod tests { #[test] fn test_duration() { let days = |d| TimeDelta::try_days(d).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); - assert!(TimeDelta::seconds(1) != TimeDelta::zero()); - assert_eq!(TimeDelta::seconds(1) + TimeDelta::seconds(2), TimeDelta::seconds(3)); + assert!(seconds(1) != TimeDelta::zero()); + assert_eq!(seconds(1) + seconds(2), seconds(3)); + assert_eq!(seconds(86_399) + seconds(4), days(1) + seconds(3)); + assert_eq!(days(10) - seconds(1000), seconds(863_000)); + assert_eq!(days(10) - seconds(1_000_000), seconds(-136_000)); assert_eq!( - TimeDelta::seconds(86_399) + TimeDelta::seconds(4), - days(1) + TimeDelta::seconds(3) - ); - assert_eq!(days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000)); - assert_eq!(days(10) - TimeDelta::seconds(1_000_000), TimeDelta::seconds(-136_000)); - assert_eq!( - days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), + days(2) + seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), days(3) + TimeDelta::nanoseconds(234_567_890) ); assert_eq!(-days(3), days(-3)); - assert_eq!(-(days(3) + TimeDelta::seconds(70)), days(-4) + TimeDelta::seconds(86_400 - 70)); + assert_eq!(-(days(3) + seconds(70)), days(-4) + seconds(86_400 - 70)); let mut d = TimeDelta::default(); d += TimeDelta::try_minutes(1).unwrap(); - d -= TimeDelta::seconds(30); - assert_eq!(d, TimeDelta::seconds(30)); + d -= seconds(30); + assert_eq!(d, seconds(30)); } #[test] @@ -649,10 +647,10 @@ mod tests { assert_eq!(TimeDelta::zero().num_days(), 0); assert_eq!(TimeDelta::try_days(1).unwrap().num_days(), 1); assert_eq!(TimeDelta::try_days(-1).unwrap().num_days(), -1); - assert_eq!(TimeDelta::seconds(86_399).num_days(), 0); - assert_eq!(TimeDelta::seconds(86_401).num_days(), 1); - assert_eq!(TimeDelta::seconds(-86_399).num_days(), 0); - assert_eq!(TimeDelta::seconds(-86_401).num_days(), -1); + assert_eq!(TimeDelta::try_seconds(86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_seconds(-86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().num_days(), -1); assert_eq!(TimeDelta::try_days(i32::MAX as i64).unwrap().num_days(), i32::MAX as i64); assert_eq!(TimeDelta::try_days(i32::MIN as i64).unwrap().num_days(), i32::MIN as i64); } @@ -660,8 +658,8 @@ mod tests { #[test] fn test_duration_num_seconds() { assert_eq!(TimeDelta::zero().num_seconds(), 0); - assert_eq!(TimeDelta::seconds(1).num_seconds(), 1); - assert_eq!(TimeDelta::seconds(-1).num_seconds(), -1); + assert_eq!(TimeDelta::try_seconds(1).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().num_seconds(), -1); assert_eq!(TimeDelta::milliseconds(999).num_seconds(), 0); assert_eq!(TimeDelta::milliseconds(1001).num_seconds(), 1); assert_eq!(TimeDelta::milliseconds(-999).num_seconds(), 0); @@ -670,7 +668,7 @@ mod tests { #[test] fn test_duration_seconds_max_allowed() { - let duration = TimeDelta::seconds(i64::MAX / 1_000); + let duration = TimeDelta::try_seconds(i64::MAX / 1_000).unwrap(); assert_eq!(duration.num_seconds(), i64::MAX / 1_000); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -691,7 +689,7 @@ mod tests { #[test] fn test_duration_seconds_min_allowed() { - let duration = TimeDelta::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + let duration = TimeDelta::try_seconds(i64::MIN / 1_000).unwrap(); // Same as -i64::MAX / 1_000 due to rounding assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -1079,20 +1077,24 @@ mod tests { assert_eq!(TimeDelta::zero() * i32::MIN, TimeDelta::zero()); assert_eq!(TimeDelta::nanoseconds(1) * 0, TimeDelta::zero()); assert_eq!(TimeDelta::nanoseconds(1) * 1, TimeDelta::nanoseconds(1)); - assert_eq!(TimeDelta::nanoseconds(1) * 1_000_000_000, TimeDelta::seconds(1)); - assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::seconds(1)); - assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::seconds(1)); + assert_eq!(TimeDelta::nanoseconds(1) * 1_000_000_000, TimeDelta::try_seconds(1).unwrap()); + assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); + assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); assert_eq!( TimeDelta::nanoseconds(30) * 333_333_333, - TimeDelta::seconds(10) - TimeDelta::nanoseconds(10) + TimeDelta::try_seconds(10).unwrap() - TimeDelta::nanoseconds(10) ); assert_eq!( - (TimeDelta::nanoseconds(1) + TimeDelta::seconds(1) + TimeDelta::try_days(1).unwrap()) + (TimeDelta::nanoseconds(1) + + TimeDelta::try_seconds(1).unwrap() + + TimeDelta::try_days(1).unwrap()) * 3, - TimeDelta::nanoseconds(3) + TimeDelta::seconds(3) + TimeDelta::try_days(3).unwrap() + TimeDelta::nanoseconds(3) + + TimeDelta::try_seconds(3).unwrap() + + TimeDelta::try_days(3).unwrap() ); - assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::seconds(-3)); - assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::seconds(-3)); + assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::try_seconds(-3).unwrap()); + assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::try_seconds(-3).unwrap()); } #[test] @@ -1103,38 +1105,38 @@ mod tests { assert_eq!(TimeDelta::nanoseconds(123_456_789) / -1, -TimeDelta::nanoseconds(123_456_789)); assert_eq!(-TimeDelta::nanoseconds(123_456_789) / -1, TimeDelta::nanoseconds(123_456_789)); assert_eq!(-TimeDelta::nanoseconds(123_456_789) / 1, -TimeDelta::nanoseconds(123_456_789)); - assert_eq!(TimeDelta::seconds(1) / 3, TimeDelta::nanoseconds(333_333_333)); - assert_eq!(TimeDelta::seconds(4) / 3, TimeDelta::nanoseconds(1_333_333_333)); - assert_eq!(TimeDelta::seconds(-1) / 2, TimeDelta::milliseconds(-500)); - assert_eq!(TimeDelta::seconds(1) / -2, TimeDelta::milliseconds(-500)); - assert_eq!(TimeDelta::seconds(-1) / -2, TimeDelta::milliseconds(500)); - assert_eq!(TimeDelta::seconds(-4) / 3, TimeDelta::nanoseconds(-1_333_333_333)); - assert_eq!(TimeDelta::seconds(-4) / -3, TimeDelta::nanoseconds(1_333_333_333)); + assert_eq!(TimeDelta::try_seconds(1).unwrap() / 3, TimeDelta::nanoseconds(333_333_333)); + assert_eq!(TimeDelta::try_seconds(4).unwrap() / 3, TimeDelta::nanoseconds(1_333_333_333)); + assert_eq!(TimeDelta::try_seconds(-1).unwrap() / 2, TimeDelta::milliseconds(-500)); + assert_eq!(TimeDelta::try_seconds(1).unwrap() / -2, TimeDelta::milliseconds(-500)); + assert_eq!(TimeDelta::try_seconds(-1).unwrap() / -2, TimeDelta::milliseconds(500)); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / 3, TimeDelta::nanoseconds(-1_333_333_333)); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / -3, TimeDelta::nanoseconds(1_333_333_333)); } #[test] fn test_duration_sum() { - let duration_list_1 = [TimeDelta::zero(), TimeDelta::seconds(1)]; + let duration_list_1 = [TimeDelta::zero(), TimeDelta::try_seconds(1).unwrap()]; let sum_1: TimeDelta = duration_list_1.iter().sum(); - assert_eq!(sum_1, TimeDelta::seconds(1)); + assert_eq!(sum_1, TimeDelta::try_seconds(1).unwrap()); let duration_list_2 = [ TimeDelta::zero(), - TimeDelta::seconds(1), - TimeDelta::seconds(6), - TimeDelta::seconds(10), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), ]; let sum_2: TimeDelta = duration_list_2.iter().sum(); - assert_eq!(sum_2, TimeDelta::seconds(17)); + assert_eq!(sum_2, TimeDelta::try_seconds(17).unwrap()); let duration_arr = [ TimeDelta::zero(), - TimeDelta::seconds(1), - TimeDelta::seconds(6), - TimeDelta::seconds(10), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), ]; let sum_3: TimeDelta = duration_arr.into_iter().sum(); - assert_eq!(sum_3, TimeDelta::seconds(17)); + assert_eq!(sum_3, TimeDelta::try_seconds(17).unwrap()); } #[test] @@ -1142,7 +1144,7 @@ mod tests { assert_eq!(TimeDelta::zero().to_string(), "P0D"); assert_eq!(TimeDelta::try_days(42).unwrap().to_string(), "PT3628800S"); assert_eq!(TimeDelta::try_days(-42).unwrap().to_string(), "-PT3628800S"); - assert_eq!(TimeDelta::seconds(42).to_string(), "PT42S"); + assert_eq!(TimeDelta::try_seconds(42).unwrap().to_string(), "PT42S"); assert_eq!(TimeDelta::milliseconds(42).to_string(), "PT0.042S"); assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); @@ -1150,7 +1152,7 @@ mod tests { (TimeDelta::try_days(7).unwrap() + TimeDelta::milliseconds(6543)).to_string(), "PT604806.543S" ); - assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-PT86401S"); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().to_string(), "-PT86401S"); assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S"); // the format specifier should have no effect on `TimeDelta` @@ -1162,20 +1164,26 @@ mod tests { #[test] fn test_to_std() { - assert_eq!(TimeDelta::seconds(1).to_std(), Ok(Duration::new(1, 0))); - assert_eq!(TimeDelta::seconds(86_401).to_std(), Ok(Duration::new(86_401, 0))); + assert_eq!(TimeDelta::try_seconds(1).unwrap().to_std(), Ok(Duration::new(1, 0))); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().to_std(), Ok(Duration::new(86_401, 0))); assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(Duration::new(0, 123_000_000))); assert_eq!(TimeDelta::milliseconds(123_765).to_std(), Ok(Duration::new(123, 765_000_000))); assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(Duration::new(0, 777))); assert_eq!(MAX.to_std(), Ok(Duration::new(9_223_372_036_854_775, 807_000_000))); - assert_eq!(TimeDelta::seconds(-1).to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); assert_eq!(TimeDelta::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); } #[test] fn test_from_std() { - assert_eq!(Ok(TimeDelta::seconds(1)), TimeDelta::from_std(Duration::new(1, 0))); - assert_eq!(Ok(TimeDelta::seconds(86_401)), TimeDelta::from_std(Duration::new(86_401, 0))); + assert_eq!( + Ok(TimeDelta::try_seconds(1).unwrap()), + TimeDelta::from_std(Duration::new(1, 0)) + ); + assert_eq!( + Ok(TimeDelta::try_seconds(86_401).unwrap()), + TimeDelta::from_std(Duration::new(86_401, 0)) + ); assert_eq!( Ok(TimeDelta::milliseconds(123)), TimeDelta::from_std(Duration::new(0, 123_000_000)) @@ -1202,7 +1210,7 @@ mod tests { const ONE_DAY: TimeDelta = expect!(TimeDelta::try_days(1), ""); const ONE_HOUR: TimeDelta = expect!(TimeDelta::try_hours(1), ""); const ONE_MINUTE: TimeDelta = expect!(TimeDelta::try_minutes(1), ""); - const ONE_SECOND: TimeDelta = TimeDelta::seconds(1); + const ONE_SECOND: TimeDelta = expect!(TimeDelta::try_seconds(1), ""); const ONE_MILLI: TimeDelta = TimeDelta::milliseconds(1); const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); @@ -1225,7 +1233,7 @@ mod tests { assert!(ONE_NANO != TimeDelta::zero()); assert_eq!( combo, - TimeDelta::seconds(86400 * 7 + 86400 + 3600 + 60 + 1) + TimeDelta::try_seconds(86400 * 7 + 86400 + 3600 + 60 + 1).unwrap() + TimeDelta::nanoseconds(1 + 1_000 + 1_000_000) ); } @@ -1233,7 +1241,7 @@ mod tests { #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { - let duration = TimeDelta::seconds(1); + let duration = TimeDelta::try_seconds(1).unwrap(); let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); } From 2c1b0bea947ca322aa17b79c26c1d0ddc1505488 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 22 Feb 2024 13:36:41 +0100 Subject: [PATCH 790/999] Tests: replace `TimeDelta::milliseconds` with `try_milliseconds` --- src/datetime/tests.rs | 2 +- src/naive/datetime/mod.rs | 42 +++++---- src/naive/time/mod.rs | 57 ++++++++---- src/naive/time/tests.rs | 36 ++++---- src/round.rs | 12 +-- src/time_delta.rs | 186 ++++++++++++++++++++++---------------- 6 files changed, 196 insertions(+), 139 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 5adf16f33c..81e5c1bb59 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -301,7 +301,7 @@ fn test_nanosecond_range() { // Just beyond range let maximum = "2262-04-11T23:47:16.854775804UTC"; let parsed: DateTime = maximum.parse().unwrap(); - let beyond_max = parsed + TimeDelta::milliseconds(300); + let beyond_max = parsed + TimeDelta::try_milliseconds(300).unwrap(); assert!(beyond_max.timestamp_nanos_opt().is_none()); // Far beyond range diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index b310fdcb24..2336bd8a25 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -490,7 +490,7 @@ impl NaiveDateTime { /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!( - /// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::milliseconds(450)), + /// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::try_milliseconds(450).unwrap()), /// Some(hmsm(3, 5, 8, 430)) /// ); /// ``` @@ -513,11 +513,11 @@ impl NaiveDateTime { /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_add_signed(TimeDelta::zero()), /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(-500)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(-500).unwrap()), /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(500)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(500).unwrap()), /// Some(hmsm(3, 5, 59, 1_800))); - /// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(800)), + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(800).unwrap()), /// Some(hmsm(3, 6, 0, 100))); /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(10).unwrap()), /// Some(hmsm(3, 6, 9, 300))); @@ -674,7 +674,7 @@ impl NaiveDateTime { /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); /// assert_eq!( - /// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::milliseconds(670)), + /// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::try_milliseconds(670).unwrap()), /// Some(hmsm(3, 5, 6, 780)) /// ); /// ``` @@ -697,9 +697,9 @@ impl NaiveDateTime { /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()), /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(200)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(200).unwrap()), /// Some(hmsm(3, 5, 59, 1_100))); - /// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(500)), + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(500).unwrap()), /// Some(hmsm(3, 5, 59, 800))); /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_seconds(60).unwrap()), /// Some(hmsm(3, 5, 0, 300))); @@ -794,7 +794,8 @@ impl NaiveDateTime { /// d.and_hms_milli_opt(0, 7, 6, 500) /// .unwrap() /// .signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), - /// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + /// + TimeDelta::try_milliseconds(500).unwrap() /// ); /// ``` /// @@ -807,11 +808,11 @@ impl NaiveDateTime { /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!( /// leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), - /// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() /// ); /// assert_eq!( /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), - /// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::milliseconds(500) + /// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() /// ); /// ``` #[must_use] @@ -1609,7 +1610,7 @@ impl Timelike for NaiveDateTime { /// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); -/// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::milliseconds(450), hmsm(3, 5, 8, 430)); +/// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::try_milliseconds(450).unwrap(), hmsm(3, 5, 8, 430)); /// ``` /// /// Leap seconds are handled, @@ -1621,9 +1622,9 @@ impl Timelike for NaiveDateTime { /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + TimeDelta::milliseconds(-500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + TimeDelta::milliseconds(500), hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); /// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), hmsm(3, 6, 9, 300)); /// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), hmsm(3, 5, 50, 300)); /// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), @@ -1799,7 +1800,7 @@ impl Add for NaiveDateTime { /// ); /// /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); -/// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::milliseconds(670), hmsm(3, 5, 6, 780)); +/// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::try_milliseconds(670).unwrap(), hmsm(3, 5, 6, 780)); /// ``` /// /// Leap seconds are handled, @@ -1811,8 +1812,8 @@ impl Add for NaiveDateTime { /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); /// let leap = hmsm(3, 5, 59, 1_300); /// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - TimeDelta::milliseconds(200), hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - TimeDelta::milliseconds(500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 800)); /// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), hmsm(3, 5, 0, 300)); /// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); @@ -1967,7 +1968,8 @@ impl Sub for NaiveDateTime { /// let d0 = from_ymd(2016, 1, 1); /// assert_eq!( /// d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), -/// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() +/// + TimeDelta::try_milliseconds(500).unwrap() /// ); /// ``` /// @@ -1980,11 +1982,11 @@ impl Sub for NaiveDateTime { /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); /// assert_eq!( /// leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), -/// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() /// ); /// assert_eq!( /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, -/// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::milliseconds(500) +/// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() /// ); /// ``` impl Sub for NaiveDateTime { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 778d415e7b..23b1cb0e55 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -696,11 +696,11 @@ impl NaiveTime { /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), TimeDelta::zero()); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), - /// TimeDelta::milliseconds(25) + /// TimeDelta::try_milliseconds(25).unwrap() /// ); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), - /// TimeDelta::milliseconds(975) + /// TimeDelta::try_milliseconds(975).unwrap() /// ); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), @@ -720,7 +720,7 @@ impl NaiveTime { /// ); /// assert_eq!( /// since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::milliseconds(100) + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() /// ); /// ``` /// @@ -734,7 +734,7 @@ impl NaiveTime { /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), /// TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), - /// TimeDelta::milliseconds(1500)); + /// TimeDelta::try_milliseconds(1500).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), /// TimeDelta::try_seconds(60).unwrap()); /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), @@ -1166,9 +1166,18 @@ impl Timelike for NaiveTime { /// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(7 * 60 * 60 - 6 * 60).unwrap(), /// from_hmsm(9, 59, 7, 0) /// ); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::milliseconds(80), from_hmsm(3, 5, 7, 80)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(280), from_hmsm(3, 5, 8, 230)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + TimeDelta::milliseconds(-980), from_hmsm(3, 5, 6, 970)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 7, 80) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 8, 230) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(-980).unwrap(), +/// from_hmsm(3, 5, 6, 970) +/// ); /// ``` /// /// The addition wraps around. @@ -1188,9 +1197,9 @@ impl Timelike for NaiveTime { /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); /// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + TimeDelta::milliseconds(-500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + TimeDelta::milliseconds(800), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), from_hmsm(3, 6, 0, 100)); /// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), from_hmsm(3, 6, 9, 300)); /// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), from_hmsm(3, 5, 50, 300)); /// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), from_hmsm(3, 5, 59, 300)); @@ -1286,8 +1295,14 @@ impl Add for NaiveTime { /// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(2 * 60 * 60 + 6 * 60).unwrap(), /// from_hmsm(0, 59, 7, 0) /// ); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::milliseconds(80), from_hmsm(3, 5, 6, 920)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) - TimeDelta::milliseconds(280), from_hmsm(3, 5, 7, 670)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 6, 920) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) - TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 7, 670) +/// ); /// ``` /// /// The subtraction wraps around. @@ -1306,8 +1321,8 @@ impl Add for NaiveTime { /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// let leap = from_hmsm(3, 5, 59, 1_300); /// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - TimeDelta::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - TimeDelta::milliseconds(500), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), from_hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 800)); /// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), from_hmsm(3, 5, 0, 300)); /// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), from_hmsm(3, 6, 0, 300)); /// ``` @@ -1396,8 +1411,14 @@ impl Sub for NaiveTime { /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); /// /// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), TimeDelta::milliseconds(25)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), TimeDelta::milliseconds(975)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), +/// TimeDelta::try_milliseconds(25).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), +/// TimeDelta::try_milliseconds(975).unwrap() +/// ); /// assert_eq!( /// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), /// TimeDelta::try_seconds(7).unwrap() @@ -1416,7 +1437,7 @@ impl Sub for NaiveTime { /// ); /// assert_eq!( /// from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::milliseconds(100) +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() /// ); /// ``` /// @@ -1428,7 +1449,7 @@ impl Sub for NaiveTime { /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), -/// TimeDelta::milliseconds(1500)); +/// TimeDelta::try_milliseconds(1500).unwrap()); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::try_seconds(60).unwrap()); /// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::try_seconds(1).unwrap()); /// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 6ecdaed94e..a8754aebd6 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -94,13 +94,17 @@ fn test_time_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900)); - check!(hmsm(3, 5, 59, 900), TimeDelta::milliseconds(100), hmsm(3, 6, 0, 0)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-1800), hmsm(3, 5, 58, 500)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-800), hmsm(3, 5, 59, 500)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(-100), hmsm(3, 5, 59, 1_200)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(100), hmsm(3, 5, 59, 1_400)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(800), hmsm(3, 6, 0, 100)); - check!(hmsm(3, 5, 59, 1_300), TimeDelta::milliseconds(1800), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-1800).unwrap(), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-800).unwrap(), hmsm(3, 5, 59, 500)); + check!( + hmsm(3, 5, 59, 1_300), + TimeDelta::try_milliseconds(-100).unwrap(), + hmsm(3, 5, 59, 1_200) + ); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(1800).unwrap(), hmsm(3, 6, 1, 100)); check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900)); check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900)); @@ -108,8 +112,8 @@ fn test_time_add() { check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300)); // regression tests for #37 - check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), TimeDelta::milliseconds(-9990), hmsm(23, 59, 50, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-990).unwrap(), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-9990).unwrap(), hmsm(23, 59, 50, 10)); } #[test] @@ -173,24 +177,24 @@ fn test_time_sub() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::milliseconds(300)); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::try_milliseconds(300).unwrap()); check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap()); check!( hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 300), - TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::milliseconds(900) + TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::try_milliseconds(900).unwrap() ); // treats the leap second as if it coincides with the prior non-leap second, // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::milliseconds(400)); - //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::milliseconds(1400)); - //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::milliseconds(1400)); + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::try_milliseconds(400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::try_milliseconds(1400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::try_milliseconds(1400).unwrap()); // additional equality: `time1 + duration = time2` is equivalent to // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::milliseconds(400), hmsm(3, 5, 7, 200)); - //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::milliseconds(400), hmsm(3, 5, 7, 200)); + assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); } #[test] diff --git a/src/round.rs b/src/round.rs index 9d9d2311bb..d9b1b9bc0e 100644 --- a/src/round.rs +++ b/src/round.rs @@ -127,7 +127,7 @@ pub trait DurationRound: Sized { /// .and_local_timezone(Utc) /// .unwrap(); /// assert_eq!( - /// dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( @@ -149,7 +149,7 @@ pub trait DurationRound: Sized { /// .and_local_timezone(Utc) /// .unwrap(); /// assert_eq!( - /// dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( @@ -464,7 +464,7 @@ mod tests { ); assert_eq!( - dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); @@ -555,7 +555,7 @@ mod tests { ); assert_eq!( - dt.duration_round(TimeDelta::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.180" ); @@ -627,7 +627,7 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" ); @@ -712,7 +712,7 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_trunc(TimeDelta::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170" ); diff --git a/src/time_delta.rs b/src/time_delta.rs index 1258c475cc..b279995e25 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -227,6 +227,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] + #[deprecated] pub const fn milliseconds(milliseconds: i64) -> TimeDelta { expect!(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } @@ -660,10 +661,10 @@ mod tests { assert_eq!(TimeDelta::zero().num_seconds(), 0); assert_eq!(TimeDelta::try_seconds(1).unwrap().num_seconds(), 1); assert_eq!(TimeDelta::try_seconds(-1).unwrap().num_seconds(), -1); - assert_eq!(TimeDelta::milliseconds(999).num_seconds(), 0); - assert_eq!(TimeDelta::milliseconds(1001).num_seconds(), 1); - assert_eq!(TimeDelta::milliseconds(-999).num_seconds(), 0); - assert_eq!(TimeDelta::milliseconds(-1001).num_seconds(), -1); + assert_eq!(TimeDelta::try_milliseconds(999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(1001).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(-1001).unwrap().num_seconds(), -1); } #[test] @@ -711,8 +712,8 @@ mod tests { #[test] fn test_duration_num_milliseconds() { assert_eq!(TimeDelta::zero().num_milliseconds(), 0); - assert_eq!(TimeDelta::milliseconds(1).num_milliseconds(), 1); - assert_eq!(TimeDelta::milliseconds(-1).num_milliseconds(), -1); + assert_eq!(TimeDelta::try_milliseconds(1).unwrap().num_milliseconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().num_milliseconds(), -1); assert_eq!(TimeDelta::microseconds(999).num_milliseconds(), 0); assert_eq!(TimeDelta::microseconds(1001).num_milliseconds(), 1); assert_eq!(TimeDelta::microseconds(-999).num_milliseconds(), 0); @@ -723,7 +724,7 @@ mod tests { fn test_duration_milliseconds_max_allowed() { // The maximum number of milliseconds acceptable through the constructor is // equal to the number that can be stored in a TimeDelta. - let duration = TimeDelta::milliseconds(i64::MAX); + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); assert_eq!(duration.num_milliseconds(), i64::MAX); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -735,8 +736,9 @@ mod tests { fn test_duration_milliseconds_max_overflow() { // Here we ensure that trying to add one millisecond to the maximum storable // value will fail. - assert!(TimeDelta::milliseconds(i64::MAX) - .checked_add(&TimeDelta::milliseconds(1)) + assert!(TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::try_milliseconds(1).unwrap()) .is_none()); } @@ -745,7 +747,7 @@ mod tests { // The minimum number of milliseconds acceptable through the constructor is // not equal to the number that can be stored in a TimeDelta - there is a // difference of one (i64::MIN vs -i64::MAX). - let duration = TimeDelta::milliseconds(-i64::MAX); + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); assert_eq!(duration.num_milliseconds(), -i64::MAX); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -757,8 +759,9 @@ mod tests { fn test_duration_milliseconds_min_underflow() { // Here we ensure that trying to subtract one millisecond from the minimum // storable value will fail. - assert!(TimeDelta::milliseconds(-i64::MAX) - .checked_sub(&TimeDelta::milliseconds(1)) + assert!(TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::try_milliseconds(1).unwrap()) .is_none()); } @@ -816,7 +819,7 @@ mod tests { // microseconds by creating a TimeDelta with the maximum number of // milliseconds and then checking that the number of microseconds matches // the storage limit. - let duration = TimeDelta::milliseconds(i64::MAX); + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -835,7 +838,8 @@ mod tests { ); // Here we ensure that trying to add one microsecond to the maximum storable // value will fail. - assert!(TimeDelta::milliseconds(i64::MAX) + assert!(TimeDelta::try_milliseconds(i64::MAX) + .unwrap() .checked_add(&TimeDelta::microseconds(1)) .is_none()); } @@ -854,7 +858,7 @@ mod tests { // microseconds by creating a TimeDelta with the minimum number of // milliseconds and then checking that the number of microseconds matches // the storage limit. - let duration = TimeDelta::milliseconds(-i64::MAX); + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); assert!(duration.num_microseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -873,7 +877,8 @@ mod tests { ); // Here we ensure that trying to subtract one microsecond from the minimum // storable value will fail. - assert!(TimeDelta::milliseconds(-i64::MAX) + assert!(TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() .checked_sub(&TimeDelta::microseconds(1)) .is_none()); } @@ -917,7 +922,7 @@ mod tests { // Here we create a TimeDelta with the maximum possible number of nanoseconds // by creating a TimeDelta with the maximum number of milliseconds and then // checking that the number of nanoseconds matches the storage limit. - let duration = TimeDelta::milliseconds(i64::MAX); + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -937,7 +942,8 @@ mod tests { ); // Here we ensure that trying to add one nanosecond to the maximum storable // value will fail. - assert!(TimeDelta::milliseconds(i64::MAX) + assert!(TimeDelta::try_milliseconds(i64::MAX) + .unwrap() .checked_add(&TimeDelta::nanoseconds(1)) .is_none()); } @@ -956,7 +962,7 @@ mod tests { // Here we create a TimeDelta with the minimum possible number of nanoseconds // by creating a TimeDelta with the minimum number of milliseconds and then // checking that the number of nanoseconds matches the storage limit. - let duration = TimeDelta::milliseconds(-i64::MAX); + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); assert!(duration.num_nanoseconds().is_none()); assert_eq!( duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, @@ -976,7 +982,8 @@ mod tests { ); // Here we ensure that trying to subtract one nanosecond from the minimum // storable value will fail. - assert!(TimeDelta::milliseconds(-i64::MAX) + assert!(TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() .checked_sub(&TimeDelta::nanoseconds(1)) .is_none()); } @@ -987,7 +994,7 @@ mod tests { MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, i64::MAX as i128 * 1_000_000 ); - assert_eq!(MAX, TimeDelta::milliseconds(i64::MAX)); + assert_eq!(MAX, TimeDelta::try_milliseconds(i64::MAX).unwrap()); assert_eq!(MAX.num_milliseconds(), i64::MAX); assert_eq!(MAX.num_microseconds(), None); assert_eq!(MAX.num_nanoseconds(), None); @@ -999,7 +1006,7 @@ mod tests { MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, -i64::MAX as i128 * 1_000_000 ); - assert_eq!(MIN, TimeDelta::milliseconds(-i64::MAX)); + assert_eq!(MIN, TimeDelta::try_milliseconds(-i64::MAX).unwrap()); assert_eq!(MIN.num_milliseconds(), -i64::MAX); assert_eq!(MIN.num_microseconds(), None); assert_eq!(MIN.num_nanoseconds(), None); @@ -1007,67 +1014,65 @@ mod tests { #[test] fn test_duration_ord() { - assert!(TimeDelta::milliseconds(1) < TimeDelta::milliseconds(2)); - assert!(TimeDelta::milliseconds(2) > TimeDelta::milliseconds(1)); - assert!(TimeDelta::milliseconds(-1) > TimeDelta::milliseconds(-2)); - assert!(TimeDelta::milliseconds(-2) < TimeDelta::milliseconds(-1)); - assert!(TimeDelta::milliseconds(-1) < TimeDelta::milliseconds(1)); - assert!(TimeDelta::milliseconds(1) > TimeDelta::milliseconds(-1)); - assert!(TimeDelta::milliseconds(0) < TimeDelta::milliseconds(1)); - assert!(TimeDelta::milliseconds(0) > TimeDelta::milliseconds(-1)); - assert!(TimeDelta::milliseconds(1_001) < TimeDelta::milliseconds(1_002)); - assert!(TimeDelta::milliseconds(-1_001) > TimeDelta::milliseconds(-1_002)); + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert!(milliseconds(1) < milliseconds(2)); + assert!(milliseconds(2) > milliseconds(1)); + assert!(milliseconds(-1) > milliseconds(-2)); + assert!(milliseconds(-2) < milliseconds(-1)); + assert!(milliseconds(-1) < milliseconds(1)); + assert!(milliseconds(1) > milliseconds(-1)); + assert!(milliseconds(0) < milliseconds(1)); + assert!(milliseconds(0) > milliseconds(-1)); + assert!(milliseconds(1_001) < milliseconds(1_002)); + assert!(milliseconds(-1_001) > milliseconds(-1_002)); assert!(TimeDelta::nanoseconds(1_234_567_890) < TimeDelta::nanoseconds(1_234_567_891)); assert!(TimeDelta::nanoseconds(-1_234_567_890) > TimeDelta::nanoseconds(-1_234_567_891)); - assert!(TimeDelta::milliseconds(i64::MAX) > TimeDelta::milliseconds(i64::MAX - 1)); - assert!(TimeDelta::milliseconds(-i64::MAX) < TimeDelta::milliseconds(-i64::MAX + 1)); + assert!(milliseconds(i64::MAX) > milliseconds(i64::MAX - 1)); + assert!(milliseconds(-i64::MAX) < milliseconds(-i64::MAX + 1)); } #[test] fn test_duration_checked_ops() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + assert_eq!( - TimeDelta::milliseconds(i64::MAX).checked_add(&TimeDelta::milliseconds(0)), - Some(TimeDelta::milliseconds(i64::MAX)) + milliseconds(i64::MAX).checked_add(&milliseconds(0)), + Some(milliseconds(i64::MAX)) ); assert_eq!( - TimeDelta::milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)), - Some(TimeDelta::milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999)) + milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)), + Some(milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999)) ); - assert!(TimeDelta::milliseconds(i64::MAX) - .checked_add(&TimeDelta::microseconds(1000)) - .is_none()); - assert!(TimeDelta::milliseconds(i64::MAX) - .checked_add(&TimeDelta::nanoseconds(1)) - .is_none()); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::microseconds(1000)).is_none()); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::nanoseconds(1)).is_none()); assert_eq!( - TimeDelta::milliseconds(-i64::MAX).checked_sub(&TimeDelta::milliseconds(0)), - Some(TimeDelta::milliseconds(-i64::MAX)) + milliseconds(-i64::MAX).checked_sub(&milliseconds(0)), + Some(milliseconds(-i64::MAX)) ); assert_eq!( - TimeDelta::milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)), - Some(TimeDelta::milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999)) + milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)), + Some(milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999)) ); - assert!(TimeDelta::milliseconds(-i64::MAX) - .checked_sub(&TimeDelta::milliseconds(1)) - .is_none()); - assert!(TimeDelta::milliseconds(-i64::MAX) - .checked_sub(&TimeDelta::nanoseconds(1)) - .is_none()); + assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none()); + assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none()); } #[test] fn test_duration_abs() { - assert_eq!(TimeDelta::milliseconds(1300).abs(), TimeDelta::milliseconds(1300)); - assert_eq!(TimeDelta::milliseconds(1000).abs(), TimeDelta::milliseconds(1000)); - assert_eq!(TimeDelta::milliseconds(300).abs(), TimeDelta::milliseconds(300)); - assert_eq!(TimeDelta::milliseconds(0).abs(), TimeDelta::milliseconds(0)); - assert_eq!(TimeDelta::milliseconds(-300).abs(), TimeDelta::milliseconds(300)); - assert_eq!(TimeDelta::milliseconds(-700).abs(), TimeDelta::milliseconds(700)); - assert_eq!(TimeDelta::milliseconds(-1000).abs(), TimeDelta::milliseconds(1000)); - assert_eq!(TimeDelta::milliseconds(-1300).abs(), TimeDelta::milliseconds(1300)); - assert_eq!(TimeDelta::milliseconds(-1700).abs(), TimeDelta::milliseconds(1700)); - assert_eq!(TimeDelta::milliseconds(-i64::MAX).abs(), TimeDelta::milliseconds(i64::MAX)); + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert_eq!(milliseconds(1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(300).abs(), milliseconds(300)); + assert_eq!(milliseconds(0).abs(), milliseconds(0)); + assert_eq!(milliseconds(-300).abs(), milliseconds(300)); + assert_eq!(milliseconds(-700).abs(), milliseconds(700)); + assert_eq!(milliseconds(-1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(-1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(-1700).abs(), milliseconds(1700)); + assert_eq!(milliseconds(-i64::MAX).abs(), milliseconds(i64::MAX)); } #[test] @@ -1093,8 +1098,14 @@ mod tests { + TimeDelta::try_seconds(3).unwrap() + TimeDelta::try_days(3).unwrap() ); - assert_eq!(TimeDelta::milliseconds(1500) * -2, TimeDelta::try_seconds(-3).unwrap()); - assert_eq!(TimeDelta::milliseconds(-1500) * 2, TimeDelta::try_seconds(-3).unwrap()); + assert_eq!( + TimeDelta::try_milliseconds(1500).unwrap() * -2, + TimeDelta::try_seconds(-3).unwrap() + ); + assert_eq!( + TimeDelta::try_milliseconds(-1500).unwrap() * 2, + TimeDelta::try_seconds(-3).unwrap() + ); } #[test] @@ -1107,9 +1118,18 @@ mod tests { assert_eq!(-TimeDelta::nanoseconds(123_456_789) / 1, -TimeDelta::nanoseconds(123_456_789)); assert_eq!(TimeDelta::try_seconds(1).unwrap() / 3, TimeDelta::nanoseconds(333_333_333)); assert_eq!(TimeDelta::try_seconds(4).unwrap() / 3, TimeDelta::nanoseconds(1_333_333_333)); - assert_eq!(TimeDelta::try_seconds(-1).unwrap() / 2, TimeDelta::milliseconds(-500)); - assert_eq!(TimeDelta::try_seconds(1).unwrap() / -2, TimeDelta::milliseconds(-500)); - assert_eq!(TimeDelta::try_seconds(-1).unwrap() / -2, TimeDelta::milliseconds(500)); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / 2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(1).unwrap() / -2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / -2, + TimeDelta::try_milliseconds(500).unwrap() + ); assert_eq!(TimeDelta::try_seconds(-4).unwrap() / 3, TimeDelta::nanoseconds(-1_333_333_333)); assert_eq!(TimeDelta::try_seconds(-4).unwrap() / -3, TimeDelta::nanoseconds(1_333_333_333)); } @@ -1145,11 +1165,12 @@ mod tests { assert_eq!(TimeDelta::try_days(42).unwrap().to_string(), "PT3628800S"); assert_eq!(TimeDelta::try_days(-42).unwrap().to_string(), "-PT3628800S"); assert_eq!(TimeDelta::try_seconds(42).unwrap().to_string(), "PT42S"); - assert_eq!(TimeDelta::milliseconds(42).to_string(), "PT0.042S"); + assert_eq!(TimeDelta::try_milliseconds(42).unwrap().to_string(), "PT0.042S"); assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); assert_eq!( - (TimeDelta::try_days(7).unwrap() + TimeDelta::milliseconds(6543)).to_string(), + (TimeDelta::try_days(7).unwrap() + TimeDelta::try_milliseconds(6543).unwrap()) + .to_string(), "PT604806.543S" ); assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().to_string(), "-PT86401S"); @@ -1157,7 +1178,10 @@ mod tests { // the format specifier should have no effect on `TimeDelta` assert_eq!( - format!("{:30}", TimeDelta::try_days(1).unwrap() + TimeDelta::milliseconds(2345)), + format!( + "{:30}", + TimeDelta::try_days(1).unwrap() + TimeDelta::try_milliseconds(2345).unwrap() + ), "PT86402.345S" ); } @@ -1166,12 +1190,18 @@ mod tests { fn test_to_std() { assert_eq!(TimeDelta::try_seconds(1).unwrap().to_std(), Ok(Duration::new(1, 0))); assert_eq!(TimeDelta::try_seconds(86_401).unwrap().to_std(), Ok(Duration::new(86_401, 0))); - assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(Duration::new(0, 123_000_000))); - assert_eq!(TimeDelta::milliseconds(123_765).to_std(), Ok(Duration::new(123, 765_000_000))); + assert_eq!( + TimeDelta::try_milliseconds(123).unwrap().to_std(), + Ok(Duration::new(0, 123_000_000)) + ); + assert_eq!( + TimeDelta::try_milliseconds(123_765).unwrap().to_std(), + Ok(Duration::new(123, 765_000_000)) + ); assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(Duration::new(0, 777))); assert_eq!(MAX.to_std(), Ok(Duration::new(9_223_372_036_854_775, 807_000_000))); assert_eq!(TimeDelta::try_seconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); - assert_eq!(TimeDelta::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); } #[test] @@ -1185,11 +1215,11 @@ mod tests { TimeDelta::from_std(Duration::new(86_401, 0)) ); assert_eq!( - Ok(TimeDelta::milliseconds(123)), + Ok(TimeDelta::try_milliseconds(123).unwrap()), TimeDelta::from_std(Duration::new(0, 123_000_000)) ); assert_eq!( - Ok(TimeDelta::milliseconds(123_765)), + Ok(TimeDelta::try_milliseconds(123_765).unwrap()), TimeDelta::from_std(Duration::new(123, 765_000_000)) ); assert_eq!(Ok(TimeDelta::nanoseconds(777)), TimeDelta::from_std(Duration::new(0, 777))); @@ -1211,7 +1241,7 @@ mod tests { const ONE_HOUR: TimeDelta = expect!(TimeDelta::try_hours(1), ""); const ONE_MINUTE: TimeDelta = expect!(TimeDelta::try_minutes(1), ""); const ONE_SECOND: TimeDelta = expect!(TimeDelta::try_seconds(1), ""); - const ONE_MILLI: TimeDelta = TimeDelta::milliseconds(1); + const ONE_MILLI: TimeDelta = expect!(TimeDelta::try_milliseconds(1), ""); const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); let combo: TimeDelta = ONE_WEEK From 9e667b606e4f4896dea157a8f498d41511c54f7b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 19 Feb 2024 17:09:33 +0100 Subject: [PATCH 791/999] Deprecate panicking `TimeDelta` constructors --- src/time_delta.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index b279995e25..1d05037fb3 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -102,6 +102,7 @@ impl TimeDelta { /// Panics when the duration is out of bounds. #[inline] #[must_use] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_weeks` instead")] pub const fn weeks(weeks: i64) -> TimeDelta { expect!(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") } @@ -129,6 +130,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_days` instead")] pub const fn days(days: i64) -> TimeDelta { expect!(TimeDelta::try_days(days), "TimeDelta::days out of bounds") } @@ -155,6 +157,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_hours` instead")] pub const fn hours(hours: i64) -> TimeDelta { expect!(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") } @@ -180,6 +183,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_minutes` instead")] pub const fn minutes(minutes: i64) -> TimeDelta { expect!(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") } @@ -204,6 +208,7 @@ impl TimeDelta { /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). #[inline] #[must_use] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_seconds` instead")] pub const fn seconds(seconds: i64) -> TimeDelta { expect!(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") } @@ -227,7 +232,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] - #[deprecated] + #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_milliseconds` instead")] pub const fn milliseconds(milliseconds: i64) -> TimeDelta { expect!(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } @@ -683,6 +688,7 @@ mod tests { } #[test] + #[allow(deprecated)] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_max_overflow_panic() { let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); @@ -704,6 +710,7 @@ mod tests { } #[test] + #[allow(deprecated)] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_min_underflow_panic() { let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); @@ -766,6 +773,7 @@ mod tests { } #[test] + #[allow(deprecated)] #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] fn test_duration_milliseconds_min_underflow_panic() { // Here we ensure that trying to create a value one millisecond below the From 9fdb59638eda6b0e605edfeb59499730a2094a9b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 6 Mar 2024 14:10:05 +0100 Subject: [PATCH 792/999] Prepare 0.4.35 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 904009d9ad..a1dbb0e2e9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-02-11 -version: 0.4.34 +date-released: 2024-03-06 +version: 0.4.35 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 1a3aef330b..308d307d3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.34" +version = "0.4.35" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 2d2f1a9670a807db561a23711b76ebed20164377 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 6 Mar 2024 15:45:18 +0100 Subject: [PATCH 793/999] Correct timestamp deprecation notes --- src/naive/datetime/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 2336bd8a25..0ce8f6828e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -135,7 +135,7 @@ impl NaiveDateTime { /// /// 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) - #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_millis` instead")] + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_millis` instead")] #[inline] #[must_use] pub const fn from_timestamp_millis(millis: i64) -> Option { @@ -150,7 +150,7 @@ impl NaiveDateTime { /// /// 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) - #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_micros` instead")] + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_micros` instead")] #[inline] #[must_use] pub const fn from_timestamp_micros(micros: i64) -> Option { @@ -167,7 +167,7 @@ impl NaiveDateTime { /// /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` /// (more than ca. 262,000 years away from common era) - #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp_nanos` instead")] + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_nanos` instead")] #[inline] #[must_use] pub const fn from_timestamp_nanos(nanos: i64) -> Option { @@ -190,7 +190,7 @@ impl NaiveDateTime { /// 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). - #[deprecated(since = "0.4.34", note = "use `DateTime::from_timestamp` instead")] + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp` instead")] #[inline] #[must_use] pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { @@ -352,7 +352,7 @@ 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. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp()` instead")] #[inline] #[must_use] pub const fn timestamp(&self) -> i64 { @@ -363,7 +363,7 @@ 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. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_millis()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_millis()` instead")] #[inline] #[must_use] pub const fn timestamp_millis(&self) -> i64 { @@ -374,7 +374,7 @@ 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. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_micros()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_micros()` instead")] #[inline] #[must_use] pub const fn timestamp_micros(&self) -> i64 { @@ -413,7 +413,7 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 /// and 2262-04-11T23:47:16.854775807. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_nanos_opt()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub const fn timestamp_nanos_opt(&self) -> Option { @@ -424,7 +424,7 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_subsec_millis()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_millis()` instead")] #[inline] #[must_use] pub const fn timestamp_subsec_millis(&self) -> u32 { @@ -435,7 +435,7 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999,999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. - #[deprecated(since = "0.4.34", note = "use `.and_utc().timestamp_subsec_micros()` instead")] + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_micros()` instead")] #[inline] #[must_use] pub const fn timestamp_subsec_micros(&self) -> u32 { @@ -446,6 +446,7 @@ impl NaiveDateTime { /// /// The return value ranges from 0 to 999,999,999, /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. + #[deprecated(since = "0.4.36", note = "use `.and_utc().timestamp_subsec_nanos()` instead")] pub const fn timestamp_subsec_nanos(&self) -> u32 { self.and_utc().timestamp_subsec_nanos() } From 31145976e1a305215c2ccd60a870d2cb52db9ff1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 6 Mar 2024 18:46:14 +0100 Subject: [PATCH 794/999] CI: Lint benchmarks --- .github/workflows/lint.yml | 4 ++-- bench/benches/chrono.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b0917746ce..65840a0078 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,10 +29,10 @@ jobs: cargo clippy --target=x86_64-pc-windows-msvc --all-targets --color=always \ -- -D warnings - run: | - cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ + cargo clippy --manifest-path fuzz/Cargo.toml --all-targets --color=always \ -- -D warnings - run: | - cargo clippy --manifest-path bench/Cargo.toml --color=always \ + cargo clippy --manifest-path bench/Cargo.toml --all-targets --color=always \ -- -D warnings env: RUSTFLAGS: "-Dwarnings" diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 528b7e8195..925c2939f1 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -208,7 +208,7 @@ fn bench_format_manual(c: &mut Criterion) { fn bench_naivedate_add_signed(c: &mut Criterion) { let date = NaiveDate::from_ymd_opt(2023, 7, 29).unwrap(); - let extra = TimeDelta::days(25); + let extra = TimeDelta::try_days(25).unwrap(); c.bench_function("bench_naivedate_add_signed", |b| { b.iter(|| black_box(date).checked_add_signed(extra).unwrap()) }); From e79fe2187d505f7215230612c869b20c84b61994 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Thu, 7 Mar 2024 00:45:29 +0000 Subject: [PATCH 795/999] Remove unnessary Copy and Send impls Fuchsia is doing an audit of chrono 0.4.34, and we noticed an unncessary unsafe of `Send` for `DateTime`. While it's valid since `Tz::Offset` is `Send`, and `NaiveDateTime` only has `u32` fields, there's a potential hazard if `NaiveDateTime` ever grows unsendable fields (unlikely as that is). This (and the `Copy` impl) were added 9 years ago to fix #25, which stemmed from early versions of associate traits not working properly with auto-traits. This has since been fixed, and is no longer necessary with the MSRV 1.61.0. --- src/datetime/mod.rs | 6 +----- src/datetime/tests.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index bb157f836b..1834c1cc6b 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -49,7 +49,7 @@ mod tests; /// There are some constructors implemented here (the `from_*` methods), but /// the general-purpose constructors are all via the methods on the /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. -#[derive(Clone)] +#[derive(Copy, Clone)] #[cfg_attr( any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), derive(Archive, Deserialize, Serialize), @@ -1386,10 +1386,6 @@ impl Timelike for DateTime { } } -// we need them as automatic impls cannot handle associated types -impl Copy for DateTime where ::Offset: Copy {} -unsafe impl Send for DateTime where ::Offset: Send {} - impl PartialEq> for DateTime { fn eq(&self, other: &DateTime) -> bool { self.datetime == other.datetime diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 81e5c1bb59..4d81ce7a73 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1866,3 +1866,12 @@ fn nano_roundrip() { assert_eq!(nanos, nanos2); } } + +#[test] +fn test_datetime_utc_is_copy_and_send() { + fn is_copy(_: T) {} + fn is_send(_: T) {} + + is_copy(DateTime::::MIN_UTC); + is_send(DateTime::::MIN_UTC); +} From bcb53864f789c609d973290b1842117736ceaba9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 7 Mar 2024 09:27:22 +0100 Subject: [PATCH 796/999] Remove duplicate test --- src/datetime/tests.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 4d81ce7a73..81e5c1bb59 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1866,12 +1866,3 @@ fn nano_roundrip() { assert_eq!(nanos, nanos2); } } - -#[test] -fn test_datetime_utc_is_copy_and_send() { - fn is_copy(_: T) {} - fn is_send(_: T) {} - - is_copy(DateTime::::MIN_UTC); - is_send(DateTime::::MIN_UTC); -} From d40417fd1db766965f93d421627501f518b97f2d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Feb 2024 22:07:20 +0100 Subject: [PATCH 797/999] Improve error checking in `naive_date_time_from_system_time` --- src/offset/local/windows.rs | 63 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index e1ec682eee..dc8393b49a 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -9,7 +9,6 @@ // except according to those terms. use std::cmp::Ordering; -use std::convert::TryFrom; use std::mem::MaybeUninit; use std::ptr; @@ -144,39 +143,63 @@ impl TzInfo { Some(TzInfo { std_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.StandardBias) * 60)?, dst_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.DaylightBias) * 60)?, - std_transition: system_time_from_naive_date_time(tz_info.StandardDate, year), - dst_transition: system_time_from_naive_date_time(tz_info.DaylightDate, year), + std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?, + dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?, }) } } -fn system_time_from_naive_date_time(st: SYSTEMTIME, year: i32) -> Option { +/// Resolve a `SYSTEMTIME` object to an `Option`. +/// +/// A `SYSTEMTIME` within a `TIME_ZONE_INFORMATION` struct can be zero to indicate there is no +/// transition. +/// If it has year, month and day values it is a concrete date. +/// If the year is missing the `SYSTEMTIME` is a rule, which this method resolves for the provided +/// year. A rule has a month, weekday, and nth weekday of the month as components. +/// +/// Returns `Err` if any of the values is invalid, which should never happen. +fn naive_date_time_from_system_time( + st: SYSTEMTIME, + year: i32, +) -> Result, ()> { if st.wYear == 0 && st.wMonth == 0 { - return None; // No DST transitions for this year in this timezone. + return Ok(None); } let time = NaiveTime::from_hms_milli_opt( st.wHour as u32, st.wMinute as u32, st.wSecond as u32, st.wMilliseconds as u32, - )?; - // In Chrono's Weekday, Monday is 0 whereas in SYSTEMTIME Monday is 1 and Sunday is 0. - // Therefore we move back one day after converting the u16 value to a Weekday. - let day_of_week = Weekday::try_from(u8::try_from(st.wDayOfWeek).ok()?).ok()?.pred(); + ) + .ok_or(())?; + if st.wYear != 0 { - return NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32) - .map(|d| d.and_time(time)); + // We have a concrete date. + let date = + NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).ok_or(())?; + return Ok(Some(date.and_time(time))); } - let date = if let Some(date) = - NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, st.wDay as u8) - { - date - } else if st.wDay == 5 { - NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, day_of_week, 4)? - } else { - return None; + + // Resolve a rule with month, weekday, and nth weekday of the month to a date in the current + // year. + let weekday = match st.wDayOfWeek { + 0 => Weekday::Sun, + 1 => Weekday::Mon, + 2 => Weekday::Tue, + 3 => Weekday::Wed, + 4 => Weekday::Thu, + 5 => Weekday::Fri, + 6 => Weekday::Sat, + _ => return Err(()), + }; + let nth_day = match st.wDay { + 1..=5 => st.wDay as u8, + _ => return Err(()), }; - Some(date.and_time(time)) + let date = NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, nth_day) + .or_else(|| NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, 4)) + .ok_or(())?; // `st.wMonth` must be invalid + Ok(Some(date.and_time(time))) } #[cfg(test)] From db1c8eff167d7b3461a627db8bf15f9c2d00f43c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Feb 2024 06:38:39 +0100 Subject: [PATCH 798/999] Guard against integer overflow in `TzInfo::for_year` --- src/offset/local/windows.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index dc8393b49a..639710c8a0 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -140,9 +140,17 @@ impl TzInfo { } tz_info.assume_init() }; + let std_offset = (tz_info.Bias) + .checked_add(tz_info.StandardBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; + let dst_offset = (tz_info.Bias) + .checked_add(tz_info.DaylightBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; Some(TzInfo { - std_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.StandardBias) * 60)?, - dst_offset: FixedOffset::west_opt((tz_info.Bias + tz_info.DaylightBias) * 60)?, + std_offset, + dst_offset, std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?, dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?, }) From 4ac0615ddb5afb5ba05361da3cd40d01b848acf9 Mon Sep 17 00:00:00 2001 From: Moritz Oberhauser Date: Fri, 8 Mar 2024 23:29:54 +0100 Subject: [PATCH 799/999] Streamline `test_date_(add/sub)_days` --- src/naive/date/tests.rs | 50 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 6d8c299896..396f60295b 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -504,43 +504,45 @@ fn test_date_sub() { #[test] fn test_date_add_days() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: Days, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); - assert_eq!(lhs.checked_add_days(rhs), sum); + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_days(days), rhs); } + let ymd = NaiveDate::from_ymd_opt; - check((2014, 1, 1), Days::new(0), Some((2014, 1, 1))); + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); // always round towards zero - check((2014, 1, 1), Days::new(1), Some((2014, 1, 2))); - check((2014, 1, 1), Days::new(364), Some((2014, 12, 31))); - check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1))); + check(ymd(2014, 1, 1), Days::new(1), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), Days::new(364), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), Days::new(365 * 4 + 1), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), Days::new(365 * 400 + 97), ymd(2414, 1, 1)); - check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1))); + check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1)); // overflow check - check((0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); + check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31)); + check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); } #[test] fn test_date_sub_days() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Days) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); - assert_eq!(lhs - diff, rhs); + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_sub_days(days), rhs); } + let ymd = NaiveDate::from_ymd_opt; - check((2014, 1, 1), (2014, 1, 1), Days::new(0)); - check((2014, 1, 2), (2014, 1, 1), Days::new(1)); - check((2014, 12, 31), (2014, 1, 1), Days::new(364)); - check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Days::new(365 * 400 + 97)); + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); + check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1)); + check(ymd(2014, 12, 31), Days::new(364), ymd(2014, 1, 1)); + check(ymd(2015, 1, 3), Days::new(365 + 2), ymd(2014, 1, 1)); + check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1)); + check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1)); - check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap())); - check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); + check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1)); + check( + ymd(0, 1, 1), + Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()), + ymd(MIN_YEAR, 1, 1), + ); } #[test] From 50c0e6b93c4c14640acbfc3b254601019d3cea2d Mon Sep 17 00:00:00 2001 From: Moritz Oberhauser Date: Sat, 9 Mar 2024 13:16:15 +0100 Subject: [PATCH 800/999] Streamline `test_date_signed_duration_since` --- src/naive/date/tests.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 396f60295b..59e4e2cae3 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -483,23 +483,22 @@ fn test_date_add() { } #[test] -fn test_date_sub() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: TimeDelta) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let rhs = NaiveDate::from_ymd_opt(y2, m2, d2).unwrap(); - assert_eq!(lhs.signed_duration_since(rhs), diff); - assert_eq!(rhs.signed_duration_since(lhs), -diff); +fn test_date_signed_duration_since() { + fn check(lhs: Option, rhs: Option, delta: TimeDelta) { + assert_eq!(lhs.unwrap().signed_duration_since(rhs.unwrap()), delta); + assert_eq!(rhs.unwrap().signed_duration_since(lhs.unwrap()), -delta); } + let ymd = NaiveDate::from_ymd_opt; - check((2014, 1, 1), (2014, 1, 1), TimeDelta::zero()); - check((2014, 1, 2), (2014, 1, 1), TimeDelta::try_days(1).unwrap()); - check((2014, 12, 31), (2014, 1, 1), TimeDelta::try_days(364).unwrap()); - check((2015, 1, 3), (2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap()); - check((2018, 1, 1), (2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); - check((2414, 1, 1), (2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); + check(ymd(2014, 1, 1), ymd(2014, 1, 1), TimeDelta::zero()); + check(ymd(2014, 1, 2), ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap()); + check(ymd(2014, 12, 31), ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap()); + check(ymd(2015, 1, 3), ymd(2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap()); + check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); + check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); - check((MAX_YEAR, 12, 31), (0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap()); - check((MIN_YEAR, 1, 1), (0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap()); + check(ymd(MAX_YEAR, 12, 31), ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap()); + check(ymd(MIN_YEAR, 1, 1), ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap()); } #[test] From c1cb93608efec43ce4cb59194db23a07e5f35af5 Mon Sep 17 00:00:00 2001 From: Moritz Oberhauser Date: Sat, 9 Mar 2024 13:27:01 +0100 Subject: [PATCH 801/999] Streamline `test_date_checked_add_signed` --- src/naive/date/tests.rs | 57 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 59e4e2cae3..ce5ffa4a68 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -445,41 +445,40 @@ fn test_date_pred() { } #[test] -fn test_date_add() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: TimeDelta, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd_opt(y1, m1, d1).unwrap(); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).unwrap()); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); +fn test_date_checked_add_signed() { + fn check(lhs: Option, delta: TimeDelta, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_signed(delta), rhs); + assert_eq!(lhs.unwrap().checked_sub_signed(-delta), rhs); } + let ymd = NaiveDate::from_ymd_opt; - check((2014, 1, 1), TimeDelta::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), Some((2014, 1, 1))); + check(ymd(2014, 1, 1), TimeDelta::zero(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), ymd(2014, 1, 1)); // always round towards zero - check((2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), Some((2014, 1, 1))); - check((2014, 1, 1), TimeDelta::try_days(1).unwrap(), Some((2014, 1, 2))); - check((2014, 1, 1), TimeDelta::try_days(-1).unwrap(), Some((2013, 12, 31))); - check((2014, 1, 1), TimeDelta::try_days(364).unwrap(), Some((2014, 12, 31))); - check((2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), Some((2018, 1, 1))); - check((2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), Some((2414, 1, 1))); + check(ymd(2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap(), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), TimeDelta::try_days(-1).unwrap(), ymd(2013, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap(), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), ymd(2414, 1, 1)); - check((-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), Some((5, 1, 1))); + check(ymd(-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), ymd(5, 1, 1)); // overflow check check( - (0, 1, 1), + ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), - Some((MAX_YEAR, 12, 31)), + ymd(MAX_YEAR, 12, 31), ); - check((0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); - check((0, 1, 1), TimeDelta::max_value(), None); + check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::max_value(), None); check( - (0, 1, 1), + ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), - Some((MIN_YEAR, 1, 1)), + ymd(MIN_YEAR, 1, 1), ); - check((0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); - check((0, 1, 1), TimeDelta::min_value(), None); + check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::min_value(), None); } #[test] @@ -497,8 +496,16 @@ fn test_date_signed_duration_since() { check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); - check(ymd(MAX_YEAR, 12, 31), ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap()); - check(ymd(MIN_YEAR, 1, 1), ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap()); + check( + ymd(MAX_YEAR, 12, 31), + ymd(0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); + check( + ymd(MIN_YEAR, 1, 1), + ymd(0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); } #[test] From 1aabe844ed1d8b0fbf8c05ce9552c2fb10104122 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Mar 2024 07:05:32 +0100 Subject: [PATCH 802/999] Update windows-bindgen to 0.55 --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 26 ++++++++++++-------------- src/offset/local/win_bindings.txt | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 308d307d3c..5349c2c6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.54" } # The MSRV of its windows-metatada 0.54 dependency is 1.70 +windows-bindgen = { version = "0.55" } # The MSRV of its windows-metatada dependency is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 39060adadd..ca2def1e65 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,10 +1,8 @@ -// Bindings generated by `windows-bindgen` 0.54.0 - #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] -::windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); -::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); -::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); -::windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); +windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); +windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); +windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); +windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); pub type BOOL = i32; pub type BOOLEAN = u8; #[repr(C)] @@ -19,8 +17,8 @@ pub struct DYNAMIC_TIME_ZONE_INFORMATION { pub TimeZoneKeyName: [u16; 128], pub DynamicDaylightTimeDisabled: BOOLEAN, } -impl ::core::marker::Copy for DYNAMIC_TIME_ZONE_INFORMATION {} -impl ::core::clone::Clone for DYNAMIC_TIME_ZONE_INFORMATION { +impl Copy for DYNAMIC_TIME_ZONE_INFORMATION {} +impl Clone for DYNAMIC_TIME_ZONE_INFORMATION { fn clone(&self) -> Self { *self } @@ -30,8 +28,8 @@ pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } -impl ::core::marker::Copy for FILETIME {} -impl ::core::clone::Clone for FILETIME { +impl Copy for FILETIME {} +impl Clone for FILETIME { fn clone(&self) -> Self { *self } @@ -47,8 +45,8 @@ pub struct SYSTEMTIME { pub wSecond: u16, pub wMilliseconds: u16, } -impl ::core::marker::Copy for SYSTEMTIME {} -impl ::core::clone::Clone for SYSTEMTIME { +impl Copy for SYSTEMTIME {} +impl Clone for SYSTEMTIME { fn clone(&self) -> Self { *self } @@ -63,8 +61,8 @@ pub struct TIME_ZONE_INFORMATION { pub DaylightDate: SYSTEMTIME, pub DaylightBias: i32, } -impl ::core::marker::Copy for TIME_ZONE_INFORMATION {} -impl ::core::clone::Clone for TIME_ZONE_INFORMATION { +impl Copy for TIME_ZONE_INFORMATION {} +impl Clone for TIME_ZONE_INFORMATION { fn clone(&self) -> Self { *self } diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt index 7fb3e2fa1c..fc00509cfb 100644 --- a/src/offset/local/win_bindings.txt +++ b/src/offset/local/win_bindings.txt @@ -1,5 +1,5 @@ --out src/offset/local/win_bindings.rs ---config flatten sys +--config flatten sys no-bindgen-comment --filter Windows.Win32.System.Time.GetTimeZoneInformationForYear Windows.Win32.System.Time.SystemTimeToFileTime From 248683338d47ad88068b4b63cd6ff1d91bf2125f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Mar 2024 11:24:15 +0100 Subject: [PATCH 803/999] Avoid duplicate imports --- src/datetime/mod.rs | 2 +- src/format/formatting.rs | 4 +--- src/format/mod.rs | 2 +- src/format/strftime.rs | 2 +- src/round.rs | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1834c1cc6b..6715dfc843 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 date and time with time zone. -#[cfg(all(not(feature = "std"), feature = "alloc"))] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::string::String; use core::borrow::Borrow; use core::cmp::Ordering; diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f3448f94fe..35c253fff1 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -3,7 +3,7 @@ //! Date and time formatting routines. -#[cfg(all(not(feature = "std"), feature = "alloc"))] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::string::{String, ToString}; #[cfg(feature = "alloc")] use core::borrow::Borrow; @@ -20,8 +20,6 @@ use crate::{NaiveDate, NaiveTime, Weekday}; #[cfg(feature = "alloc")] use super::locales; -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] -use super::Locale; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(feature = "alloc")] diff --git a/src/format/mod.rs b/src/format/mod.rs index 75bc02ee31..c2401a7fc1 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -30,7 +30,7 @@ //! # Ok::<(), chrono::ParseError>(()) //! ``` -#[cfg(all(not(feature = "std"), feature = "alloc"))] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::boxed::Box; use core::fmt; use core::str::FromStr; diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 26fa391201..997faa66c7 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -165,7 +165,7 @@ use super::{locales, Locale}; use super::{Fixed, InternalInternal, Item, Numeric, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] use super::{ParseError, BAD_FORMAT}; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::vec::Vec; /// Parsing iterator for `strftime`-like format strings. diff --git a/src/round.rs b/src/round.rs index d9b1b9bc0e..8a76cb3ddb 100644 --- a/src/round.rs +++ b/src/round.rs @@ -6,7 +6,6 @@ use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike}; use core::cmp::Ordering; use core::fmt; -use core::marker::Sized; use core::ops::{Add, Sub}; /// Extension trait for subsecond rounding or truncation to a maximum number From 920e73b033429e8ed2a87fb4ed93c7e299c01cdf Mon Sep 17 00:00:00 2001 From: avoidalone Date: Mon, 11 Mar 2024 14:55:09 +0800 Subject: [PATCH 804/999] chore: fix some typos Signed-off-by: avoidalone --- src/datetime/tests.rs | 2 +- src/naive/datetime/mod.rs | 2 +- src/offset/local/unix.rs | 2 +- src/offset/local/windows.rs | 2 +- tests/dateutils.rs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 81e5c1bb59..2309af11a7 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -603,7 +603,7 @@ fn signed_duration_since_autoref() { 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 diff1 = dt1 - &dt2; // We can choose to subtract rhs by reference let diff2 = dt2 - dt1; // Or consume rhs assert_eq!(diff1, -diff2); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 0ce8f6828e..5395e396ed 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -909,7 +909,7 @@ impl NaiveDateTime { /// This can fail in cases where the local time represented by the `NaiveDateTime` /// is not a valid local timestamp in the target timezone due to an offset transition /// for example if the target timezone had a change from +00:00 to +01:00 - /// occuring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 + /// occurring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 /// could never occur. Similarly, if the offset transitioned in the opposite direction /// then there would be two local times of 2015-09-05 23:56:04, one at +00:00 and one /// at +01:00. diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 0b5bfefe70..a2c2724380 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -29,7 +29,7 @@ fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { } // we have to store the `Cache` in an option as it can't -// be initalized in a static context. +// be initialized in a static context. thread_local! { static TZ_INFO: RefCell> = Default::default(); } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 639710c8a0..8256baea80 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -273,7 +273,7 @@ mod tests { SystemTimeToFileTime(st, init.as_mut_ptr()); } // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is - // initalized. + // initialized. let filetime = unsafe { init.assume_init() }; let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 34f4ac7e3a..a57d439d30 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -28,8 +28,8 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // assert_eq!("", date_command_str); // } - // This is used while a decision is made wheter the `date` output needs to - // be exactly matched, or whether LocalResult::Ambigious should be handled + // This is used while a decision is made whether the `date` output needs to + // be exactly matched, or whether LocalResult::Ambiguous should be handled // differently let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); From 9511dd29e6b12794b03fadf05095cd7300fd5874 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 9 Mar 2024 13:01:44 +0100 Subject: [PATCH 805/999] Rename `LocalResult` to `MappedLocalTime`, add alias --- README.md | 2 +- src/datetime/rustc_serialize.rs | 12 +- src/datetime/serde.rs | 10 +- src/datetime/tests.rs | 14 +-- src/format/parsed.rs | 14 +-- src/lib.rs | 12 +- src/naive/datetime/mod.rs | 4 +- src/naive/datetime/tests.rs | 6 +- src/offset/fixed.rs | 10 +- src/offset/local/mod.rs | 164 ++++++++++++++------------- src/offset/local/tz_info/rule.rs | 48 ++++---- src/offset/local/tz_info/timezone.rs | 26 ++--- src/offset/local/unix.rs | 16 +-- src/offset/local/windows.rs | 16 +-- src/offset/mod.rs | 153 +++++++++++++------------ src/offset/utc.rs | 10 +- tests/dateutils.rs | 8 +- 17 files changed, 267 insertions(+), 258 deletions(-) diff --git a/README.md b/README.md index 3e4266fb77..bfe17fcbf9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Chrono aims to provide all functionality needed to do correct operations on date * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware by default, with separate timezone-naive types. * Operations that may produce an invalid or ambiguous date and time return `Option` or - [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). + [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). * Configurable parsing and formatting with an `strftime` inspired date and time formatting syntax. * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with the current timezone of the OS. diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index f704b2f97b..5236a52fad 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -2,7 +2,7 @@ use super::DateTime; use crate::format::SecondsFormat; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; +use crate::offset::{FixedOffset, MappedLocalTime, TimeZone, Utc}; use core::fmt; use core::ops::Deref; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -13,16 +13,16 @@ impl Encodable for DateTime { } } -// lik? function to convert a LocalResult into a serde-ish Result -fn from(me: LocalResult, d: &mut D) -> Result +// Function to convert a MappedLocalTime into a serde-ish Result +fn from(me: MappedLocalTime, d: &mut D) -> Result where D: Decoder, T: fmt::Display, { match me { - LocalResult::None => Err(d.error("value is not a legal timestamp")), - LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - LocalResult::Single(val) => Ok(val), + MappedLocalTime::None => Err(d.error("value is not a legal timestamp")), + MappedLocalTime::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), + MappedLocalTime::Single(val) => Ok(val), } } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 4ef9f95515..5e66f5786e 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1251,7 +1251,7 @@ mod tests { #[test] fn test_serde_no_offset_debug() { - use crate::{LocalResult, NaiveDate, NaiveDateTime, Offset}; + use crate::{MappedLocalTime, NaiveDate, NaiveDateTime, Offset}; use core::fmt::Debug; #[derive(Clone)] @@ -1266,14 +1266,14 @@ mod tests { fn from_offset(_state: &TestTimeZone) -> TestTimeZone { TestTimeZone } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(TestTimeZone) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) } fn offset_from_local_datetime( &self, _local: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(TestTimeZone) + ) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { TestTimeZone diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2309af11a7..126e1d34d1 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -3,7 +3,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; +use crate::{Datelike, Days, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; #[derive(Clone)] struct DstTester; @@ -31,14 +31,14 @@ impl TimeZone for DstTester { DstTester } - fn offset_from_local_date(&self, _: &NaiveDate) -> crate::LocalResult { + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { unimplemented!() } fn offset_from_local_datetime( &self, local: &NaiveDateTime, - ) -> crate::LocalResult { + ) -> crate::MappedLocalTime { let local_to_winter_transition_start = NaiveDate::from_ymd_opt( local.year(), DstTester::TO_WINTER_MONTH_DAY.0, @@ -72,19 +72,19 @@ impl TimeZone for DstTester { .and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap()); if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { - LocalResult::Single(DstTester::summer_offset()) + MappedLocalTime::Single(DstTester::summer_offset()) } else if *local >= local_to_winter_transition_start && *local < local_to_summer_transition_start { - LocalResult::Single(DstTester::winter_offset()) + MappedLocalTime::Single(DstTester::winter_offset()) } else if *local >= local_to_winter_transition_end && *local < local_to_winter_transition_start { - LocalResult::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) + MappedLocalTime::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) } else if *local >= local_to_summer_transition_start && *local < local_to_summer_transition_end { - LocalResult::None + MappedLocalTime::None } else { panic!("Unexpected local time {}", local) } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 6f7253de4c..4f74b85224 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -6,7 +6,7 @@ use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// A type to hold parsed fields of date and time that can check all fields are consistent. @@ -888,9 +888,9 @@ impl Parsed { let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; match offset.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => Ok(t), - LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => Ok(t), + MappedLocalTime::Ambiguous(..) => Err(NOT_ENOUGH), } } @@ -944,15 +944,15 @@ impl Parsed { // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; match tz.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => { + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => { if check_offset(&t) { Ok(t) } else { Err(IMPOSSIBLE) } } - LocalResult::Ambiguous(min, max) => { + MappedLocalTime::Ambiguous(min, max) => { // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), diff --git a/src/lib.rs b/src/lib.rs index 917b39691b..9fe35437e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware //! by default, with separate timezone-naive types. //! * Operations that may produce an invalid or ambiguous date and time return `Option` or -//! [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). +//! [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). //! * Configurable parsing and formatting with a `strftime` inspired date and time formatting syntax. //! * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with //! the current timezone of the OS. @@ -130,7 +130,7 @@ //! #![cfg_attr(not(feature = "now"), doc = "```ignore")] #![cfg_attr(feature = "now", doc = "```rust")] -//! use chrono::offset::LocalResult; +//! use chrono::offset::MappedLocalTime; //! use chrono::prelude::*; //! //! # fn doctest() -> Option<()> { @@ -174,12 +174,12 @@ //! // dynamic verification //! assert_eq!( //! Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), -//! LocalResult::Single( +//! MappedLocalTime::Single( //! NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc() //! ) //! ); -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), MappedLocalTime::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), MappedLocalTime::None); //! //! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. @@ -590,9 +590,9 @@ pub mod offset; #[cfg(feature = "clock")] #[doc(inline)] pub use offset::Local; -pub use offset::LocalResult; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; +pub use offset::{LocalResult, MappedLocalTime}; pub mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 5395e396ed..d24633542d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -21,7 +21,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; use crate::time_delta::NANOS_PER_SEC; use crate::{ - expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone, + expect, try_opt, DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, Weekday, }; #[cfg(feature = "rustc-serialize")] @@ -929,7 +929,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timezone(), tz); /// ``` #[must_use] - pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { + pub fn and_local_timezone(&self, tz: Tz) -> MappedLocalTime> { tz.from_local_datetime(self) } diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 27e884c036..1b5a84133d 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; #[test] fn test_datetime_add() { @@ -385,13 +385,13 @@ fn test_and_timezone_min_max_dates() { if offset_hour >= 0 { assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); } else { - assert_eq!(local_max, LocalResult::None); + assert_eq!(local_max, MappedLocalTime::None); } let local_min = NaiveDateTime::MIN.and_local_timezone(offset); if offset_hour <= 0 { assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); } else { - assert_eq!(local_min, LocalResult::None); + assert_eq!(local_min, MappedLocalTime::None); } } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 745dad29a1..24cd983766 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -9,7 +9,7 @@ use core::str::FromStr; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use super::{LocalResult, Offset, TimeZone}; +use super::{MappedLocalTime, Offset, TimeZone}; use crate::format::{scan, ParseError, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime}; @@ -129,11 +129,11 @@ impl TimeZone for FixedOffset { *offset } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(*self) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(*self) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 3b71a9a0ab..611fe18d8d 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -10,7 +10,7 @@ use std::cmp::Ordering; use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; -use super::{LocalResult, TimeZone}; +use super::{MappedLocalTime, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::Date; @@ -38,16 +38,18 @@ mod win_bindings; )) ))] mod inner { - use crate::{FixedOffset, LocalResult, NaiveDateTime}; + use crate::{FixedOffset, MappedLocalTime, NaiveDateTime}; - pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + pub(super) fn offset_from_utc_datetime( + _utc_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) } pub(super) fn offset_from_local_datetime( _local_time: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) } } @@ -57,14 +59,16 @@ mod inner { not(any(target_os = "emscripten", target_os = "wasi")) ))] mod inner { - use crate::{Datelike, FixedOffset, LocalResult, NaiveDateTime, Timelike}; + use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike}; - pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset(); - LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } - pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + pub(super) fn offset_from_local_datetime( + local: &NaiveDateTime, + ) -> MappedLocalTime { let mut year = local.year(); if year < 100 { // The API in `js_sys` does not let us create a `Date` with negative years. @@ -84,7 +88,7 @@ mod inner { ); 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()) + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } } @@ -166,12 +170,12 @@ impl TimeZone for Local { } #[allow(deprecated)] - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime { // Get the offset at local midnight. self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) } - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime { inner::offset_from_local_datetime(local) } @@ -229,7 +233,7 @@ impl Ord for Transition { fn lookup_with_dst_transitions( transitions: &[Transition], dt: NaiveDateTime, -) -> LocalResult { +) -> MappedLocalTime { for t in transitions.iter() { // A transition can result in the wall clock time going forward (creating a gap) or going // backward (creating a fold). We are interested in the earliest and latest wall time of the @@ -247,24 +251,24 @@ fn lookup_with_dst_transitions( let wall_latest = t.transition_utc.overflowing_add_offset(offset_max); if dt < wall_earliest { - return LocalResult::Single(t.offset_before); + return MappedLocalTime::Single(t.offset_before); } else if dt <= wall_latest { return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) { - Ordering::Equal => LocalResult::Single(t.offset_before), - Ordering::Less => LocalResult::Ambiguous(t.offset_before, t.offset_after), + Ordering::Equal => MappedLocalTime::Single(t.offset_before), + Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after), Ordering::Greater => { if dt == wall_earliest { - LocalResult::Single(t.offset_before) + MappedLocalTime::Single(t.offset_before) } else if dt == wall_latest { - LocalResult::Single(t.offset_after) + MappedLocalTime::Single(t.offset_after) } else { - LocalResult::None + MappedLocalTime::None } } }; } } - LocalResult::Single(transitions.last().unwrap().offset_after) + MappedLocalTime::Single(transitions.last().unwrap().offset_after) } #[cfg(test)] @@ -275,7 +279,7 @@ mod tests { use crate::offset::TimeZone; use crate::{Datelike, Days, Utc}; #[cfg(windows)] - use crate::{FixedOffset, LocalResult, NaiveDate, NaiveDateTime}; + use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime}; #[test] fn verify_correct_offsets() { @@ -368,7 +372,7 @@ mod tests { h: u32, n: u32, s: u32, - result: LocalResult, + result: MappedLocalTime, ) { let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); assert_eq!(lookup_with_dst_transitions(transitions, dt), result); @@ -382,17 +386,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst), Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); - - compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std)); // std transition before dst transition // dst offset > std offset @@ -402,17 +406,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std), Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst), ]; - compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, LocalResult::Single(std)); - - compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst)); // dst transition before std transition // dst offset < std offset @@ -422,17 +426,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst), Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - - compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); // std transition before dst transition // dst offset < std offset @@ -442,17 +446,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std), Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst), ]; - compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Single(std)); - - compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); // offset stays the same let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); @@ -460,18 +464,18 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std), Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); // single transition let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); } #[test] @@ -486,17 +490,17 @@ mod tests { ]; assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()), - LocalResult::Single(std) + MappedLocalTime::Single(std) ); assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()), - LocalResult::Single(dst) + MappedLocalTime::Single(dst) ); // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when // converted to UTC). assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX), - LocalResult::Ambiguous(dst, std) + MappedLocalTime::Ambiguous(dst, std) ); // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN` @@ -508,17 +512,17 @@ mod tests { ]; assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()), - LocalResult::Single(dst) + MappedLocalTime::Single(dst) ); assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()), - LocalResult::Single(std) + MappedLocalTime::Single(std) ); // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when // converted to UTC). assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN), - LocalResult::Ambiguous(std, dst) + MappedLocalTime::Ambiguous(std, dst) ); } diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 0ff4fe7bde..daaed416f1 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -83,10 +83,10 @@ impl TransitionRule { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, Error> { match self { TransitionRule::Fixed(local_time_type) => { - Ok(crate::LocalResult::Single(*local_time_type)) + Ok(crate::MappedLocalTime::Single(*local_time_type)) } TransitionRule::Alternate(alternate_time) => { alternate_time.find_local_time_type_from_local(local_time, year) @@ -231,7 +231,7 @@ impl AlternateTime { &self, local_time: i64, current_year: i32, - ) -> Result, Error> { + ) -> Result, Error> { // Check if the current year is valid for the following computations if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) { return Err(Error::OutOfRange("out of range date time")); @@ -252,7 +252,7 @@ impl AlternateTime { - i64::from(self.dst.ut_offset); match self.std.ut_offset.cmp(&self.dst.ut_offset) { - Ordering::Equal => Ok(crate::LocalResult::Single(self.std)), + Ordering::Equal => Ok(crate::MappedLocalTime::Single(self.std)), Ordering::Less => { if self.dst_start.transition_date(current_year).0 < self.dst_end.transition_date(current_year).0 @@ -260,41 +260,41 @@ impl AlternateTime { // northern hemisphere // For the DST END transition, the `start` happens at a later timestamp than the `end`. if local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time > dst_start_transition_start && local_time < dst_start_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else if local_time >= dst_start_transition_end && local_time < dst_end_transition_end { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_end && local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) } else { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } } else { // southern hemisphere regular DST // For the DST END transition, the `start` happens at a later timestamp than the `end`. if local_time < dst_end_transition_end { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_end && local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) } else if local_time > dst_end_transition_end && local_time < dst_start_transition_start { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_start && local_time < dst_start_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } } } @@ -305,41 +305,41 @@ impl AlternateTime { // southern hemisphere reverse DST // For the DST END transition, the `start` happens at a later timestamp than the `end`. if local_time < dst_start_transition_end { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_end && local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) } else if local_time > dst_start_transition_start && local_time < dst_end_transition_start { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_start && local_time < dst_end_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } } else { // northern hemisphere reverse DST // For the DST END transition, the `start` happens at a later timestamp than the `end`. if local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time > dst_end_transition_start && local_time < dst_end_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else if local_time >= dst_end_transition_end && local_time < dst_start_transition_end { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_end && local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) } else { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } } } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 0965a8a3e3..dbb2def931 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -129,7 +129,7 @@ impl TimeZone { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, Error> { self.as_ref().find_local_time_type_from_local(local_time, year) } @@ -218,7 +218,7 @@ impl<'a> TimeZoneRef<'a> { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, Error> { // #TODO: this is wrong as we need 'local_time_to_local_leap_time ? // but ... does the local time even include leap seconds ?? // let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) { @@ -229,7 +229,7 @@ impl<'a> TimeZoneRef<'a> { let local_leap_time = local_time; // if we have at least one transition, - // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions + // we must check _all_ of them, incase of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions let offset_after_last = if !self.transitions.is_empty() { let mut prev = self.local_time_types[0]; @@ -246,26 +246,26 @@ impl<'a> TimeZoneRef<'a> { // bakwards transition, eg from DST to regular // this means a given local time could have one of two possible offsets if local_leap_time < transition_end { - return Ok(crate::LocalResult::Single(prev)); + return Ok(crate::MappedLocalTime::Single(prev)); } else if local_leap_time >= transition_end && local_leap_time <= transition_start { if prev.ut_offset < after_ltt.ut_offset { - return Ok(crate::LocalResult::Ambiguous(prev, after_ltt)); + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); } } } Ordering::Equal => { // should this ever happen? presumably we have to handle it anyway. if local_leap_time < transition_start { - return Ok(crate::LocalResult::Single(prev)); + return Ok(crate::MappedLocalTime::Single(prev)); } else if local_leap_time == transition_end { if prev.ut_offset < after_ltt.ut_offset { - return Ok(crate::LocalResult::Ambiguous(prev, after_ltt)); + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); } } } @@ -273,11 +273,11 @@ impl<'a> TimeZoneRef<'a> { // forwards transition, eg from regular to DST // this means that times that are skipped are invalid local times if local_leap_time <= transition_start { - return Ok(crate::LocalResult::Single(prev)); + return Ok(crate::MappedLocalTime::Single(prev)); } else if local_leap_time < transition_end { - return Ok(crate::LocalResult::None); + return Ok(crate::MappedLocalTime::None); } else if local_leap_time == transition_end { - return Ok(crate::LocalResult::Single(after_ltt)); + return Ok(crate::MappedLocalTime::Single(after_ltt)); } } } @@ -298,7 +298,7 @@ impl<'a> TimeZoneRef<'a> { err => err, } } else { - Ok(crate::LocalResult::Single(offset_after_last)) + Ok(crate::MappedLocalTime::Single(offset_after_last)) } } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index a2c2724380..ad99542330 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,17 +12,17 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::Sys use super::tz_info::TimeZone; use super::{FixedOffset, NaiveDateTime}; -use crate::{Datelike, LocalResult}; +use crate::{Datelike, MappedLocalTime}; -pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { offset(utc, false) } -pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { offset(local, true) } -fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { +fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime { TZ_INFO.with(|maybe_cache| { maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) }) @@ -104,7 +104,7 @@ fn current_zone(var: Option<&str>) -> TimeZone { } impl Cache { - fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime { let now = SystemTime::now(); match now.duration_since(self.last_checked) { @@ -156,13 +156,13 @@ impl Cache { .offset(); return match FixedOffset::east_opt(offset) { - Some(offset) => LocalResult::Single(offset), - None => LocalResult::None, + Some(offset) => MappedLocalTime::Single(offset), + None => MappedLocalTime::None, }; } // we pass through the year as the year of a local point in time must either be valid in that locale, or - // the entire time was skipped in which case we will return LocalResult::None anyway. + // the entire time was skipped in which case we will return MappedLocalTime::None anyway. self.zone .find_local_time_type_from_local(d.and_utc().timestamp(), d.year()) .expect("unable to select local time type") diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 8256baea80..100b1f1eef 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -15,7 +15,7 @@ use std::ptr; use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; use crate::offset::local::{lookup_with_dst_transitions, Transition}; -use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; // We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates // as Chrono. Also it really isn't that difficult to work out the correct offset from the provided @@ -24,13 +24,13 @@ use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveT // This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC // falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is // very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`. -pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { // Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be // using the rules for the year of the corresponding local time. But this matches what // `SystemTimeToTzSpecificLocalTime` is documented to do. let tz_info = match TzInfo::for_year(utc.year()) { Some(tz_info) => tz_info, - None => return LocalResult::None, + None => return MappedLocalTime::None, }; let offset = match (tz_info.std_transition, tz_info.dst_transition) { (Some(std_transition), Some(dst_transition)) => { @@ -64,16 +64,16 @@ pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult tz_info.std_offset, }; - LocalResult::Single(offset) + MappedLocalTime::Single(offset) } // We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle // ambiguous cases (during a DST transition). Instead we get the timezone information for the // current year and compute it ourselves, like we do on Unix. -pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { let tz_info = match TzInfo::for_year(local.year()) { Some(tz_info) => tz_info, - None => return LocalResult::None, + None => return MappedLocalTime::None, }; // Create a sorted slice of transitions and use `lookup_with_dst_transitions`. match (tz_info.std_transition, tz_info.dst_transition) { @@ -87,7 +87,7 @@ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult [dst_transition, std_transition], Ordering::Equal => { // This doesn't make sense. Let's just return the standard offset. - return LocalResult::Single(tz_info.std_offset); + return MappedLocalTime::Single(tz_info.std_offset); } }; lookup_with_dst_transitions(&transitions, *local) @@ -102,7 +102,7 @@ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult LocalResult::Single(tz_info.std_offset), + (None, None) => MappedLocalTime::Single(tz_info.std_offset), } } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 005c6bdd9a..87beaa7a21 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -39,7 +39,7 @@ pub use self::utc::Utc; /// The conversion result from the local time to the timezone-aware datetime types. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] -pub enum LocalResult { +pub enum MappedLocalTime { /// Given local time representation is invalid. /// This can occur when, for example, the positive timezone transition. None, @@ -50,12 +50,12 @@ pub enum LocalResult { Ambiguous(T /* min */, T /* max */), } -impl LocalResult { +impl MappedLocalTime { /// Returns `Some` only when the conversion result is unique, or `None` otherwise. #[must_use] pub fn single(self) -> Option { match self { - LocalResult::Single(t) => Some(t), + MappedLocalTime::Single(t) => Some(t), _ => None, } } @@ -64,7 +64,7 @@ impl LocalResult { #[must_use] pub fn earliest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t), _ => None, } } @@ -73,36 +73,39 @@ impl LocalResult { #[must_use] pub fn latest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(_, t) => Some(t), _ => None, } } - /// Maps a `LocalResult` into `LocalResult` with given function. + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. #[must_use] - pub fn map U>(self, mut f: F) -> LocalResult { + pub fn map U>(self, mut f: F) -> MappedLocalTime { match self { - LocalResult::None => LocalResult::None, - LocalResult::Single(v) => LocalResult::Single(f(v)), - LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)), + MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)), } } } +/// The conversion result from the local time to the timezone-aware datetime types. +pub type LocalResult = MappedLocalTime; + #[allow(deprecated)] -impl LocalResult> { +impl MappedLocalTime> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] - pub fn and_time(self, time: NaiveTime) -> LocalResult> { + pub fn and_time(self, time: NaiveTime) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_time(time).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_time(time).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -112,12 +115,12 @@ impl LocalResult> { /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] - pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { + pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_hms_opt(hour, min, sec).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -134,12 +137,12 @@ impl LocalResult> { min: u32, sec: u32, milli: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_milli_opt(hour, min, sec, milli) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -156,12 +159,12 @@ impl LocalResult> { min: u32, sec: u32, micro: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_micro_opt(hour, min, sec, micro) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -178,25 +181,25 @@ impl LocalResult> { min: u32, sec: u32, nano: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_nano_opt(hour, min, sec, nano) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } } -impl LocalResult { +impl MappedLocalTime { /// Returns the single unique conversion result, or panics accordingly. #[must_use] #[track_caller] pub fn unwrap(self) -> T { match self { - LocalResult::None => panic!("No such local time"), - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, t2) => { + MappedLocalTime::None => panic!("No such local time"), + MappedLocalTime::Single(t) => t, + MappedLocalTime::Ambiguous(t1, t2) => { panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) } } @@ -222,7 +225,7 @@ pub trait TimeZone: Sized + Clone { /// /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// - /// Returns `LocalResult::None` on invalid input data. + /// Returns `MappedLocalTime::None` on invalid input data. fn with_ymd_and_hms( &self, year: i32, @@ -231,11 +234,11 @@ pub trait TimeZone: Sized + Clone { hour: u32, min: u32, sec: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) { Some(dt) => self.from_local_datetime(&dt), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -261,10 +264,10 @@ pub trait TimeZone: Sized + Clone { /// Returns `None` on the out-of-range date, invalid month and/or day. #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] #[allow(deprecated)] - fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { + fn ymd_opt(&self, year: i32, month: u32, day: u32) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -296,10 +299,10 @@ pub trait TimeZone: Sized + Clone { note = "use `from_local_datetime()` with a `NaiveDateTime` instead" )] #[allow(deprecated)] - fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { + fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -335,10 +338,10 @@ pub trait TimeZone: Sized + Clone { note = "use `from_local_datetime()` with a `NaiveDateTime` instead" )] #[allow(deprecated)] - fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { + fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> MappedLocalTime> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -369,8 +372,8 @@ pub trait TimeZone: Sized + Clone { /// /// # Errors /// - /// Returns `LocalResult::None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise always returns `LocalResult::Single`. + /// Returns `MappedLocalTime::None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise always returns `MappedLocalTime::Single`. /// /// # Example /// @@ -379,10 +382,10 @@ pub trait TimeZone: Sized + Clone { /// /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); /// ``` - fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { + fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime> { match DateTime::from_timestamp(secs, nsecs) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -400,23 +403,23 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// - /// Returns `LocalResult::None` on out-of-range number of milliseconds + /// Returns `MappedLocalTime::None` on out-of-range number of milliseconds /// and/or invalid nanosecond, otherwise always returns - /// `LocalResult::Single`. + /// `MappedLocalTime::Single`. /// /// # Example /// /// ``` - /// use chrono::{LocalResult, TimeZone, Utc}; + /// use chrono::{MappedLocalTime, TimeZone, Utc}; /// match Utc.timestamp_millis_opt(1431648000) { - /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), + /// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), /// }; /// ``` - fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { + fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime> { match DateTime::from_timestamp_millis(millis) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -446,10 +449,10 @@ pub trait TimeZone: Sized + Clone { /// /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); /// ``` - fn timestamp_micros(&self, micros: i64) -> LocalResult> { + fn timestamp_micros(&self, micros: i64) -> MappedLocalTime> { match DateTime::from_timestamp_micros(micros) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -482,16 +485,16 @@ pub trait TimeZone: Sized + Clone { fn from_offset(offset: &Self::Offset) -> Self; /// Creates the offset(s) for given local `NaiveDate` if possible. - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult; + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime; /// Creates the offset(s) for given local `NaiveDateTime` if possible. - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. #[allow(clippy::wrong_self_convention)] #[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")] #[allow(deprecated)] - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected Date::from_utc(*local, offset) @@ -500,22 +503,24 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - // Return `LocalResult::None` when the offset pushes a value out of range, instead of + fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime> { + // Return `MappedLocalTime::None` when the offset pushes a value out of range, instead of // panicking. match self.offset_from_local_datetime(local) { - LocalResult::None => LocalResult::None, - LocalResult::Single(offset) => match local.checked_sub_offset(offset.fix()) { - Some(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)), - None => LocalResult::None, + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(offset) => match local.checked_sub_offset(offset.fix()) { + Some(dt) => { + MappedLocalTime::Single(DateTime::from_naive_utc_and_offset(dt, offset)) + } + None => MappedLocalTime::None, }, - LocalResult::Ambiguous(o1, o2) => { + MappedLocalTime::Ambiguous(o1, o2) => { match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { - (Some(d1), Some(d2)) => LocalResult::Ambiguous( + (Some(d1), Some(d2)) => MappedLocalTime::Ambiguous( DateTime::from_naive_utc_and_offset(d1, o1), DateTime::from_naive_utc_and_offset(d2, o2), ), - _ => LocalResult::None, + _ => MappedLocalTime::None, } } } @@ -563,13 +568,13 @@ mod tests { if offset_hour >= 0 { assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); } else { - assert_eq!(local_max, LocalResult::None); + assert_eq!(local_max, MappedLocalTime::None); } let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); if offset_hour <= 0 { assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); } else { - assert_eq!(local_min, LocalResult::None); + assert_eq!(local_min, MappedLocalTime::None); } } } @@ -599,7 +604,7 @@ mod tests { (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis_opt(*millis) { - LocalResult::Single(dt) => { + MappedLocalTime::Single(dt) => { assert_eq!(dt.to_string(), *expected); } e => panic!("Got {:?} instead of an okay answer", e), diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 5bfbe69e9c..5ae26ed86d 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -17,7 +17,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use super::{FixedOffset, LocalResult, Offset, TimeZone}; +use super::{FixedOffset, MappedLocalTime, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[cfg(feature = "now")] #[allow(deprecated)] @@ -118,11 +118,11 @@ impl TimeZone for Utc { Utc } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc { diff --git a/tests/dateutils.rs b/tests/dateutils.rs index a57d439d30..5aa00da6bf 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -29,18 +29,18 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // } // This is used while a decision is made whether the `date` output needs to - // be exactly matched, or whether LocalResult::Ambiguous should be handled + // be exactly matched, or whether MappedLocalTime::Ambiguous should be handled // differently let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { - chrono::LocalResult::Ambiguous(a, b) => assert!( + chrono::MappedLocalTime::Ambiguous(a, b) => assert!( format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str ), - chrono::LocalResult::Single(a) => { + chrono::MappedLocalTime::Single(a) => { assert_eq!(format!("{}\n", a), date_command_str); } - chrono::LocalResult::None => { + chrono::MappedLocalTime::None => { assert_eq!("", date_command_str); } } From 178918301276a78c7273c21d1fd451148542532d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 9 Mar 2024 13:25:48 +0100 Subject: [PATCH 806/999] Extend documentation for `MappedLocalTime` --- src/offset/mod.rs | 67 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 87beaa7a21..c1d1cc5619 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -37,21 +37,47 @@ pub use self::local::Local; pub(crate) mod utc; pub use self::utc::Utc; -/// The conversion result from the local time to the timezone-aware datetime types. +/// The result of mapping a local time to a concrete instant in a given time zone. +/// +/// The calculation to go from a local time (wall clock time) to an instant in UTC can end up in +/// three cases: +/// * A single, simple result. +/// * An ambiguous result when the clock is turned backwards during a transition due to for example +/// DST. +/// * No result when the clock is turned forwards during a transition due to for example DST. +/// +/// When the clock is turned backwards it creates a _fold_ in local time, during which the local +/// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during +/// which the local time is _missing_, or does not exist. +/// +/// Chrono does not return a default choice or invalid data during time zone transitions, but has +/// the `MappedLocalTime` type to help deal with the result correctly. +/// +/// The type of `T` is usually a [`DateTime`] but may also be only an offset. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] pub enum MappedLocalTime { - /// Given local time representation is invalid. - /// This can occur when, for example, the positive timezone transition. - None, - /// Given local time representation has a single unique result. + /// The local time maps to a single unique result. Single(T), - /// Given local time representation has multiple results and thus ambiguous. - /// This can occur when, for example, the negative timezone transition. - Ambiguous(T /* min */, T /* max */), + + /// The local time is _ambiguous_ because there is a _fold_ in the local time. + /// + /// This variant contains the two possible results, in the order `(earliest, latest)`. + Ambiguous(T, T), + + /// The local time does not exist because there is a _gap_ in the local time. + /// + /// This variant may also be returned if there was an error while resolving the local time, + /// caused by for example missing time zone data files, an error in an OS API, or overflow. + None, } impl MappedLocalTime { - /// Returns `Some` only when the conversion result is unique, or `None` otherwise. + /// Returns `Some` if the time zone mapping has a single result. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _fold_ or _gap_ in the local time, or if there was + /// an error. #[must_use] pub fn single(self) -> Option { match self { @@ -60,7 +86,11 @@ impl MappedLocalTime { } } - /// Returns `Some` for the earliest possible conversion result, or `None` if none. + /// Returns the earliest possible result of a the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. #[must_use] pub fn earliest(self) -> Option { match self { @@ -69,7 +99,11 @@ impl MappedLocalTime { } } - /// Returns `Some` for the latest possible conversion result, or `None` if none. + /// Returns the latest possible result of a the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. #[must_use] pub fn latest(self) -> Option { match self { @@ -192,7 +226,16 @@ impl MappedLocalTime> { } impl MappedLocalTime { - /// Returns the single unique conversion result, or panics accordingly. + /// Returns a single unique conversion result or panics. + /// + /// `unwrap()` is best combined with time zone types where the mapping can never fail like + /// [`Utc`] and [`FixedOffset`]. Note that for [`FixedOffset`] there is a rare case where a + /// resulting [`DateTime`] can be out of range. + /// + /// # Panics + /// + /// Panics if the local time falls within a _fold_ or a _gap_ in the local time, and on any + /// error that may have been returned by the type implementing [`TimeZone`]. #[must_use] #[track_caller] pub fn unwrap(self) -> T { From 236b7adf32a2a4890131d5ab9f19a97147c390a1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Mar 2024 13:32:53 +0100 Subject: [PATCH 807/999] Add `DateTime::with_time()` --- src/datetime/mod.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 6715dfc843..5e5f96da5c 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -25,7 +25,7 @@ use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; #[allow(deprecated)] use crate::Date; use crate::{expect, try_opt}; @@ -684,6 +684,31 @@ impl DateTime { result } + /// Set the time to a new fixed time on the existing date. + /// + /// # Errors + /// + /// Returns `LocalResult::None` if the datetime is at the edge of the representable range for a + /// `DateTime`, and `with_time` would push the value in UTC out of range. + /// + /// # Example + /// + #[cfg_attr(not(feature = "clock"), doc = "```ignore")] + #[cfg_attr(feature = "clock", doc = "```rust")] + /// use chrono::{Local, NaiveTime}; + /// + /// let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap(); + /// let today_noon = Local::now().with_time(noon); + /// let today_midnight = Local::now().with_time(NaiveTime::MIN); + /// + /// assert_eq!(today_noon.single().unwrap().time(), noon); + /// assert_eq!(today_midnight.single().unwrap().time(), NaiveTime::MIN); + /// ``` + #[must_use] + pub fn with_time(&self, time: NaiveTime) -> LocalResult { + self.timezone().from_local_datetime(&self.overflowing_naive_local().date().and_time(time)) + } + /// The minimum possible `DateTime`. pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; /// The maximum possible `DateTime`. From d91b7d4a22056fbffb0c494575546af1baac5f64 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 15 Mar 2024 08:23:17 +0100 Subject: [PATCH 808/999] Add extra assertions to `NaiveDate::from_yof` --- src/naive/date/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 9c6d04b480..cf36e328de 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1437,10 +1437,14 @@ impl NaiveDate { /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. /// /// In a valid value an ordinal is never `0`, and neither are the year flags. This method - /// doesn't do any validation. + /// doesn't do any validation in release builds. #[inline] const fn from_yof(yof: i32) -> NaiveDate { - debug_assert!(yof != 0); + // The following are the invariants our ordinal and flags should uphold for a valid + // `NaiveDate`. + debug_assert!(((yof & OL_MASK) >> 3) > 1); + debug_assert!(((yof & OL_MASK) >> 3) <= MAX_OL); + debug_assert!((yof & 0b111) != 000); NaiveDate { yof: unsafe { NonZeroI32::new_unchecked(yof) } } } From c21b359c88cf3a65129f478c93fae79417862ccf Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 18 Mar 2024 08:18:45 +0100 Subject: [PATCH 809/999] Turn `expect` macro into a function --- src/datetime/mod.rs | 6 +++--- src/lib.rs | 14 +++++--------- src/naive/date/mod.rs | 24 ++++++++++++------------ src/naive/datetime/mod.rs | 8 ++++---- src/naive/mod.rs | 4 ++-- src/naive/time/mod.rs | 12 ++++++------ src/time_delta.rs | 24 ++++++++++++------------ 7 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5e5f96da5c..03bdea1655 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -279,9 +279,9 @@ impl DateTime { #[inline] #[must_use] pub const fn timestamp_nanos(&self) -> i64 { - expect!( + expect( self.timestamp_nanos_opt(), - "value can not be represented in a timestamp with nanosecond precision." + "value can not be represented in a timestamp with nanosecond precision.", ) } @@ -857,7 +857,7 @@ impl DateTime { pub const fn from_timestamp_nanos(nanos: i64) -> Self { let secs = nanos.div_euclid(1_000_000_000); let nsecs = nanos.rem_euclid(1_000_000_000) as u32; - expect!(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range") + expect(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range") } /// The Unix Epoch, 1970-01-01 00:00:00 UTC. diff --git a/src/lib.rs b/src/lib.rs index 9fe35437e4..e43512cc5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -716,15 +716,11 @@ macro_rules! try_opt { } /// Workaround because `.expect()` is not (yet) available in const context. -#[macro_export] -#[doc(hidden)] -macro_rules! expect { - ($e:expr, $m:literal) => { - match $e { - Some(v) => v, - None => panic!($m), - } - }; +pub(crate) const fn expect(opt: Option, msg: &str) -> T { + match opt { + Some(val) => val, + None => panic!("{}", msg), + } } #[cfg(test)] diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index cf36e328de..81b7851618 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -165,7 +165,7 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] #[must_use] pub const fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { - expect!(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") + expect(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) @@ -213,7 +213,7 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] #[must_use] pub const fn from_yo(year: i32, ordinal: u32) -> NaiveDate { - expect!(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") + expect(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) @@ -258,7 +258,7 @@ impl NaiveDate { #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] #[must_use] pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { - expect!(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") + expect(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") } /// Makes a new `NaiveDate` from the [ISO week date](#week-date) @@ -349,7 +349,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn from_num_days_from_ce(days: i32) -> NaiveDate { - expect!(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") + expect(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") } /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with @@ -402,7 +402,7 @@ impl NaiveDate { weekday: Weekday, n: u8, ) -> NaiveDate { - expect!(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") + expect(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week @@ -752,7 +752,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { - expect!(self.and_hms_opt(hour, min, sec), "invalid time") + expect(self.and_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. @@ -794,7 +794,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { - expect!(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") + expect(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. @@ -858,7 +858,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { - expect!(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") + expect(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. @@ -908,7 +908,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { - expect!(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") + expect(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. @@ -975,7 +975,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn succ(&self) -> NaiveDate { - expect!(self.succ_opt(), "out of bound") + expect(self.succ_opt(), "out of bound") } /// Makes a new `NaiveDate` for the next calendar date. @@ -1014,7 +1014,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn pred(&self) -> NaiveDate { - expect!(self.pred_opt(), "out of bound") + expect(self.pred_opt(), "out of bound") } /// Makes a new `NaiveDate` for the previous calendar date. @@ -1158,7 +1158,7 @@ impl NaiveDate { let days = (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2); // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 // years. - expect!(TimeDelta::try_days(days), "always in range") + expect(TimeDelta::try_days(days), "always in range") } /// Returns the number of whole years from the given `base` until `self`. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index d24633542d..a851cb394b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -123,7 +123,7 @@ impl NaiveDateTime { #[must_use] pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = - expect!(DateTime::from_timestamp(secs, nsecs), "invalid or out-of-range datetime"); + expect(DateTime::from_timestamp(secs, nsecs), "invalid or out-of-range datetime"); datetime.naive_utc() } @@ -818,11 +818,11 @@ impl NaiveDateTime { /// ``` #[must_use] pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { - expect!( + expect( self.date .signed_duration_since(rhs.date) .checked_add(&self.time.signed_duration_since(rhs.time)), - "always in range" + "always in range", ) } @@ -956,7 +956,7 @@ impl NaiveDateTime { /// The Unix Epoch, 1970-01-01 00:00:00. pub const UNIX_EPOCH: Self = - expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); + expect(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); } impl From for NaiveDateTime { diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 4b33a5594d..80877c6e73 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -69,7 +69,7 @@ impl NaiveWeek { // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`") + expect(self.date.add_days(days), "first weekday out of range for `NaiveDate`") } /// Returns a date representing the last day of the week. @@ -97,7 +97,7 @@ impl NaiveWeek { // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`") + expect(self.date.add_days(days), "last weekday out of range for `NaiveDate`") } /// Returns a [`RangeInclusive`] representing the whole week bounded by diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 23b1cb0e55..0aaff0a973 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -256,7 +256,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { - expect!(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") + expect(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") } /// Makes a new `NaiveTime` from hour, minute and second. @@ -299,7 +299,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { - expect!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") + expect(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and millisecond. @@ -350,7 +350,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { - expect!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") + expect(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. @@ -401,7 +401,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { - expect!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") + expect(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. @@ -453,7 +453,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { - expect!(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") + expect(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. @@ -767,7 +767,7 @@ impl NaiveTime { let secs_from_frac = frac.div_euclid(1_000_000_000); let frac = frac.rem_euclid(1_000_000_000) as u32; - expect!(TimeDelta::new(secs + secs_from_frac, frac), "must be in range") + expect(TimeDelta::new(secs + secs_from_frac, frac), "must be in range") } /// Adds given `FixedOffset` to the current time, and returns the number of days that should be diff --git a/src/time_delta.rs b/src/time_delta.rs index 1d05037fb3..9785151998 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -104,7 +104,7 @@ impl TimeDelta { #[must_use] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_weeks` instead")] pub const fn weeks(weeks: i64) -> TimeDelta { - expect!(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") + expect(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") } /// Makes a new `TimeDelta` with the given number of weeks. @@ -132,7 +132,7 @@ impl TimeDelta { #[must_use] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_days` instead")] pub const fn days(days: i64) -> TimeDelta { - expect!(TimeDelta::try_days(days), "TimeDelta::days out of bounds") + expect(TimeDelta::try_days(days), "TimeDelta::days out of bounds") } /// Makes a new `TimeDelta` with the given number of days. @@ -159,7 +159,7 @@ impl TimeDelta { #[must_use] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_hours` instead")] pub const fn hours(hours: i64) -> TimeDelta { - expect!(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") + expect(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") } /// Makes a new `TimeDelta` with the given number of hours. @@ -185,7 +185,7 @@ impl TimeDelta { #[must_use] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_minutes` instead")] pub const fn minutes(minutes: i64) -> TimeDelta { - expect!(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") + expect(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") } /// Makes a new `TimeDelta` with the given number of minutes. @@ -210,7 +210,7 @@ impl TimeDelta { #[must_use] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_seconds` instead")] pub const fn seconds(seconds: i64) -> TimeDelta { - expect!(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") + expect(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") } /// Makes a new `TimeDelta` with the given number of seconds. @@ -234,7 +234,7 @@ impl TimeDelta { #[inline] #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_milliseconds` instead")] pub const fn milliseconds(milliseconds: i64) -> TimeDelta { - expect!(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") + expect(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } /// Makes a new `TimeDelta` with the given number of milliseconds. @@ -1244,12 +1244,12 @@ mod tests { #[test] fn test_duration_const() { - const ONE_WEEK: TimeDelta = expect!(TimeDelta::try_weeks(1), ""); - const ONE_DAY: TimeDelta = expect!(TimeDelta::try_days(1), ""); - const ONE_HOUR: TimeDelta = expect!(TimeDelta::try_hours(1), ""); - const ONE_MINUTE: TimeDelta = expect!(TimeDelta::try_minutes(1), ""); - const ONE_SECOND: TimeDelta = expect!(TimeDelta::try_seconds(1), ""); - const ONE_MILLI: TimeDelta = expect!(TimeDelta::try_milliseconds(1), ""); + const ONE_WEEK: TimeDelta = expect(TimeDelta::try_weeks(1), ""); + const ONE_DAY: TimeDelta = expect(TimeDelta::try_days(1), ""); + const ONE_HOUR: TimeDelta = expect(TimeDelta::try_hours(1), ""); + const ONE_MINUTE: TimeDelta = expect(TimeDelta::try_minutes(1), ""); + const ONE_SECOND: TimeDelta = expect(TimeDelta::try_seconds(1), ""); + const ONE_MILLI: TimeDelta = expect(TimeDelta::try_milliseconds(1), ""); const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); let combo: TimeDelta = ONE_WEEK From 04eb6d346fa8ae6cb1e66ea15c4425d592b74039 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 15 Mar 2024 11:37:47 +0100 Subject: [PATCH 810/999] Slightly improve serde documentation --- src/datetime/serde.rs | 41 +++++++++++++++++++++---------------- src/lib.rs | 16 ++++++++++----- src/naive/datetime/serde.rs | 5 ++--- src/naive/mod.rs | 10 ++++----- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 5e66f5786e..6a2b8d5115 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -23,10 +23,12 @@ pub struct MicroSecondsTimestampVisitor; #[derive(Debug)] pub struct MilliSecondsTimestampVisitor; -/// Serialize into an ISO 8601 formatted string. +/// Serialize to an RFC 3339 formatted string /// -/// See [the `serde` module](./serde/index.html) for alternate -/// serializations. +/// As an extension to RFC 3339 this can serialize `DateTime`s outside the range of 0-9999 years +/// using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate serializations. impl ser::Serialize for DateTime { fn serialize(&self, serializer: S) -> Result where @@ -54,7 +56,7 @@ impl<'de> de::Visitor<'de> for DateTimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a formatted date and time string or a unix timestamp") + formatter.write_str("an RFC 3339 formatted date and time string") } fn visit_str(self, value: &str) -> Result @@ -65,13 +67,12 @@ impl<'de> de::Visitor<'de> for DateTimeVisitor { } } -/// Deserialize a value that optionally includes a timezone offset in its -/// string representation +/// Deserialize an RFC 3339 formatted string into a `DateTime` /// -/// The value to be deserialized must be an rfc3339 string. +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). /// -/// See [the `serde` module](./serde/index.html) for alternate -/// deserialization formats. +/// See [the `serde` module](crate::serde) for alternate deserialization formats. impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where @@ -81,12 +82,14 @@ impl<'de> de::Deserialize<'de> for DateTime { } } -/// Deserialize into a UTC value +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// If the value contains an offset from UTC that is not zero, the value will be converted to UTC. /// -/// The value to be deserialized must be an rfc3339 string. +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). /// -/// See [the `serde` module](./serde/index.html) for alternate -/// deserialization formats. +/// See [the `serde` module](crate::serde) for alternate deserialization formats. impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result where @@ -96,13 +99,15 @@ impl<'de> de::Deserialize<'de> for DateTime { } } -/// Deserialize a value that includes no timezone in its string -/// representation +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// The value will remain the same instant in UTC, but the offset will be recalculated to match +/// that of the `Local` platform time zone. /// -/// The value to be deserialized must be an rfc3339 string. +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). /// -/// See [the `serde` module](./serde/index.html) for alternate -/// serialization formats. +/// See [the `serde` module](crate::serde) for alternate deserialization formats. #[cfg(feature = "clock")] impl<'de> de::Deserialize<'de> for DateTime { fn deserialize(deserializer: D) -> Result diff --git a/src/lib.rs b/src/lib.rs index e43512cc5e..eda620e16a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -614,15 +614,21 @@ pub use traits::{Datelike, Timelike}; #[doc(hidden)] pub use naive::__BenchYearFlags; -/// Serialization/Deserialization with serde. +/// Serialization/Deserialization with serde /// -/// This module provides default implementations for `DateTime` using the [RFC 3339][1] format and various -/// alternatives for use with serde's [`with` annotation][2]. +/// The [`DateTime`] type has default implementations for (de)serializing to/from the [RFC 3339] +/// format. This module provides alternatives for serializing to timestamps. +/// +/// The alternatives are for use with serde's [`with` annotation] combined with the module name. +/// Alternatively the individual `serialize` and `deserialize` functions in each module can be used +/// with serde's [`serialize_with`] and [`deserialize_with`] annotations. /// /// *Available on crate feature 'serde' only.* /// -/// [1]: https://tools.ietf.org/html/rfc3339 -/// [2]: https://serde.rs/field-attrs.html#with +/// [RFC 3339]: https://tools.ietf.org/html/rfc3339 +/// [`with` annotation]: https://serde.rs/field-attrs.html#with +/// [`serialize_with`]: https://serde.rs/field-attrs.html#serialize_with +/// [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with #[cfg(feature = "serde")] pub mod serde { use core::fmt; diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index de8abd7dc5..45c1935d6a 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -3,10 +3,9 @@ use serde::{de, ser}; use super::NaiveDateTime; -/// Serialize a `NaiveDateTime` as an RFC 3339 string +/// Serialize a `NaiveDateTime` as an ISO 8601 string /// -/// See [the `serde` module](./serde/index.html) for alternate -/// serialization formats. +/// See [the `naive::serde` module](crate::naive::serde) for alternate serialization formats. impl ser::Serialize for NaiveDateTime { fn serialize(&self, serializer: S) -> Result where diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 80877c6e73..df42ccae58 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -141,14 +141,12 @@ impl Days { } } -/// Serialization/Deserialization of naive types in alternate formats +/// Serialization/Deserialization of `NaiveDateTime` in alternate formats /// -/// The various modules in here are intended to be used with serde's [`with` -/// annotation][1] to serialize as something other than the default [RFC -/// 3339][2] format. +/// The various modules in here are intended to be used with serde's [`with` annotation] to +/// serialize as something other than the default ISO 8601 format. /// -/// [1]: https://serde.rs/attributes.html#field-attributes -/// [2]: https://tools.ietf.org/html/rfc3339 +/// [`with` annotation]: https://serde.rs/field-attrs.html#with #[cfg(feature = "serde")] pub mod serde { pub use super::datetime::serde::*; From 96644949a40d9d837c94297d762e8b510c1372de Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 18 Mar 2024 11:27:09 +0100 Subject: [PATCH 811/999] Simplify `DateTime::date_naive` --- src/datetime/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 03bdea1655..d3de820fde 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -173,8 +173,7 @@ impl DateTime { #[inline] #[must_use] pub fn date_naive(&self) -> NaiveDate { - let local = self.naive_local(); - NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap() + self.naive_local().date() } /// Retrieves the time component. From c370ef6a3b5341d1506a1b5520ef80b6bd810f74 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 20 Mar 2024 14:53:34 +0100 Subject: [PATCH 812/999] Simplify `NaiveDate::diff_months` --- src/naive/date/mod.rs | 41 ++++++----------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 81b7851618..c5bd66e7ce 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -546,7 +546,7 @@ impl NaiveDate { return Some(self); } - match months.0 <= core::i32::MAX as u32 { + match months.0 <= i32::MAX as u32 { true => self.diff_months(months.0 as i32), false => None, } @@ -582,47 +582,18 @@ impl NaiveDate { return Some(self); } - // Copy `i32::MAX` here so we don't have to do a complicated cast - match months.0 <= 2_147_483_647 { + match months.0 <= i32::MAX as u32 { true => self.diff_months(-(months.0 as i32)), false => None, } } const fn diff_months(self, months: i32) -> Option { - let (years, left) = ((months / 12), (months % 12)); - - // Determine new year (without taking months into account for now - - let year = if (years > 0 && years > (MAX_YEAR - self.year())) - || (years < 0 && years < (MIN_YEAR - self.year())) - { - return None; - } else { - self.year() + years - }; - - // Determine new month - - let month = self.month() as i32 + left; - let (year, month) = if month <= 0 { - if year == MIN_YEAR { - return None; - } - - (year - 1, month + 12) - } else if month > 12 { - if year == MAX_YEAR { - return None; - } - - (year + 1, month - 12) - } else { - (year, month) - }; + let months = try_opt!((self.year() * 12 + self.month() as i32 - 1).checked_add(months)); + let year = months.div_euclid(12); + let month = months.rem_euclid(12) as u32 + 1; // Clamp original day in case new month is shorter - let flags = YearFlags::from_year(year); let feb_days = if flags.ndays() == 366 { 29 } else { 28 }; let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; @@ -632,7 +603,7 @@ impl NaiveDate { day = day_max; }; - NaiveDate::from_mdf(year, try_opt!(Mdf::new(month as u32, day, flags))) + NaiveDate::from_ymd_opt(year, month, day) } /// Add a duration in [`Days`] to the date From 28a46c9b2f65beee770127ab29286db4d07c5cec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 20 Mar 2024 15:16:07 +0100 Subject: [PATCH 813/999] Remove 'This can fail' documentation from `NaiveDateTime::and_local_timezone` --- src/naive/datetime/mod.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index a851cb394b..1861466bf5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -903,16 +903,8 @@ impl NaiveDateTime { self.format_with_items(StrftimeItems::new(fmt)) } - /// Converts the `NaiveDateTime` into the timezone-aware `DateTime` - /// with the provided timezone, if possible. - /// - /// This can fail in cases where the local time represented by the `NaiveDateTime` - /// is not a valid local timestamp in the target timezone due to an offset transition - /// for example if the target timezone had a change from +00:00 to +01:00 - /// occurring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 - /// could never occur. Similarly, if the offset transitioned in the opposite direction - /// then there would be two local times of 2015-09-05 23:56:04, one at +00:00 and one - /// at +01:00. + /// Converts the `NaiveDateTime` into a timezone-aware `DateTime` with the provided + /// time zone. /// /// # Example /// From 18328a8d9fb947e983638fa0e30ddbcc530687a0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 18 Mar 2024 19:16:29 +0100 Subject: [PATCH 814/999] Replace `.and_local_timezone(Utc)` with `.and_utc()` --- src/datetime/mod.rs | 5 ++--- src/datetime/serde.rs | 36 ++++++++++++------------------------ src/lib.rs | 18 ++++++------------ src/naive/datetime/tests.rs | 2 +- src/naive/time/mod.rs | 10 ++++------ src/round.rs | 25 ++++++++++--------------- 6 files changed, 35 insertions(+), 61 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index d3de820fde..8a8234ae84 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -652,13 +652,12 @@ impl DateTime { /// # Examples /// /// ```rust - /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26) /// .unwrap() /// .and_hms_micro_opt(18, 30, 9, 453_829) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), "2018-01-26T18:30:09.453+00:00"); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), "2018-01-26T18:30:09.453Z"); /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T18:30:09Z"); diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 6a2b8d5115..724c1ae717 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -138,8 +138,7 @@ impl<'de> de::Deserialize<'de> for DateTime { /// .unwrap() /// .and_hms_nano_opt(02, 04, 59, 918355733) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(); +/// .and_utc(); /// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; @@ -186,8 +185,7 @@ pub mod ts_nanoseconds { /// .unwrap() /// .and_hms_nano_opt(02, 04, 59, 918355733) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); @@ -283,8 +281,7 @@ pub mod ts_nanoseconds { /// .unwrap() /// .and_hms_nano_opt(02, 04, 59, 918355733) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(), +/// .and_utc(), /// ); /// let my_s = S { time: time.clone() }; /// @@ -332,8 +329,7 @@ pub mod ts_nanoseconds_option { /// .unwrap() /// .and_hms_nano_opt(02, 04, 59, 918355733) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; @@ -434,8 +430,7 @@ pub mod ts_nanoseconds_option { /// .unwrap() /// .and_hms_micro_opt(02, 04, 59, 918355) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(); +/// .and_utc(); /// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; @@ -474,8 +469,7 @@ pub mod ts_microseconds { /// .unwrap() /// .and_hms_micro_opt(02, 04, 59, 918355) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); @@ -572,8 +566,7 @@ pub mod ts_microseconds { /// .unwrap() /// .and_hms_micro_opt(02, 04, 59, 918355) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(), +/// .and_utc(), /// ); /// let my_s = S { time: time.clone() }; /// @@ -612,8 +605,7 @@ pub mod ts_microseconds_option { /// .unwrap() /// .and_hms_micro_opt(02, 04, 59, 918355) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; @@ -712,8 +704,7 @@ pub mod ts_microseconds_option { /// .unwrap() /// .and_hms_milli_opt(02, 04, 59, 918) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(); +/// .and_utc(); /// let my_s = S { time: time.clone() }; /// /// let as_string = serde_json::to_string(&my_s)?; @@ -752,8 +743,7 @@ pub mod ts_milliseconds { /// .unwrap() /// .and_hms_milli_opt(02, 04, 59, 918) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699918}"#); @@ -843,8 +833,7 @@ pub mod ts_milliseconds { /// .unwrap() /// .and_hms_milli_opt(02, 04, 59, 918) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(), +/// .and_utc(), /// ); /// let my_s = S { time: time.clone() }; /// @@ -883,8 +872,7 @@ pub mod ts_milliseconds_option { /// .unwrap() /// .and_hms_milli_opt(02, 04, 59, 918) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(), + /// .and_utc(), /// ), /// }; /// let as_string = serde_json::to_string(&my_s)?; diff --git a/src/lib.rs b/src/lib.rs index eda620e16a..b7136bc33c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,8 +140,7 @@ //! dt, //! NaiveDate::from_ymd_opt(2014, 7, 8)? //! .and_hms_opt(9, 10, 11)? -//! .and_local_timezone(Utc) -//! .unwrap() +//! .and_utc() //! ); //! //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") @@ -154,21 +153,18 @@ //! //! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)? //! .and_hms_milli_opt(9, 10, 11, 12)? -//! .and_local_timezone(Utc) -//! .unwrap(); // `2014-07-08T09:10:11.012Z` +//! .and_utc(); // `2014-07-08T09:10:11.012Z` //! assert_eq!( //! dt, //! NaiveDate::from_ymd_opt(2014, 7, 8)? //! .and_hms_micro_opt(9, 10, 11, 12_000)? -//! .and_local_timezone(Utc) -//! .unwrap() +//! .and_utc() //! ); //! assert_eq!( //! dt, //! NaiveDate::from_ymd_opt(2014, 7, 8)? //! .and_hms_nano_opt(9, 10, 11, 12_000_000)? -//! .and_local_timezone(Utc) -//! .unwrap() +//! .and_utc() //! ); //! //! // dynamic verification @@ -245,8 +241,7 @@ //! .unwrap() //! .and_hms_nano_opt(12, 45, 59, 324310806) //! .unwrap() -//! .and_local_timezone(Utc) -//! .unwrap() +//! .and_utc() //! ); //! //! // a sample of property manipulations (validates dynamically) @@ -313,8 +308,7 @@ //! .unwrap() //! .and_hms_nano_opt(12, 0, 9, 1) //! .unwrap() -//! .and_local_timezone(Utc) -//! .unwrap(); +//! .and_utc(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); //! # } //! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 1b5a84133d..b5a3d5cf4f 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -274,7 +274,7 @@ fn test_datetime_add_sub_invariant() { #[test] fn test_and_local_timezone() { let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); - let dt_utc = ndt.and_local_timezone(Utc).unwrap(); + let dt_utc = ndt.and_utc(); assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 0aaff0a973..56dc5cd891 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -76,7 +76,7 @@ mod tests; /// All methods accepting fractional seconds will accept such values. /// /// ``` -/// use chrono::{NaiveDate, NaiveTime, Utc}; +/// use chrono::{NaiveDate, NaiveTime}; /// /// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); /// @@ -89,8 +89,7 @@ mod tests; /// .unwrap() /// .and_hms_nano_opt(23, 59, 59, 1_000_000_000) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(); +/// .and_utc(); /// # let _ = (t, dt1, dt2); /// ``` /// @@ -172,14 +171,13 @@ mod tests; /// will be represented as the second part being 60, as required by ISO 8601. /// /// ``` -/// use chrono::{NaiveDate, Utc}; +/// use chrono::NaiveDate; /// /// let dt = NaiveDate::from_ymd_opt(2015, 6, 30) /// .unwrap() /// .and_hms_milli_opt(23, 59, 59, 1_000) /// .unwrap() -/// .and_local_timezone(Utc) -/// .unwrap(); +/// .and_utc(); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// diff --git a/src/round.rs b/src/round.rs index 8a76cb3ddb..b5030a9412 100644 --- a/src/round.rs +++ b/src/round.rs @@ -21,13 +21,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) /// .unwrap() /// .and_hms_milli_opt(12, 0, 0, 154) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -38,13 +37,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) /// .unwrap() /// .and_hms_milli_opt(12, 0, 0, 154) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -118,13 +116,12 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) /// .unwrap() /// .and_hms_milli_opt(12, 0, 0, 154) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// assert_eq!( /// dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -140,13 +137,12 @@ pub trait DurationRound: Sized { /// /// # Example /// ``` rust - /// # use chrono::{DurationRound, TimeDelta, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) /// .unwrap() /// .and_hms_milli_opt(12, 0, 0, 154) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// assert_eq!( /// dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" @@ -258,13 +254,12 @@ pub enum RoundingError { /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DurationRound, TimeDelta, RoundingError, Utc, NaiveDate}; + /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31) /// .unwrap() /// .and_hms_nano_opt(23, 59, 59, 1_75_500_000) /// .unwrap() - /// .and_local_timezone(Utc) - /// .unwrap(); + /// .and_utc(); /// /// assert_eq!( /// dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()), From 61d54b5de8e8878f8f80592e1e4a781a32d09b0e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 19:04:50 +0100 Subject: [PATCH 815/999] Remove unnecessary "```ignore" --- src/format/strftime.rs | 4 ++-- src/lib.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 997faa66c7..724e9ee421 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -208,8 +208,7 @@ impl<'a> StrftimeItems<'a> { /// /// # Example /// - #[cfg_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")] - #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")] + /// ``` /// use chrono::format::*; /// /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601) @@ -279,6 +278,7 @@ impl<'a> StrftimeItems<'a> { /// assert_eq!(fmtr.to_string(), "2023년 07월 11일"); /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP)); /// assert_eq!(fmtr.to_string(), "2023年07月11日"); + /// # } /// ``` #[cfg(feature = "unstable-locales")] #[must_use] diff --git a/src/lib.rs b/src/lib.rs index b7136bc33c..bc7c05b81d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,8 +128,7 @@ //! This is a bit verbose due to Rust's lack of function and method overloading, //! but in turn we get a rich combination of initialization methods. //! -#![cfg_attr(not(feature = "now"), doc = "```ignore")] -#![cfg_attr(feature = "now", doc = "```rust")] +//! ``` //! use chrono::offset::MappedLocalTime; //! use chrono::prelude::*; //! From 6f2c7ccabd94e78f4bbf585085b54e21eb6e99fe Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 19:04:00 +0100 Subject: [PATCH 816/999] Use different method to run feature-dependent doctests --- src/datetime/mod.rs | 10 ++++++---- src/format/mod.rs | 5 +++-- src/format/parsed.rs | 10 ++++++---- src/format/strftime.rs | 4 ++-- src/lib.rs | 15 +++++++++------ src/naive/time/mod.rs | 10 ++++++---- src/offset/fixed.rs | 10 ++++++---- src/weekday.rs | 5 +++-- 8 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8a8234ae84..3271835442 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -79,8 +79,8 @@ impl DateTime { /// /// # Example /// - #[cfg_attr(not(feature = "clock"), doc = "```ignore")] - #[cfg_attr(feature = "clock", doc = "```rust")] + /// ``` + /// # #[cfg(feature = "clock")] { /// use chrono::{DateTime, Local}; /// /// let dt = Local::now(); @@ -90,6 +90,7 @@ impl DateTime { /// // Serialize, pass through FFI... and recreate the `DateTime`: /// let dt_new = DateTime::::from_naive_utc_and_offset(naive_utc, offset); /// assert_eq!(dt, dt_new); + /// # } /// ``` #[inline] #[must_use] @@ -691,8 +692,8 @@ impl DateTime { /// /// # Example /// - #[cfg_attr(not(feature = "clock"), doc = "```ignore")] - #[cfg_attr(feature = "clock", doc = "```rust")] + /// ``` + /// # #[cfg(feature = "clock")] { /// use chrono::{Local, NaiveTime}; /// /// let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap(); @@ -701,6 +702,7 @@ impl DateTime { /// /// assert_eq!(today_noon.single().unwrap().time(), noon); /// assert_eq!(today_midnight.single().unwrap().time(), NaiveTime::MIN); + /// # } /// ``` #[must_use] pub fn with_time(&self, time: NaiveTime) -> LocalResult { diff --git a/src/format/mod.rs b/src/format/mod.rs index c2401a7fc1..d67a20177c 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -16,8 +16,8 @@ //! C's `strftime` format. The available options can be found [here](./strftime/index.html). //! //! # Example -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -#![cfg_attr(feature = "std", doc = "```rust")] +//! ``` +//! # #[cfg(feature = "std")] { //! use chrono::{NaiveDateTime, TimeZone, Utc}; //! //! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); @@ -27,6 +27,7 @@ //! //! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc(); //! assert_eq!(parsed, date_time); +//! # } //! # Ok::<(), chrono::ParseError>(()) //! ``` diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 4f74b85224..8d1cde2d5a 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -57,8 +57,8 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// /// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent. /// -#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] -#[cfg_attr(feature = "alloc", doc = "```rust")] +/// ``` +/// # #[cfg(feature = "alloc")] { /// use chrono::format::{ParseErrorKind, Parsed}; /// use chrono::Weekday; /// @@ -89,6 +89,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// if let Err(error) = result { /// assert_eq!(error.kind(), ParseErrorKind::Impossible); /// } +/// # } /// # Ok::<(), chrono::ParseError>(()) /// ``` /// @@ -98,8 +99,8 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 /// [`format::parse()`]: crate::format::parse() /// -#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] -#[cfg_attr(feature = "alloc", doc = "```rust")] +/// ``` +/// # #[cfg(feature = "alloc")] { /// use chrono::format::{parse, Fixed, Item, Parsed}; /// use chrono::Weekday; /// @@ -120,6 +121,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// // What is the weekday? /// assert_eq!(parsed.weekday(), Some(Weekday::Thu)); /// } +/// # } /// # Ok::<(), chrono::ParseError>(()) /// ``` #[allow(clippy::manual_non_exhaustive)] diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 724e9ee421..555e36f9c0 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -258,8 +258,8 @@ impl<'a> StrftimeItems<'a> { /// /// # Example /// - #[cfg_attr(not(any(feature = "alloc", feature = "std")), doc = "```ignore")] - #[cfg_attr(any(feature = "alloc", feature = "std"), doc = "```rust")] + /// ``` + /// # #[cfg(feature = "alloc")] { /// use chrono::format::{Locale, StrftimeItems}; /// use chrono::{FixedOffset, TimeZone}; /// diff --git a/src/lib.rs b/src/lib.rs index bc7c05b81d..ef9794ffed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,20 +108,22 @@ //! or in the local time zone //! ([`Local::now()`](./offset/struct.Local.html#method.now)). //! -#![cfg_attr(not(feature = "now"), doc = "```ignore")] -#![cfg_attr(feature = "now", doc = "```rust")] +//! ``` +//! # #[cfg(feature = "now")] { //! use chrono::prelude::*; //! //! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! # let _ = utc; +//! # } //! ``` //! -#![cfg_attr(not(feature = "clock"), doc = "```ignore")] -#![cfg_attr(feature = "clock", doc = "```rust")] +//! ``` +//! # #[cfg(feature = "clock")] { //! use chrono::prelude::*; //! //! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` //! # let _ = local; +//! # } //! ``` //! //! Alternatively, you can create your own date and time. @@ -382,8 +384,8 @@ //! [`DateTime.timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos) //! to get the number of additional number of nanoseconds. //! -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -#![cfg_attr(feature = "std", doc = "```rust")] +//! ``` +//! # #[cfg(feature = "std")] { //! // We need the trait in scope to use Utc::timestamp(). //! use chrono::{DateTime, Utc}; //! @@ -394,6 +396,7 @@ //! // Get epoch value from a datetime: //! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); //! assert_eq!(dt.timestamp(), 1_500_000_000); +//! # } //! ``` //! //! ### Naive date and time diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 56dc5cd891..7fe26a3615 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -948,12 +948,13 @@ impl Timelike for NaiveTime { /// ([Why?](#leap-second-handling)) /// Use the proper [formatting method](#method.format) to get a human-readable representation. /// - #[cfg_attr(not(feature = "std"), doc = "```ignore")] - #[cfg_attr(feature = "std", doc = "```")] + /// ``` + /// # #[cfg(feature = "std")] { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.second(), 59); /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); + /// # } /// ``` #[inline] fn second(&self) -> u32 { @@ -980,12 +981,13 @@ impl Timelike for NaiveTime { /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or /// use the proper [formatting method](#method.format) to get a human-readable representation. /// - #[cfg_attr(not(feature = "std"), doc = "```ignore")] - #[cfg_attr(feature = "std", doc = "```")] + /// ``` + /// # #[cfg(feature = "std")] { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.nanosecond(), 1_000_000_000); /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); + /// # } /// ``` #[inline] fn nanosecond(&self) -> u32 { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 24cd983766..59c6aeec3a 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -49,13 +49,14 @@ impl FixedOffset { /// /// # Example /// - #[cfg_attr(not(feature = "std"), doc = "```ignore")] - #[cfg_attr(feature = "std", doc = "```")] + /// ``` + /// # #[cfg(feature = "std")] { /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = /// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") + /// # } /// ``` #[must_use] pub const fn east_opt(secs: i32) -> Option { @@ -83,13 +84,14 @@ impl FixedOffset { /// /// # Example /// - #[cfg_attr(not(feature = "std"), doc = "```ignore")] - #[cfg_attr(feature = "std", doc = "```")] + /// ``` + /// # #[cfg(feature = "std")] { /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = /// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") + /// # } /// ``` #[must_use] pub const fn west_opt(secs: i32) -> Option { diff --git a/src/weekday.rs b/src/weekday.rs index 618d5355d6..72ce34084a 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -123,8 +123,8 @@ impl Weekday { /// /// # Example /// - #[cfg_attr(not(feature = "clock"), doc = "```ignore")] - #[cfg_attr(feature = "clock", doc = "```rust")] + /// ``` + /// # #[cfg(feature = "clock")] { /// # use chrono::{Local, Datelike}; /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays. /// // Use `num_days_from_monday` to index into the array. @@ -132,6 +132,7 @@ impl Weekday { /// /// let today = Local::now().weekday(); /// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]); + /// # } /// ``` #[inline] pub const fn num_days_from_monday(&self) -> u32 { From 1b578597827d0a850acb81b9cbbd1835b2141a8d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 19:10:51 +0100 Subject: [PATCH 817/999] Run doctests with `alloc` feature if possible --- src/format/mod.rs | 2 +- src/lib.rs | 2 +- src/naive/time/mod.rs | 4 ++-- src/offset/fixed.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d67a20177c..01770f9076 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -17,7 +17,7 @@ //! //! # Example //! ``` -//! # #[cfg(feature = "std")] { +//! # #[cfg(feature = "alloc")] { //! use chrono::{NaiveDateTime, TimeZone, Utc}; //! //! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index ef9794ffed..f35b1535bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,7 +385,7 @@ //! to get the number of additional number of nanoseconds. //! //! ``` -//! # #[cfg(feature = "std")] { +//! # #[cfg(feature = "alloc")] { //! // We need the trait in scope to use Utc::timestamp(). //! use chrono::{DateTime, Utc}; //! diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 7fe26a3615..cecc19192d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -949,7 +949,7 @@ impl Timelike for NaiveTime { /// Use the proper [formatting method](#method.format) to get a human-readable representation. /// /// ``` - /// # #[cfg(feature = "std")] { + /// # #[cfg(feature = "alloc")] { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.second(), 59); @@ -982,7 +982,7 @@ impl Timelike for NaiveTime { /// use the proper [formatting method](#method.format) to get a human-readable representation. /// /// ``` - /// # #[cfg(feature = "std")] { + /// # #[cfg(feature = "alloc")] { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.nanosecond(), 1_000_000_000); diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 59c6aeec3a..e7382bed1d 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -50,7 +50,7 @@ impl FixedOffset { /// # Example /// /// ``` - /// # #[cfg(feature = "std")] { + /// # #[cfg(feature = "alloc")] { /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = @@ -85,7 +85,7 @@ impl FixedOffset { /// # Example /// /// ``` - /// # #[cfg(feature = "std")] { + /// # #[cfg(feature = "alloc")] { /// use chrono::{FixedOffset, TimeZone}; /// let hour = 3600; /// let datetime = From 1e8df65f47577c0db3e2790660b00d728145cde2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 19:19:29 +0100 Subject: [PATCH 818/999] Rustfmt doc comments --- src/lib.rs | 15 ++++++++++----- src/naive/datetime/serde.rs | 3 +-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f35b1535bb..6fc07ae183 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ //! # Chrono: Date and Time for Rust //! - //! Chrono aims to provide all functionality needed to do correct operations on dates and times in the //! [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): //! @@ -255,10 +254,16 @@ //! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); //! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::try_seconds(-2 * 3600 + 2).unwrap()); //! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::try_seconds(2 * 3600 - 2).unwrap()); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::try_seconds(1_000_000_000).unwrap(), -//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::try_seconds(1_000_000_000).unwrap(), -//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! + TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap() +//! ); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! - TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap() +//! ); //! ``` //! //! ### Formatting and Parsing diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 45c1935d6a..2beda08745 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1047,8 +1047,7 @@ pub mod ts_seconds_option { /// time: Option, /// } /// - /// let expected = - /// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap(); + /// let expected = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap(); /// let my_s = S { time: Some(expected) }; /// let as_string = serde_json::to_string(&my_s)?; /// assert_eq!(as_string, r#"{"time":1526522699}"#); From 3adfd88ce05756e3bd0baa7e008abbe6d5b61076 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 18:31:52 +0100 Subject: [PATCH 819/999] Main documentation: simplify links and reflow text --- src/lib.rs | 186 ++++++++++++++++++++++++----------------------------- 1 file changed, 84 insertions(+), 102 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6fc07ae183..80a6e3c6af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,32 +1,33 @@ //! # Chrono: Date and Time for Rust //! -//! Chrono aims to provide all functionality needed to do correct operations on dates and times in the -//! [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar): +//! Chrono aims to provide all functionality needed to do correct operations on dates and times in +//! the [proleptic Gregorian calendar]: //! -//! * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware -//! by default, with separate timezone-naive types. +//! * The [`DateTime`] type is timezone-aware by default, with separate timezone-naive types. //! * Operations that may produce an invalid or ambiguous date and time return `Option` or -//! [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). -//! * Configurable parsing and formatting with a `strftime` inspired date and time formatting syntax. -//! * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with -//! the current timezone of the OS. +//! [`MappedLocalTime`]. +//! * Configurable parsing and formatting with a `strftime` inspired date and time formatting +//! syntax. +//! * The [`Local`] timezone works with the current timezone of the OS. //! * Types and operations are implemented to be reasonably efficient. //! -//! Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate -//! [Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for -//! full timezone support. +//! Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion +//! crate [Chrono-TZ] or [`tzfile`] for full timezone support. +//! +//! [proleptic Gregorian calendar]: https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar +//! [Chrono-TZ]: https://crates.io/crates/chrono-tz +//! [`tzfile`]: https://crates.io/crates/tzfile //! //! ### Features //! -//! Chrono supports various runtime environments and operating systems, and has -//! several features that may be enabled or disabled. +//! Chrono supports various runtime environments and operating systems, and has several features +//! that may be enabled or disabled. //! //! Default features: //! //! - `alloc`: Enable features that depend on allocation (primarily string formatting). -//! - `std`: Enables functionality that depends on the standard library. This -//! is a superset of `alloc` and adds interoperation with standard library types -//! and traits. +//! - `std`: Enables functionality that depends on the standard library. This is a superset of +//! `alloc` and adds interoperation with standard library types and traits. //! - `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. //! - `now`: Enables reading the system time (`now`). //! - `wasmbind`: Interface with the JS Date API for the `wasm32` target. @@ -35,16 +36,19 @@ //! //! - `serde`: Enable serialization/deserialization via [serde]. //! - `rkyv`: Deprecated, use the `rkyv-*` features. -//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], using 16-bit integers for integral `*size` types. -//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types. -//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types. +//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], +//! using 16-bit integers for integral `*size` types. +//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], +//! using 32-bit integers for integral `*size` types. +//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], +//! using 64-bit integers for integral `*size` types. //! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. //! - `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). //! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. -//! - `unstable-locales`: Enable localization. This adds various methods with a -//! `_localized` suffix. The implementation and API may change or even be -//! removed in a patch release. Feedback welcome. -//! - `oldtime`: This feature no longer has any effect; it used to offer compatibility with the `time` 0.1 crate. +//! - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. +//! The implementation and API may change or even be removed in a patch release. Feedback welcome. +//! - `oldtime`: This feature no longer has any effect; it used to offer compatibility with the +//! `time` 0.1 crate. //! //! Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. //! @@ -58,54 +62,45 @@ //! //! ### Time delta / Duration //! -//! Chrono has a [`TimeDelta`] type to represent the magnitude of a time span. This is an -//! "accurate" duration represented as seconds and nanoseconds, and does not represent "nominal" -//! components such as days or months. +//! Chrono has a [`TimeDelta`] type to represent the magnitude of a time span. This is an "accurate" +//! duration represented as seconds and nanoseconds, and does not represent "nominal" components +//! such as days or months. //! //! The [`TimeDelta`] type was previously named `Duration` (and is still available as a type alias //! with that name). A notable difference with the similar [`core::time::Duration`] is that it is a //! signed value instead of unsigned. //! -//! Chrono currently only supports a small number of operations with [`core::time::Duration`] . +//! Chrono currently only supports a small number of operations with [`core::time::Duration`]. //! You can convert between both types with the [`TimeDelta::from_std`] and [`TimeDelta::to_std`] //! methods. //! //! ### Date and Time //! -//! Chrono provides a -//! [**`DateTime`**](./struct.DateTime.html) -//! type to represent a date and a time in a timezone. +//! Chrono provides a [`DateTime`] type to represent a date and a time in a timezone. //! -//! For more abstract moment-in-time tracking such as internal timekeeping -//! that is unconcerned with timezones, consider -//! [`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -//! which tracks your system clock, or -//! [`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -//! is an opaque but monotonically-increasing representation of a moment in time. +//! For more abstract moment-in-time tracking such as internal timekeeping that is unconcerned with +//! timezones, consider [`std::time::SystemTime`], which tracks your system clock, or +//! [`std::time::Instant`], which is an opaque but monotonically-increasing representation of a +//! moment in time. //! -//! `DateTime` is timezone-aware and must be constructed from -//! the [**`TimeZone`**](./offset/trait.TimeZone.html) object, -//! which defines how the local date is converted to and back from the UTC date. -//! There are three well-known `TimeZone` implementations: +//! [`DateTime`] is timezone-aware and must be constructed from a [`TimeZone`] object, which defines +//! how the local date is converted to and back from the UTC date. +//! There are three well-known [`TimeZone`] implementations: //! -//! * [**`Utc`**](./offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. +//! * [`Utc`] specifies the UTC time zone. It is most efficient. //! -//! * [**`Local`**](./offset/struct.Local.html) specifies the system local time zone. +//! * [`Local`] specifies the system local time zone. //! -//! * [**`FixedOffset`**](./offset/struct.FixedOffset.html) specifies -//! an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. -//! This often results from the parsed textual date and time. -//! Since it stores the most information and does not depend on the system environment, -//! you would want to normalize other `TimeZone`s into this type. +//! * [`FixedOffset`] specifies an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. +//! This often results from the parsed textual date and time. Since it stores the most information +//! and does not depend on the system environment, you would want to normalize other `TimeZone`s +//! into this type. //! -//! `DateTime`s with different `TimeZone` types are distinct and do not mix, -//! but can be converted to each other using -//! the [`DateTime::with_timezone`](./struct.DateTime.html#method.with_timezone) method. +//! [`DateTime`]s with different [`TimeZone`] types are distinct and do not mix, but can be +//! converted to each other using the [`DateTime::with_timezone`] method. //! -//! You can get the current date and time in the UTC time zone -//! ([`Utc::now()`](./offset/struct.Utc.html#method.now)) -//! or in the local time zone -//! ([`Local::now()`](./offset/struct.Local.html#method.now)). +//! You can get the current date and time in the UTC time zone ([`Utc::now()`]) or in the local time +//! zone ([`Local::now()`]). //! //! ``` //! # #[cfg(feature = "now")] { @@ -125,9 +120,9 @@ //! # } //! ``` //! -//! Alternatively, you can create your own date and time. -//! This is a bit verbose due to Rust's lack of function and method overloading, -//! but in turn we get a rich combination of initialization methods. +//! Alternatively, you can create your own date and time. This is a bit verbose due to Rust's lack +//! of function and method overloading, but in turn we get a rich combination of initialization +//! methods. //! //! ``` //! use chrono::offset::MappedLocalTime; @@ -202,9 +197,8 @@ //! # doctest().unwrap(); //! ``` //! -//! Various properties are available to the date and time, and can be altered individually. -//! Most of them are defined in the traits [`Datelike`](./trait.Datelike.html) and -//! [`Timelike`](./trait.Timelike.html) which you should `use` before. +//! Various properties are available to the date and time, and can be altered individually. Most of +//! them are defined in the traits [`Datelike`] and [`Timelike`] which you should `use` before. //! Addition and subtraction is also supported. //! The following illustrates most supported operations to the date and time: //! @@ -268,20 +262,18 @@ //! //! ### Formatting and Parsing //! -//! Formatting is done via the [`format`](./struct.DateTime.html#method.format) method, -//! which format is equivalent to the familiar `strftime` format. +//! Formatting is done via the [`format`](DateTime::format()) method, which format is equivalent to +//! the familiar `strftime` format. //! -//! See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and list of +//! specifiers. //! //! The default `to_string` method and `{:?}` specifier also give a reasonable representation. -//! Chrono also provides [`to_rfc2822`](./struct.DateTime.html#method.to_rfc2822) and -//! [`to_rfc3339`](./struct.DateTime.html#method.to_rfc3339) methods -//! for well-known formats. +//! Chrono also provides [`to_rfc2822`](DateTime::to_rfc2822) and +//! [`to_rfc3339`](DateTime::to_rfc3339) methods for well-known formats. //! -//! Chrono now also provides date formatting in almost any language without the -//! help of an additional C library. This functionality is under the feature -//! `unstable-locales`: +//! Chrono now also provides date formatting in almost any language without the help of an +//! additional C library. This functionality is under the feature `unstable-locales`: //! //! ```toml //! chrono = { version = "0.4", features = ["unstable-locales"] } @@ -326,24 +318,18 @@ //! //! Parsing can be done with two methods: //! -//! 1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait -//! (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method -//! on a string) can be used for parsing `DateTime`, `DateTime` and -//! `DateTime` values. This parses what the `{:?}` -//! ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) -//! format specifier prints, and requires the offset to be present. -//! -//! 2. [`DateTime::parse_from_str`](./struct.DateTime.html#method.parse_from_str) parses -//! a date and time with offsets and returns `DateTime`. -//! This should be used when the offset is a part of input and the caller cannot guess that. -//! It *cannot* be used when the offset can be missing. -//! [`DateTime::parse_from_rfc2822`](./struct.DateTime.html#method.parse_from_rfc2822) -//! and -//! [`DateTime::parse_from_rfc3339`](./struct.DateTime.html#method.parse_from_rfc3339) -//! are similar but for well-known formats. -//! -//! More detailed control over the parsing process is available via -//! [`format`](./format/index.html) module. +//! 1. The standard [`FromStr`](std::str::FromStr) trait (and [`parse`](str::parse) method on a +//! string) can be used for parsing `DateTime`, `DateTime` and +//! `DateTime` values. This parses what the `{:?}` ([`std::fmt::Debug`] format specifier +//! prints, and requires the offset to be present. +//! +//! 2. [`DateTime::parse_from_str`] parses a date and time with offsets and returns +//! `DateTime`. This should be used when the offset is a part of input and the +//! caller cannot guess that. It *cannot* be used when the offset can be missing. +//! [`DateTime::parse_from_rfc2822`] and [`DateTime::parse_from_rfc3339`] are similar but for +//! well-known formats. +//! +//! More detailed control over the parsing process is available via [`format`](mod@format) module. //! //! ```rust //! use chrono::prelude::*; @@ -375,8 +361,8 @@ //! assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); //! ``` //! -//! Again : See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! Again: See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and +//! list of specifiers. //! //! ### Conversion from and to EPOCH timestamps //! @@ -406,20 +392,16 @@ //! //! ### Naive date and time //! -//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -//! as [**`NaiveDate`**](./naive/struct.NaiveDate.html), -//! [**`NaiveTime`**](./naive/struct.NaiveTime.html) and -//! [**`NaiveDateTime`**](./naive/struct.NaiveDateTime.html) respectively. +//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` as +//! [`NaiveDate`], [`NaiveTime`] and [`NaiveDateTime`] respectively. //! -//! They have almost equivalent interfaces as their timezone-aware twins, -//! but are not associated to time zones obviously and can be quite low-level. -//! They are mostly useful for building blocks for higher-level types. +//! They have almost equivalent interfaces as their timezone-aware twins, but are not associated to +//! time zones obviously and can be quite low-level. They are mostly useful for building blocks for +//! higher-level types. //! //! Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -//! [`naive_local`](./struct.DateTime.html#method.naive_local) returns -//! a view to the naive local time, -//! and [`naive_utc`](./struct.DateTime.html#method.naive_utc) returns -//! a view to the naive UTC time. +//! [`naive_local`](DateTime::naive_local) returns a view to the naive local time, +//! and [`naive_utc`](DateTime::naive_utc) returns a view to the naive UTC time. //! //! ## Limitations //! @@ -427,7 +409,7 @@ //! * Date types are limited to about +/- 262,000 years from the common epoch. //! * Time types are limited to nanosecond accuracy. //! * Leap seconds can be represented, but Chrono does not fully support them. -//! See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling). +//! See [Leap Second Handling](NaiveTime#leap-second-handling). //! //! ## Rust version requirements //! From e05ba8b9c2a98943d129e664c7615bd3724e069e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 21 Mar 2024 07:22:26 +0100 Subject: [PATCH 820/999] Add `MappedLocalTime::and_then` --- src/offset/local/unix.rs | 2 +- src/offset/mod.rs | 43 +++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index ad99542330..c1942eba7e 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -166,6 +166,6 @@ impl Cache { self.zone .find_local_time_type_from_local(d.and_utc().timestamp(), d.year()) .expect("unable to select local time type") - .map(|o| FixedOffset::east_opt(o.offset()).unwrap()) + .and_then(|o| FixedOffset::east_opt(o.offset())) } } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index c1d1cc5619..89aafb61be 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -121,6 +121,24 @@ impl MappedLocalTime { MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)), } } + + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. + /// + /// Returns `MappedLocalTime::None` if the function returns `None`. + #[must_use] + pub(crate) fn and_then Option>(self, mut f: F) -> MappedLocalTime { + match self { + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => match f(v) { + Some(new) => MappedLocalTime::Single(new), + None => MappedLocalTime::None, + }, + MappedLocalTime::Ambiguous(min, max) => match (f(min), f(max)) { + (Some(min), Some(max)) => MappedLocalTime::Ambiguous(min, max), + _ => MappedLocalTime::None, + }, + } + } } /// The conversion result from the local time to the timezone-aware datetime types. @@ -547,26 +565,11 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime> { - // Return `MappedLocalTime::None` when the offset pushes a value out of range, instead of - // panicking. - match self.offset_from_local_datetime(local) { - MappedLocalTime::None => MappedLocalTime::None, - MappedLocalTime::Single(offset) => match local.checked_sub_offset(offset.fix()) { - Some(dt) => { - MappedLocalTime::Single(DateTime::from_naive_utc_and_offset(dt, offset)) - } - None => MappedLocalTime::None, - }, - MappedLocalTime::Ambiguous(o1, o2) => { - match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { - (Some(d1), Some(d2)) => MappedLocalTime::Ambiguous( - DateTime::from_naive_utc_and_offset(d1, o1), - DateTime::from_naive_utc_and_offset(d2, o2), - ), - _ => MappedLocalTime::None, - } - } - } + self.offset_from_local_datetime(local).and_then(|off| { + local + .checked_sub_offset(off.fix()) + .map(|dt| DateTime::from_naive_utc_and_offset(dt, off)) + }) } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. From 1850198da9f91fb0f4181e30947cbecb944cde74 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Mar 2024 16:06:29 +0100 Subject: [PATCH 821/999] Revert `TimeDelta` deprecations --- src/time_delta.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index 9785151998..ddab0d57a7 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -102,7 +102,6 @@ impl TimeDelta { /// Panics when the duration is out of bounds. #[inline] #[must_use] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_weeks` instead")] pub const fn weeks(weeks: i64) -> TimeDelta { expect(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") } @@ -130,7 +129,6 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_days` instead")] pub const fn days(days: i64) -> TimeDelta { expect(TimeDelta::try_days(days), "TimeDelta::days out of bounds") } @@ -157,7 +155,6 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_hours` instead")] pub const fn hours(hours: i64) -> TimeDelta { expect(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") } @@ -183,7 +180,6 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_minutes` instead")] pub const fn minutes(minutes: i64) -> TimeDelta { expect(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") } @@ -208,7 +204,6 @@ impl TimeDelta { /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). #[inline] #[must_use] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_seconds` instead")] pub const fn seconds(seconds: i64) -> TimeDelta { expect(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") } @@ -232,7 +227,6 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] - #[deprecated(since = "0.4.35", note = "Use `TimeDelta::try_milliseconds` instead")] pub const fn milliseconds(milliseconds: i64) -> TimeDelta { expect(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } @@ -688,7 +682,6 @@ mod tests { } #[test] - #[allow(deprecated)] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_max_overflow_panic() { let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); @@ -710,7 +703,6 @@ mod tests { } #[test] - #[allow(deprecated)] #[should_panic(expected = "TimeDelta::seconds out of bounds")] fn test_duration_seconds_min_underflow_panic() { let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); @@ -773,7 +765,6 @@ mod tests { } #[test] - #[allow(deprecated)] #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] fn test_duration_milliseconds_min_underflow_panic() { // Here we ensure that trying to create a value one millisecond below the From ca3c3b6293c20284405b1fa6c6895c7f2a33caf9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Mar 2024 16:07:14 +0100 Subject: [PATCH 822/999] Prepare 0.4.36 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index a1dbb0e2e9..f3bf88b582 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-03-06 -version: 0.4.35 +date-released: 2024-03-26 +version: 0.4.36 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 5349c2c6ba..f0eed71410 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.35" +version = "0.4.36" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 9e22e48d156d4c581e4f3d54cd8b2dcf77b5ae0a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Mar 2024 07:22:21 +0100 Subject: [PATCH 823/999] Swap `MappedLocalTime` and `LocalResult` type alias --- src/offset/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 89aafb61be..c8f9c59c20 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -54,8 +54,11 @@ pub use self::utc::Utc; /// the `MappedLocalTime` type to help deal with the result correctly. /// /// The type of `T` is usually a [`DateTime`] but may also be only an offset. +pub type MappedLocalTime = LocalResult; #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] -pub enum MappedLocalTime { + +/// Old name of [`MappedLocalTime`]. See that type for more documentation. +pub enum LocalResult { /// The local time maps to a single unique result. Single(T), @@ -141,9 +144,6 @@ impl MappedLocalTime { } } -/// The conversion result from the local time to the timezone-aware datetime types. -pub type LocalResult = MappedLocalTime; - #[allow(deprecated)] impl MappedLocalTime> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. From 6857d00070378037392fd9f029ccbf78a19ef2c3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Mar 2024 07:32:29 +0100 Subject: [PATCH 824/999] Hide re-export of `LocalResult` in docs --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 80a6e3c6af..07b5b9d4d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -573,9 +573,11 @@ pub mod offset; #[cfg(feature = "clock")] #[doc(inline)] pub use offset::Local; +#[doc(hidden)] +pub use offset::LocalResult; +pub use offset::MappedLocalTime; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; -pub use offset::{LocalResult, MappedLocalTime}; pub mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; From 7d62045ec4cd667e240fde899a99458bf8eae1f1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Mar 2024 07:23:00 +0100 Subject: [PATCH 825/999] Prepare 0.4.37 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index f3bf88b582..0e44adfac8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-03-26 -version: 0.4.36 +date-released: 2024-03-27 +version: 0.4.37 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index f0eed71410..495ac35c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.36" +version = "0.4.37" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 6c86cd1560652dc67557c1bf034b209241e5ce53 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Fri, 29 Mar 2024 21:02:24 -0700 Subject: [PATCH 826/999] Remove rustc-serialize from library --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 6 +- src/datetime/mod.rs | 54 +---------- src/datetime/rustc_serialize.rs | 124 -------------------------- src/format/formatting.rs | 8 +- src/format/mod.rs | 2 +- src/lib.rs | 5 -- src/month.rs | 1 - src/naive/date/mod.rs | 38 +------- src/naive/datetime/mod.rs | 24 +---- src/naive/datetime/rustc_serialize.rs | 75 ---------------- src/naive/mod.rs | 3 - src/naive/time/mod.rs | 7 +- src/naive/time/rustc_serialize.rs | 30 ------- src/weekday.rs | 1 - 16 files changed, 18 insertions(+), 364 deletions(-) delete mode 100644 src/datetime/rustc_serialize.rs delete mode 100644 src/naive/datetime/rustc_serialize.rs delete mode 100644 src/naive/time/rustc_serialize.rs diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 8893465406..8c84ea1c14 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -2,7 +2,7 @@ name: codecov env: # It's really `--all-features`, but not adding the mutually exclusive features from rkyv - ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation serde arbitrary" on: push: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 65840a0078..173883b433 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,7 +2,7 @@ name: lint env: # It's really `--all-features`, but not adding the mutually exclusive features from rkyv - ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation serde arbitrary" on: push: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 775faad3ed..505e2df5ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: All Tests and Builds env: # It's really `--all-features`, but not adding the mutually exclusive features from rkyv - ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-32 rkyv-validation rustc-serialize serde arbitrary" + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-32 rkyv-validation serde arbitrary" on: push: @@ -41,12 +41,12 @@ jobs: - run: | cargo test --lib \ --features \ - unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \ + unstable-locales,wasmbind,oldtime,clock,winapi,serde \ --color=always -- --color=always - run: | cargo test --doc \ --features \ - unstable-locales,wasmbind,oldtime,clock,rustc-serialize,winapi,serde \ + unstable-locales,wasmbind,oldtime,clock,winapi,serde \ --color=always -- --color=always rust_versions: diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3271835442..5e693ea5c7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -34,9 +34,6 @@ use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(feature = "rustc-serialize")] -pub(super) mod rustc_serialize; - /// documented at re-export site #[cfg(feature = "serde")] pub(super) mod serde; @@ -1937,7 +1934,7 @@ where /// 719163 const UNIX_EPOCH_DAY: i64 = 719_163; -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where FUtc: Fn(&DateTime) -> Result, @@ -1967,7 +1964,7 @@ where ); } -#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "clock", feature = "serde"))] fn test_decodable_json( utc_from_str: FUtc, fixed_from_str: FFixed, @@ -2022,50 +2019,3 @@ fn test_decodable_json( assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); } - -#[cfg(all(test, feature = "clock", feature = "rustc-serialize"))] -fn test_decodable_json_timestamps( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap())) - ); - assert_eq!( - norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap())) - ); - - assert_eq!( - norm(&fixed_from_str("0").ok().map(DateTime::from)), - norm(&Some( - FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - )) - ); - assert_eq!( - norm(&fixed_from_str("-1").ok().map(DateTime::from)), - norm(&Some( - FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap() - )) - ); - - assert_eq!( - *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - ); - assert_eq!( - *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.with_ymd_and_hms(1969, 12, 31, 23, 59, 59).unwrap() - ); -} diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs deleted file mode 100644 index 5236a52fad..0000000000 --- a/src/datetime/rustc_serialize.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::DateTime; -use crate::format::SecondsFormat; -#[cfg(feature = "clock")] -use crate::offset::Local; -use crate::offset::{FixedOffset, MappedLocalTime, TimeZone, Utc}; -use core::fmt; -use core::ops::Deref; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - -impl Encodable for DateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - self.to_rfc3339_opts(SecondsFormat::AutoSi, true).encode(s) - } -} - -// Function to convert a MappedLocalTime into a serde-ish Result -fn from(me: MappedLocalTime, d: &mut D) -> Result -where - D: Decoder, - T: fmt::Display, -{ - match me { - MappedLocalTime::None => Err(d.error("value is not a legal timestamp")), - MappedLocalTime::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - MappedLocalTime::Single(val) => Ok(val), - } -} - -impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()?.parse::>().map_err(|_| d.error("invalid date and time")) - } -} - -#[allow(deprecated)] -impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(FixedOffset::east_opt(0).unwrap().timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } -} - -impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map(|dt| dt.with_timezone(&Utc)) - .map_err(|_| d.error("invalid date and time")) - } -} - -/// A [`DateTime`] that can be deserialized from a timestamp -/// -/// A timestamp here is seconds since the epoch -#[derive(Debug)] -pub struct TsSeconds(DateTime); - -#[allow(deprecated)] -impl From> for DateTime { - /// Pull the inner `DateTime` out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> DateTime { - obj.0 - } -} - -#[allow(deprecated)] -impl Deref for TsSeconds { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[allow(deprecated)] -impl Decodable for TsSeconds { - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } -} - -#[cfg(feature = "clock")] -impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - match d.read_str()?.parse::>() { - Ok(dt) => Ok(dt.with_timezone(&Local)), - Err(_) => Err(d.error("invalid date and time")), - } - } -} - -#[cfg(feature = "clock")] -#[allow(deprecated)] -impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(|dt| TsSeconds(dt.with_timezone(&Local))) - } -} - -#[cfg(test)] -mod tests { - use crate::datetime::test_encodable_json; - use crate::datetime::{test_decodable_json, test_decodable_json_timestamps}; - use rustc_serialize::json; - - #[test] - fn test_encodable() { - test_encodable_json(json::encode, json::encode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable() { - test_decodable_json(json::decode, json::decode, json::decode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable_timestamps() { - test_decodable_json_timestamps(json::decode, json::decode, json::decode); - } -} diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 35c253fff1..80bfc2d010 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -13,14 +13,14 @@ use core::fmt::{self, Write}; #[cfg(feature = "alloc")] use crate::offset::Offset; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde"))] use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; #[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, Weekday}; #[cfg(feature = "alloc")] use super::locales; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde"))] use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(feature = "alloc")] use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; @@ -420,7 +420,7 @@ fn format_inner( } } -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -530,7 +530,7 @@ pub enum SecondsFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde"))] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, diff --git a/src/format/mod.rs b/src/format/mod.rs index 01770f9076..447efde817 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -57,7 +57,7 @@ pub(crate) mod locales; pub(crate) use formatting::write_hundreds; #[cfg(feature = "alloc")] pub(crate) use formatting::write_rfc2822; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] +#[cfg(any(feature = "alloc", feature = "serde"))] pub(crate) use formatting::write_rfc3339; pub use formatting::SecondsFormat; #[cfg(feature = "alloc")] diff --git a/src/lib.rs b/src/lib.rs index 07b5b9d4d8..839444c94f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -508,9 +508,6 @@ #![warn(unreachable_pub)] #![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -// can remove this if/when rustc-serialize support is removed -// keeps clippy happy in the meantime -#![cfg_attr(feature = "rustc-serialize", allow(deprecated))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "alloc")] @@ -551,8 +548,6 @@ pub use date::Date; pub use date::{MAX_DATE, MIN_DATE}; mod datetime; -#[cfg(feature = "rustc-serialize")] -pub use datetime::rustc_serialize::TsSeconds; pub use datetime::DateTime; #[allow(deprecated)] #[doc(no_inline)] diff --git a/src/month.rs b/src/month.rs index 4353dda042..f8033fccd6 100644 --- a/src/month.rs +++ b/src/month.rs @@ -29,7 +29,6 @@ use crate::OutOfRange; /// 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, Ord)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr( any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), derive(Archive, Deserialize, Serialize), diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index c5bd66e7ce..4e626d9f7c 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2387,7 +2387,7 @@ const YEAR_DELTAS: &[u8; 401] = &[ 96, 97, 97, 97, 97, // 400+1 ]; -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_encodable_json(to_string: F) where F: Fn(&NaiveDate) -> Result, @@ -2409,7 +2409,7 @@ where assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262142-12-31""#.into())); } -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_decodable_json(from_str: F) where F: Fn(&str) -> Result, @@ -2452,40 +2452,6 @@ where assert!(from_str(r#"null"#).is_err()); } -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveDate; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveDate { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDate { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date")) - } - } - - #[cfg(test)] - mod tests { - use crate::naive::date::{test_decodable_json, test_encodable_json}; - use rustc_serialize::json; - - #[test] - fn test_encodable() { - test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - test_decodable_json(json::decode); - } - } -} - #[cfg(feature = "serde")] mod serde { use super::NaiveDate; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 1861466bf5..e74f0d82f9 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -24,8 +24,6 @@ use crate::{ expect, try_opt, DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, Weekday, }; -#[cfg(feature = "rustc-serialize")] -pub(super) mod rustc_serialize; /// Tools to help serializing/deserializing `NaiveDateTime`s #[cfg(feature = "serde")] @@ -2152,7 +2150,7 @@ impl Default for NaiveDateTime { } } -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_encodable_json(to_string: F) where F: Fn(&NaiveDateTime) -> Result, @@ -2194,7 +2192,7 @@ where ); } -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_decodable_json(from_str: F) where F: Fn(&str) -> Result, @@ -2262,21 +2260,3 @@ where assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); assert!(from_str(r#"null"#).is_err()); } - -#[cfg(all(test, feature = "rustc-serialize"))] -fn test_decodable_json_timestamp(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - *from_str("0").unwrap(), - NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(), - "should parse integers as timestamps" - ); - assert_eq!( - *from_str("-1").unwrap(), - NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(), - "should parse integers as timestamps" - ); -} diff --git a/src/naive/datetime/rustc_serialize.rs b/src/naive/datetime/rustc_serialize.rs deleted file mode 100644 index d2b8a87114..0000000000 --- a/src/naive/datetime/rustc_serialize.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::NaiveDateTime; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; -use std::ops::Deref; - -impl Encodable for NaiveDateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } -} - -impl Decodable for NaiveDateTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) - } -} - -/// A `DateTime` that can be deserialized from a seconds-based timestamp -#[derive(Debug)] -#[deprecated( - since = "1.4.2", - note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" -)] -pub struct TsSeconds(NaiveDateTime); - -#[allow(deprecated)] -impl From for NaiveDateTime { - /// Pull the internal NaiveDateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> NaiveDateTime { - obj.0 - } -} - -#[allow(deprecated)] -impl Deref for TsSeconds { - type Target = NaiveDateTime; - - #[allow(deprecated)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[allow(deprecated)] -impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result { - Ok(TsSeconds( - NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) - .ok_or_else(|| d.error("invalid timestamp"))?, - )) - } -} - -#[cfg(test)] -mod tests { - use crate::naive::datetime::test_encodable_json; - use crate::naive::datetime::{test_decodable_json, test_decodable_json_timestamp}; - use rustc_serialize::json; - - #[test] - fn test_encodable() { - test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - test_decodable_json(json::decode); - } - - #[test] - fn test_decodable_timestamps() { - test_decodable_json_timestamp(json::decode); - } -} diff --git a/src/naive/mod.rs b/src/naive/mod.rs index df42ccae58..ca76c0a3bc 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -18,9 +18,6 @@ pub(crate) mod time; pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; #[allow(deprecated)] pub use self::date::{MAX_DATE, MIN_DATE}; -#[cfg(feature = "rustc-serialize")] -#[allow(deprecated)] -pub use self::datetime::rustc_serialize::TsSeconds; #[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 cecc19192d..2d9178fdf0 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -21,9 +21,6 @@ use crate::format::{ use crate::{expect, try_opt}; use crate::{FixedOffset, TimeDelta, Timelike}; -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize; - #[cfg(feature = "serde")] mod serde; @@ -1645,7 +1642,7 @@ impl Default for NaiveTime { } } -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_encodable_json(to_string: F) where F: Fn(&NaiveTime) -> Result, @@ -1685,7 +1682,7 @@ where ); } -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] +#[cfg(all(test, feature = "serde"))] fn test_decodable_json(from_str: F) where F: Fn(&str) -> Result, diff --git a/src/naive/time/rustc_serialize.rs b/src/naive/time/rustc_serialize.rs deleted file mode 100644 index 3c5c47a18b..0000000000 --- a/src/naive/time/rustc_serialize.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::NaiveTime; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - -impl Encodable for NaiveTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } -} - -impl Decodable for NaiveTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid time")) - } -} - -#[cfg(test)] -mod tests { - use crate::naive::time::{test_decodable_json, test_encodable_json}; - use rustc_serialize::json; - - #[test] - fn test_encodable() { - test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - test_decodable_json(json::decode); - } -} diff --git a/src/weekday.rs b/src/weekday.rs index 72ce34084a..fb0f315565 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -30,7 +30,6 @@ use crate::OutOfRange; /// assert_eq!(sunday.pred(), Weekday::Sat); /// ``` #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr( any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), derive(Archive, Deserialize, Serialize), From be62a7f1162f4691c35d1a0c8d94273e9d41136c Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Thu, 28 Mar 2024 14:22:04 -0700 Subject: [PATCH 827/999] Remove rustc-serialize feature And docs and deny exceptions and... --- Cargo.toml | 1 - README.md | 1 - deny.toml | 3 --- src/lib.rs | 1 - 4 files changed, 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 495ac35c60..17a3fbe58c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ __internal_bench = [] [dependencies] num-traits = { version = "0.2", default-features = false } -rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.8", optional = true } rkyv = { version = "0.7.43", optional = true, default-features = false } diff --git a/README.md b/README.md index bfe17fcbf9..76cd26e288 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,6 @@ Optional features: * `rkyv-32`: Enable serialization/deserialization via [rkyv], using 32-bit integers for integral `*size` types. * `rkyv-64`: Enable serialization/deserialization via [rkyv], using 64-bit integers for integral `*size` types. * `rkyv-validation`: Enable rkyv validation support using `bytecheck`. -* `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). * `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. * `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. The implementation and API may change or even be removed in a patch release. Feedback welcome. diff --git a/deny.toml b/deny.toml index 186290e039..5c70069aee 100644 --- a/deny.toml +++ b/deny.toml @@ -3,9 +3,6 @@ allow-osi-fsf-free = "either" copyleft = "deny" [advisories] -ignore = [ - "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility -] unmaintained = "deny" unsound = "deny" yanked = "deny" diff --git a/src/lib.rs b/src/lib.rs index 839444c94f..18b9226e40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,6 @@ //! - `rkyv-64`: Enable serialization/deserialization via [rkyv], //! using 64-bit integers for integral `*size` types. //! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. -//! - `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated). //! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. //! - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. //! The implementation and API may change or even be removed in a patch release. Feedback welcome. From 6c4e7356750a792e31a436d3dd288aa0e2b686c7 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Fri, 29 Mar 2024 20:53:06 -0700 Subject: [PATCH 828/999] Justify not using --all-features with rkyv --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 505e2df5ed..079aa3c39e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -161,8 +161,7 @@ jobs: - uses: mwilliamson/setup-wasmtime-action@v2 with: wasmtime-version: "12.0.1" - # We can't use `--all-features` because `rustc-serialize` doesn't support - # `wasm32-wasi`. + # We can't use `--all-features` because rkyv uses the mutually-exclusive-feature pattern - run: cargo wasi test --features=serde,unstable-locales --color=always -- --color=always cross-targets: From cfe7b740abbc961c588952417c79b9ac889c62dd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 30 Mar 2024 16:18:39 +0100 Subject: [PATCH 829/999] Merge `naive::date::test_*_json` into their tests --- src/naive/date/mod.rs | 133 +++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 4e626d9f7c..75e4c30958 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2387,71 +2387,6 @@ const YEAR_DELTAS: &[u8; 401] = &[ 96, 97, 97, 97, 97, // 400+1 ]; -#[cfg(all(test, feature = "serde"))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDate) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(), - Some(r#""2014-07-24""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(), - Some(r#""0000-01-01""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), - Some(r#""-0001-12-31""#.into()) - ); - assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262143-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262142-12-31""#.into())); -} - -#[cfg(all(test, feature = "serde"))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - use std::{i32, i64}; - - assert_eq!( - from_str(r#""2016-07-08""#).ok(), - Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) - ); - assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap())); - assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8)); - assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); - assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); - assert_eq!( - from_str(r#""-0001-12-31""#).ok(), - Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) - ); - assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); - assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""20001231""#).is_err()); - assert!(from_str(r#""2000-00-00""#).is_err()); - assert!(from_str(r#""2000-02-30""#).is_err()); - assert!(from_str(r#""2001-02-29""#).is_err()); - assert!(from_str(r#""2002-002-28""#).is_err()); - assert!(from_str(r#""yyyy-mm-dd""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"20.01"#).is_err()); - assert!(from_str(&i32::MIN.to_string()).is_err()); - assert!(from_str(&i32::MAX.to_string()).is_err()); - assert!(from_str(&i64::MIN.to_string()).is_err()); - assert!(from_str(&i64::MAX.to_string()).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"ymdf":20}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - #[cfg(feature = "serde")] mod serde { use super::NaiveDate; @@ -2507,17 +2442,79 @@ mod serde { #[cfg(test)] mod tests { - use crate::naive::date::{test_decodable_json, test_encodable_json}; use crate::NaiveDate; #[test] fn test_serde_serialize() { - test_encodable_json(serde_json::to_string); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(), + Some(r#""2014-07-24""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(), + Some(r#""0000-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), + Some(r#""-0001-12-31""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN).ok(), + Some(r#""-262143-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MAX).ok(), + Some(r#""+262142-12-31""#.into()) + ); } #[test] fn test_serde_deserialize() { - test_decodable_json(|input| serde_json::from_str(input)); + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!( + from_str(r#""2016-7-8""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8)); + assert_eq!( + from_str(r#""0000-01-01""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""0-1-1""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""-0001-12-31""#).ok(), + Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) + ); + assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""20001231""#).is_err()); + assert!(from_str(r#""2000-00-00""#).is_err()); + assert!(from_str(r#""2000-02-30""#).is_err()); + assert!(from_str(r#""2001-02-29""#).is_err()); + assert!(from_str(r#""2002-002-28""#).is_err()); + assert!(from_str(r#""yyyy-mm-dd""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"20.01"#).is_err()); + let min = i32::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i32::MAX.to_string(); + assert!(from_str(&max).is_err()); + let min = i64::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i64::MAX.to_string(); + assert!(from_str(&max).is_err()); + assert!(from_str(r#"{}"#).is_err()); } #[test] From cba4baad323681747e258dba90be646db45797af Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 30 Mar 2024 16:44:54 +0100 Subject: [PATCH 830/999] Merge `naive::time::test_*_json` into their tests --- src/naive/time/mod.rs | 96 ----------------------------------------- src/naive/time/serde.rs | 80 ++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 99 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 2d9178fdf0..f32d828080 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1641,99 +1641,3 @@ impl Default for NaiveTime { NaiveTime::from_hms_opt(0, 0, 0).unwrap() } } - -#[cfg(all(test, feature = "serde"))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveTime) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(), - Some(r#""00:00:00""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(), - Some(r#""00:00:00.950""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(), - Some(r#""00:00:60""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(), - Some(r#""00:01:02""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(), - Some(r#""03:05:07.098765432""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(), - Some(r#""07:08:09""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(), - Some(r#""12:34:56.000789""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), - Some(r#""23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, feature = "serde"))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); - assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); - assert_eq!( - from_str(r#""00:00:00.950""#).ok(), - Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) - ); - assert_eq!( - from_str(r#""0:0:0.95""#).ok(), - Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) - ); - assert_eq!( - from_str(r#""00:00:60""#).ok(), - Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()) - ); - assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap())); - assert_eq!( - from_str(r#""03:05:07.098765432""#).ok(), - Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()) - ); - assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap())); - assert_eq!( - from_str(r#""12:34:56.000789""#).ok(), - Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()) - ); - assert_eq!( - from_str(r#""23:59:60.999999999""#).ok(), - Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) - ); - assert_eq!( - from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""000000""#).is_err()); - assert!(from_str(r#""00:00:61""#).is_err()); - assert!(from_str(r#""00:60:00""#).is_err()); - assert!(from_str(r#""24:00:00""#).is_err()); - assert!(from_str(r#""23:59:59,1""#).is_err()); - assert!(from_str(r#""012:34:56""#).is_err()); - assert!(from_str(r#""hh:mm:ss""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"86399"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"secs":0,"frac":0}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index 0992fb59f2..c1df410292 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -42,17 +42,91 @@ impl<'de> de::Deserialize<'de> for NaiveTime { #[cfg(test)] mod tests { - use crate::naive::time::{test_decodable_json, test_encodable_json}; use crate::NaiveTime; #[test] fn test_serde_serialize() { - test_encodable_json(serde_json::to_string); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(), + Some(r#""00:00:00.950""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(), + Some(r#""00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(), + Some(r#""00:01:02""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(), + Some(r#""03:05:07.098765432""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(), + Some(r#""07:08:09""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(), + Some(r#""12:34:56.000789""#.into()) + ); + let leap = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap(); + assert_eq!(serde_json::to_string(&leap).ok(), Some(r#""23:59:60.999999999""#.into())); } #[test] fn test_serde_deserialize() { - test_decodable_json(|input| serde_json::from_str(input)); + let from_str = serde_json::from_str::; + + assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!( + from_str(r#""00:00:00.950""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""0:0:0.95""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""00:00:60""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()) + ); + assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap())); + assert_eq!( + from_str(r#""03:05:07.098765432""#).ok(), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()) + ); + assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap())); + assert_eq!( + from_str(r#""12:34:56.000789""#).ok(), + Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.999999999""#).ok(), + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""000000""#).is_err()); + assert!(from_str(r#""00:00:61""#).is_err()); + assert!(from_str(r#""00:60:00""#).is_err()); + assert!(from_str(r#""24:00:00""#).is_err()); + assert!(from_str(r#""23:59:59,1""#).is_err()); + assert!(from_str(r#""012:34:56""#).is_err()); + assert!(from_str(r#""hh:mm:ss""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"86399"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); } #[test] From e9849ed6020a79ed1e4af4e949837564409d3f2a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 30 Mar 2024 17:04:53 +0100 Subject: [PATCH 831/999] Merge `naive::datetime::test_*_json` into their tests --- src/naive/datetime/mod.rs | 111 ----------------------------- src/naive/datetime/serde.rs | 135 +++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 114 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e74f0d82f9..a2ffc69674 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -2149,114 +2149,3 @@ impl Default for NaiveDateTime { Self::UNIX_EPOCH } } - -#[cfg(all(test, feature = "serde"))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDateTime) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - to_string( - &NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() - ) - .ok(), - Some(r#""2016-07-08T09:10:48.090""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) - .ok(), - Some(r#""2014-07-24T12:34:06""#.into()) - ); - assert_eq!( - to_string( - &NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap() - ) - .ok(), - Some(r#""0000-01-01T00:00:60""#.into()) - ); - assert_eq!( - to_string( - &NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap() - ) - .ok(), - Some(r#""-0001-12-31T23:59:59.000000007""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), - Some(r#""-262143-01-01T00:00:00""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), - Some(r#""+262142-12-31T23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, feature = "serde"))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - from_str(r#""2016-07-08T09:10:48.090""#).ok(), - Some( - NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() - ) - ); - assert_eq!( - from_str(r#""2016-7-8T9:10:48.09""#).ok(), - Some( - NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap() - ) - ); - assert_eq!( - from_str(r#""2014-07-24T12:34:06""#).ok(), - Some(NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) - ); - assert_eq!( - from_str(r#""0000-01-01T00:00:60""#).ok(), - Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap()) - ); - assert_eq!( - from_str(r#""0-1-1T0:0:60""#).ok(), - Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().and_hms_milli_opt(0, 0, 59, 1_000).unwrap()) - ); - assert_eq!( - from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), - Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap()) - ); - assert_eq!( - from_str(r#""-262143-01-01T00:00:00""#).ok(), - Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) - ); - assert_eq!( - from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), - Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) - ); - assert_eq!( - from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""2016-07-08""#).is_err()); - assert!(from_str(r#""09:10:48.090""#).is_err()); - assert!(from_str(r#""20160708T091048.090""#).is_err()); - assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); - assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); - assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); - assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); - assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); - assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); - assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); - assert!(from_str(r#"20160708000000"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 2beda08745..ae06578b5c 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -1128,7 +1128,6 @@ pub mod ts_seconds_option { #[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}; @@ -1137,12 +1136,142 @@ mod tests { #[test] fn test_serde_serialize() { - test_encodable_json(serde_json::to_string); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + .ok(), + Some(r#""2016-07-08T09:10:48.090""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + .ok(), + Some(r#""0000-01-01T00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + .ok(), + Some(r#""-0001-12-31T23:59:59.000000007""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""-262143-01-01T00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap() + ) + .ok(), + Some(r#""+262142-12-31T23:59:60.999999999""#.into()) + ); } #[test] fn test_serde_deserialize() { - test_decodable_json(|input| serde_json::from_str(input)); + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08T09:10:48.090""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2016-7-8T9:10:48.09""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2014-07-24T12:34:06""#).ok(), + Some(NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) + ); + assert_eq!( + from_str(r#""0000-01-01T00:00:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""0-1-1T0:0:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), + Some( + NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-262143-01-01T00:00:00""#).ok(), + Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""2016-07-08""#).is_err()); + assert!(from_str(r#""09:10:48.090""#).is_err()); + assert!(from_str(r#""20160708T091048.090""#).is_err()); + assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); + assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); + assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); + assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); + assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); + assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); + assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); + assert!(from_str(r#"20160708000000"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); + // pre-0.3.0 rustc-serialize format is now invalid + assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); + assert!(from_str(r#"null"#).is_err()); } // Bincode is relevant to test separately from JSON because From ffe27452c66a6294046b39fd97d05fb278f807e2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 30 Mar 2024 15:42:34 +0100 Subject: [PATCH 832/999] Merge `datetime::test_*_json` into their tests --- src/datetime/mod.rs | 86 ------------------------------------------- src/datetime/serde.rs | 79 +++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 94 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5e693ea5c7..366bbe6ad7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1933,89 +1933,3 @@ where /// -------- /// 719163 const UNIX_EPOCH_DAY: i64 = 719_163; - -#[cfg(all(test, feature = "serde"))] -fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) -where - FUtc: Fn(&DateTime) -> Result, - FFixed: Fn(&DateTime) -> Result, - E: ::core::fmt::Debug, -{ - assert_eq!( - to_string_utc(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), - Some(r#""2014-07-24T12:34:06Z""#.into()) - ); - - assert_eq!( - to_string_fixed( - &FixedOffset::east_opt(3660).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - ) - .ok(), - Some(r#""2014-07-24T12:34:06+01:01""#.into()) - ); - assert_eq!( - to_string_fixed( - &FixedOffset::east_opt(3650).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - ) - .ok(), - // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. - // In this case `+01:00:50` becomes `+01:01` - Some(r#""2014-07-24T12:34:06+01:01""#.into()) - ); -} - -#[cfg(all(test, feature = "clock", feature = "serde"))] -fn test_decodable_json( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - // should check against the offset as well (the normal DateTime comparison will ignore them) - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap())) - ); - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap())) - ); - - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some( - FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - )) - ); - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some( - FixedOffset::east_opt(60 * 60 + 23 * 60) - .unwrap() - .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) - .unwrap() - )) - ); - - // we don't know the exact local offset but we can check that - // the conversion didn't change the instant itself - assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"), - Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - ); - assert_eq!( - local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - ); - - assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); -} diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 724c1ae717..6f048a4568 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1209,24 +1209,87 @@ pub mod ts_seconds_option { #[cfg(test)] mod tests { #[cfg(feature = "clock")] - use crate::datetime::test_decodable_json; - use crate::datetime::test_encodable_json; + use crate::Local; use crate::{DateTime, FixedOffset, TimeZone, Utc}; use core::fmt; #[test] fn test_serde_serialize() { - test_encodable_json(serde_json::to_string, serde_json::to_string); + assert_eq!( + serde_json::to_string(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), + Some(r#""2014-07-24T12:34:06Z""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3660) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3650) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. + // In this case `+01:00:50` becomes `+01:01` + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); } - #[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), + // should check against the offset as well (the normal DateTime comparison will ignore them) + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + let dt: Option> = serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + let dt: Option> = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + + let dt: Option> = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() + )) + ); + let dt: Option> = + serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(60 * 60 + 23 * 60) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) + .unwrap() + )) ); + + // we don't know the exact local offset but we can check that + // the conversion didn't change the instant itself + #[cfg(feature = "clock")] + { + let dt: DateTime = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + + let dt: DateTime = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#) + .expect("local should parse with offset"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + } + + assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); } #[test] From a36a45ad36f43975e57540870cee712544d53076 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 13:26:30 +0200 Subject: [PATCH 833/999] Factor out `Numeric` formatting --- src/format/formatting.rs | 131 +++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 80bfc2d010..a2e9d99f4f 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -199,67 +199,7 @@ fn format_inner( Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).and_utc().timestamp()), - (Some(d), Some(t), Some(&(_, off))) => Some( - d.and_time(*t).and_utc().timestamp() - i64::from(off.local_minus_utc()), - ), - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), - } - } - } else { - Err(fmt::Error) // insufficient arguments for given format - } - } + Item::Numeric(ref spec, ref pad) => format_numeric(w, date, time, off, spec, pad), Item::Fixed(ref spec) => { use self::Fixed::*; @@ -420,6 +360,75 @@ fn format_inner( } } +#[cfg(feature = "alloc")] +fn format_numeric( + w: &mut impl Write, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + spec: &Numeric, + pad: &Pad, +) -> fmt::Result { + use self::Numeric::*; + + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); + + let (width, v) = match *spec { + Year => (4, date.map(|d| i64::from(d.year()))), + YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), + IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), + IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), + Month => (2, date.map(|d| i64::from(d.month()))), + Day => (2, date.map(|d| i64::from(d.day()))), + WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), + WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), + IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), + NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), + WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), + Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), + Hour => (2, time.map(|t| i64::from(t.hour()))), + Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), + Minute => (2, time.map(|t| i64::from(t.minute()))), + Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), + Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), + Timestamp => ( + 1, + match (date, time, off) { + (Some(d), Some(t), None) => Some(d.and_time(*t).and_utc().timestamp()), + (Some(d), Some(t), Some(&(_, off))) => { + Some(d.and_time(*t).and_utc().timestamp() - i64::from(off.local_minus_utc())) + } + (_, _, _) => None, + }, + ), + + // for the future expansion + Internal(ref int) => match int._dummy {}, + }; + + if let Some(v) = v { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match *pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), + } + } else { + match *pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), + } + } + } else { + Err(fmt::Error) // insufficient arguments for given format + } +} + #[cfg(any(feature = "alloc", feature = "serde"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. From 080d623dd36ac66735e702702b776c34b6370553 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 13:33:20 +0200 Subject: [PATCH 834/999] Factor out `Fixed` formatting --- src/format/formatting.rs | 314 +++++++++++++++++++-------------------- 1 file changed, 156 insertions(+), 158 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index a2e9d99f4f..171793888b 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -193,169 +193,12 @@ fn format_inner( item: &Item<'_>, locale: Option, ) -> fmt::Result { - let locale = locale.unwrap_or(default_locale()); - match *item { Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, ref pad) => format_numeric(w, date, time, off, spec, pad), - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - let ret = match *spec { - ShortMonthName => date.map(|d| { - w.write_str(short_months(locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => date.map(|d| { - w.write_str(long_months(locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - w.write_str( - short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LongWeekdayName => date.map(|d| { - w.write_str( - long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; - for c in ampm.chars().flat_map(|c| c.to_lowercase()) { - w.write_char(c)? - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else { - w.write_str(decimal_point(locale))?; - if nano % 1_000_000 == 0 { - write!(w, "{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(w, "{:06}", nano / 1_000) - } else { - write!(w, "{:09}", nano) - } - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) - }) - } - TimezoneName => off.map(|(name, _)| { - w.write_str(name)?; - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339( - w, - crate::NaiveDateTime::new(*d, *t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None - } - } - }; - - ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format - } - + Item::Fixed(ref spec) => format_fixed(w, date, time, off, spec, locale), Item::Error => Err(fmt::Error), } } @@ -429,6 +272,161 @@ fn format_numeric( } } +#[cfg(feature = "alloc")] +fn format_fixed( + w: &mut impl Write, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + spec: &Fixed, + locale: Option, +) -> fmt::Result { + use self::Fixed::*; + + let locale = locale.unwrap_or(default_locale()); + + let ret = match *spec { + ShortMonthName => date.map(|d| { + w.write_str(short_months(locale)[d.month0() as usize])?; + Ok(()) + }), + LongMonthName => date.map(|d| { + w.write_str(long_months(locale)[d.month0() as usize])?; + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + Ok(()) + }), + LongWeekdayName => date.map(|d| { + w.write_str(long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? + } + Ok(()) + }), + UpperAmPm => time.map(|t| { + w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else { + w.write_str(decimal_point(locale))?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{:09}", nano) + } + } + }), + Nanosecond3 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:03}", nano / 1_000_000) + }), + Nanosecond6 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:06}", nano / 1_000) + }), + Nanosecond9 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", nano / 1_000_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", nano / 1_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:09}", nano) + }), + TimezoneName => off.map(|(name, _)| { + w.write_str(name)?; + Ok(()) + }), + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(*d, *t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) + } else { + None + } + } + }; + + ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format +} + #[cfg(any(feature = "alloc", feature = "serde"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. From 5c1c87c61dcab20c1e15c51ad7dbbcb2167e2b0a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 13:39:17 +0200 Subject: [PATCH 835/999] Pass date and time by value --- src/format/formatting.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 171793888b..845bea1f4b 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -126,8 +126,8 @@ impl<'a, I: Iterator + Clone, B: Borrow>> Display for Delayed for item in self.items.clone() { format_inner( &mut result, - self.date.as_ref(), - self.time.as_ref(), + self.date, + self.time, self.off.as_ref(), item.borrow(), locale, @@ -187,8 +187,8 @@ pub fn format_item( #[cfg(feature = "alloc")] fn format_inner( w: &mut impl Write, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, + date: Option, + time: Option, off: Option<&(String, FixedOffset)>, item: &Item<'_>, locale: Option, @@ -206,16 +206,16 @@ fn format_inner( #[cfg(feature = "alloc")] fn format_numeric( w: &mut impl Write, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, + date: Option, + time: Option, off: Option<&(String, FixedOffset)>, spec: &Numeric, pad: &Pad, ) -> fmt::Result { use self::Numeric::*; - let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); + let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), @@ -240,9 +240,9 @@ fn format_numeric( Timestamp => ( 1, match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).and_utc().timestamp()), + (Some(d), Some(t), None) => Some(d.and_time(t).and_utc().timestamp()), (Some(d), Some(t), Some(&(_, off))) => { - Some(d.and_time(*t).and_utc().timestamp() - i64::from(off.local_minus_utc())) + Some(d.and_time(t).and_utc().timestamp() - i64::from(off.local_minus_utc())) } (_, _, _) => None, }, @@ -275,8 +275,8 @@ fn format_numeric( #[cfg(feature = "alloc")] fn format_fixed( w: &mut impl Write, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, + date: Option, + time: Option, off: Option<&(String, FixedOffset)>, spec: &Fixed, locale: Option, @@ -402,7 +402,7 @@ fn format_fixed( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off)) + Some(write_rfc2822(w, crate::NaiveDateTime::new(d, t), off)) } else { None } @@ -413,7 +413,7 @@ fn format_fixed( if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { Some(write_rfc3339( w, - crate::NaiveDateTime::new(*d, *t), + crate::NaiveDateTime::new(d, t), off.fix(), SecondsFormat::AutoSi, false, From 4947c90046060e7c0ec6c02c86d483071f992d27 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 13:56:42 +0200 Subject: [PATCH 836/999] Factor out `DelayedFormat::format` --- src/format/formatting.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 845bea1f4b..1010e99a98 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -112,20 +112,16 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } } -} -#[cfg(feature = "alloc")] -impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn format(&self, w: &mut impl Write) -> fmt::Result { #[cfg(feature = "unstable-locales")] let locale = self.locale; #[cfg(not(feature = "unstable-locales"))] let locale = None; - let mut result = String::new(); for item in self.items.clone() { format_inner( - &mut result, + w, self.date, self.time, self.off.as_ref(), @@ -133,6 +129,15 @@ impl<'a, I: Iterator + Clone, B: Borrow>> Display for Delayed locale, )?; } + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut result = String::new(); + self.format(&mut result)?; f.pad(&result) } } From cc0139996f8384eb942e50d3fb60172789a5a187 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 13:57:37 +0200 Subject: [PATCH 837/999] Inline `format_inner` --- src/format/formatting.rs | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 1010e99a98..5dbc60ef23 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -120,14 +120,18 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { let locale = None; for item in self.items.clone() { - format_inner( - w, - self.date, - self.time, - self.off.as_ref(), - item.borrow(), - locale, - )?; + match *item.borrow() { + Item::Literal(s) | Item::Space(s) => w.write_str(s), + #[cfg(feature = "alloc")] + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), + Item::Numeric(ref spec, ref pad) => { + format_numeric(w, self.date, self.time, self.off.as_ref(), spec, pad) + } + Item::Fixed(ref spec) => { + format_fixed(w, self.date, self.time, self.off.as_ref(), spec, locale) + } + Item::Error => Err(fmt::Error), + }?; } Ok(()) } @@ -189,25 +193,6 @@ pub fn format_item( .fmt(w) } -#[cfg(feature = "alloc")] -fn format_inner( - w: &mut impl Write, - date: Option, - time: Option, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Option, -) -> fmt::Result { - match *item { - Item::Literal(s) | Item::Space(s) => w.write_str(s), - #[cfg(feature = "alloc")] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), - Item::Numeric(ref spec, ref pad) => format_numeric(w, date, time, off, spec, pad), - Item::Fixed(ref spec) => format_fixed(w, date, time, off, spec, locale), - Item::Error => Err(fmt::Error), - } -} - #[cfg(feature = "alloc")] fn format_numeric( w: &mut impl Write, From 06a6b01f3bc027ca94a6927817970a749dfc032b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:00:50 +0200 Subject: [PATCH 838/999] Make `format_numeric` a method --- src/format/formatting.rs | 137 +++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 72 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 5dbc60ef23..88d446f6dd 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -124,9 +124,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), - Item::Numeric(ref spec, ref pad) => { - format_numeric(w, self.date, self.time, self.off.as_ref(), spec, pad) - } + Item::Numeric(ref spec, ref pad) => self.format_numeric(w, spec, pad), Item::Fixed(ref spec) => { format_fixed(w, self.date, self.time, self.off.as_ref(), spec, locale) } @@ -135,6 +133,70 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } Ok(()) } + + #[cfg(feature = "alloc")] + fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: &Pad) -> fmt::Result { + use self::Numeric::*; + + let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); + + let (width, v) = match *spec { + Year => (4, self.date.map(|d| i64::from(d.year()))), + YearDiv100 => (2, self.date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, self.date.map(|d| i64::from(d.year()).rem_euclid(100))), + IsoYear => (4, self.date.map(|d| i64::from(d.iso_week().year()))), + IsoYearDiv100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), + Month => (2, self.date.map(|d| i64::from(d.month()))), + Day => (2, self.date.map(|d| i64::from(d.day()))), + WeekFromSun => (2, self.date.map(|d| i64::from(week_from_sun(d)))), + WeekFromMon => (2, self.date.map(|d| i64::from(week_from_mon(d)))), + IsoWeek => (2, self.date.map(|d| i64::from(d.iso_week().week()))), + NumDaysFromSun => (1, self.date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), + WeekdayFromMon => (1, self.date.map(|d| i64::from(d.weekday().number_from_monday()))), + Ordinal => (3, self.date.map(|d| i64::from(d.ordinal()))), + Hour => (2, self.time.map(|t| i64::from(t.hour()))), + Hour12 => (2, self.time.map(|t| i64::from(t.hour12().1))), + Minute => (2, self.time.map(|t| i64::from(t.minute()))), + Second => { + (2, self.time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))) + } + Nanosecond => (9, self.time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), + Timestamp => ( + 1, + match (self.date, self.time, self.off.as_ref()) { + (Some(d), Some(t), None) => Some(d.and_time(t).and_utc().timestamp()), + (Some(d), Some(t), Some(&(_, off))) => { + Some(d.and_time(t).and_utc().timestamp() - i64::from(off.local_minus_utc())) + } + (_, _, _) => None, + }, + ), + + // for the future expansion + Internal(ref int) => match int._dummy {}, + }; + + if let Some(v) = v { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match *pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), + } + } else { + match *pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), + } + } + } else { + Err(fmt::Error) // insufficient arguments for given format + } + } } #[cfg(feature = "alloc")] @@ -193,75 +255,6 @@ pub fn format_item( .fmt(w) } -#[cfg(feature = "alloc")] -fn format_numeric( - w: &mut impl Write, - date: Option, - time: Option, - off: Option<&(String, FixedOffset)>, - spec: &Numeric, - pad: &Pad, -) -> fmt::Result { - use self::Numeric::*; - - let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(t).and_utc().timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some(d.and_time(t).and_utc().timestamp() - i64::from(off.local_minus_utc())) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), - } - } - } else { - Err(fmt::Error) // insufficient arguments for given format - } -} - #[cfg(feature = "alloc")] fn format_fixed( w: &mut impl Write, From fe4339628306103660e2d668cc1d6bb4d9f54f69 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:05:07 +0200 Subject: [PATCH 839/999] Make `format_fixed` a method --- src/format/formatting.rs | 322 ++++++++++++++++++++------------------- 1 file changed, 164 insertions(+), 158 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 88d446f6dd..f1c40c300b 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -125,9 +125,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, ref pad) => self.format_numeric(w, spec, pad), - Item::Fixed(ref spec) => { - format_fixed(w, self.date, self.time, self.off.as_ref(), spec, locale) - } + Item::Fixed(ref spec) => self.format_fixed(w, spec, locale), Item::Error => Err(fmt::Error), }?; } @@ -197,6 +195,169 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Err(fmt::Error) // insufficient arguments for given format } } + + #[cfg(feature = "alloc")] + fn format_fixed( + &self, + w: &mut impl Write, + spec: &Fixed, + locale: Option, + ) -> fmt::Result { + use self::Fixed::*; + + let locale = locale.unwrap_or(default_locale()); + + let ret = match *spec { + ShortMonthName => self.date.map(|d| { + w.write_str(short_months(locale)[d.month0() as usize])?; + Ok(()) + }), + LongMonthName => self.date.map(|d| { + w.write_str(long_months(locale)[d.month0() as usize])?; + Ok(()) + }), + ShortWeekdayName => self.date.map(|d| { + w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + Ok(()) + }), + LongWeekdayName => self.date.map(|d| { + w.write_str(long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + Ok(()) + }), + LowerAmPm => self.time.map(|t| { + let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? + } + Ok(()) + }), + UpperAmPm => self.time.map(|t| { + w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; + Ok(()) + }), + Nanosecond => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else { + w.write_str(decimal_point(locale))?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{:09}", nano) + } + } + }), + Nanosecond3 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:03}", nano / 1_000_000) + }), + Nanosecond6 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:06}", nano / 1_000) + }), + Nanosecond9 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale))?; + write!(w, "{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", nano / 1_000_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", nano / 1_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:09}", nano) + }) + } + TimezoneName => self.off.as_ref().map(|(name, _)| { + w.write_str(name)?; + Ok(()) + }), + TimezoneOffset | TimezoneOffsetZ => self.off.as_ref().map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => self.off.as_ref().map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetDoubleColon => self.off.as_ref().map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetTripleColon => self.off.as_ref().map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = + (self.date, self.time, self.off.as_ref()) + { + Some(write_rfc2822(w, crate::NaiveDateTime::new(d, t), off)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = + (self.date, self.time, self.off.as_ref()) + { + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) + } else { + None + } + } + }; + + ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format + } } #[cfg(feature = "alloc")] @@ -255,161 +416,6 @@ pub fn format_item( .fmt(w) } -#[cfg(feature = "alloc")] -fn format_fixed( - w: &mut impl Write, - date: Option, - time: Option, - off: Option<&(String, FixedOffset)>, - spec: &Fixed, - locale: Option, -) -> fmt::Result { - use self::Fixed::*; - - let locale = locale.unwrap_or(default_locale()); - - let ret = match *spec { - ShortMonthName => date.map(|d| { - w.write_str(short_months(locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => date.map(|d| { - w.write_str(long_months(locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; - Ok(()) - }), - LongWeekdayName => date.map(|d| { - w.write_str(long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; - for c in ampm.chars().flat_map(|c| c.to_lowercase()) { - w.write_char(c)? - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else { - w.write_str(decimal_point(locale))?; - if nano % 1_000_000 == 0 { - write!(w, "{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(w, "{:06}", nano / 1_000) - } else { - write!(w, "{:09}", nano) - } - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) - }), - TimezoneName => off.map(|(name, _)| { - w.write_str(name)?; - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822(w, crate::NaiveDateTime::new(d, t), off)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339( - w, - crate::NaiveDateTime::new(d, t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None - } - } - }; - - ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format -} - #[cfg(any(feature = "alloc", feature = "serde"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. From 0b9b160d1ed4952d3842802476ee513abf595ba9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 Apr 2024 07:33:59 +0200 Subject: [PATCH 840/999] CI: Skip oldtime, rkyv and wasmbind in `cargo hack check` --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 079aa3c39e..99e10b03d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,7 +80,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps arbitrary,serde \ - --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi,rkyv-16,rkyv-64,rkyv-validation \ + --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,oldtime,winapi,rkyv-16,rkyv-64,rkyv-validation,wasmbind \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks From 8b1364f6803259af87fc10a05aad7b90dd1cf247 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 Apr 2024 07:52:01 +0200 Subject: [PATCH 841/999] CI: Treat some features as mutually exclusive in `cargo hack check` --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99e10b03d6..d866cbf7e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,7 +80,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps arbitrary,serde \ - --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,oldtime,winapi,rkyv-16,rkyv-64,rkyv-validation,wasmbind \ + --skip __internal_bench,iana-time-zone,oldtime,pure-rust-locales,libc,winapi,rkyv-validation,wasmbind \ + --mutually-exclusive-features arbitrary,rkyv,rkyv-16,rkyv-32,rkyv-64,serde \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks From 838b9dd62ad6ae1084506ae21d2563224fe61c9c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 Apr 2024 10:12:03 +0200 Subject: [PATCH 842/999] Only package files needed for building and testing --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 17a3fbe58c..e612c7b415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["date", "time", "calendar"] categories = ["date-and-time"] readme = "README.md" license = "MIT OR Apache-2.0" -exclude = ["/ci/*"] +include = ["src/*", "tests/*.rs", "LICENSE.txt", "CITATION.cff"] edition = "2021" rust-version = "1.61.0" From 7f57dde63ce66218f624af7f561622e7906c3d06 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 Apr 2024 10:17:13 +0200 Subject: [PATCH 843/999] Remove `appveyor.yml` --- appveyor.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6057e851e2..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,12 +0,0 @@ -# TODO: delete this without breaking all PRs -environment: - matrix: - - TARGET: nightly-i686-pc-windows-gnu -matrix: - allow_failures: - - channel: nightly - -build: false - -test_script: - - echo "stub" From 63021588501afd39e5ca7a76df445af5315e56f2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:29:16 +0200 Subject: [PATCH 844/999] Always have a `locale` field --- src/format/formatting.rs | 78 +++++++++++++++------------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f1c40c300b..efa1004958 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -41,10 +41,8 @@ pub struct DelayedFormat { /// An iterator returning formatting items. items: I, /// Locale used for text. - // TODO: Only used with the locale feature. We should make this property - // only present when the feature is enabled. - #[cfg(feature = "unstable-locales")] - locale: Option, + /// ZST if the `unstable-locales` feature is not enabled. + locale: Locale, } #[cfg(feature = "alloc")] @@ -52,14 +50,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { - date, - time, - off: None, - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } + DelayedFormat { date, time, off: None, items, locale: default_locale() } } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. @@ -74,14 +65,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Off: Offset + Display, { let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date, - time, - off: Some(name_and_diff), - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: default_locale() } } /// Makes a new `DelayedFormat` value out of local date and time and locale. @@ -93,7 +77,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { items: I, locale: Locale, ) -> DelayedFormat { - DelayedFormat { date, time, off: None, items, locale: Some(locale) } + DelayedFormat { date, time, off: None, items, locale } } /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. @@ -110,22 +94,17 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Off: Offset + Display, { let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } + DelayedFormat { date, time, off: Some(name_and_diff), items, locale } } fn format(&self, w: &mut impl Write) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let locale = self.locale; - #[cfg(not(feature = "unstable-locales"))] - let locale = None; - for item in self.items.clone() { match *item.borrow() { Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, ref pad) => self.format_numeric(w, spec, pad), - Item::Fixed(ref spec) => self.format_fixed(w, spec, locale), + Item::Fixed(ref spec) => self.format_fixed(w, spec), Item::Error => Err(fmt::Error), }?; } @@ -197,42 +176,43 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[cfg(feature = "alloc")] - fn format_fixed( - &self, - w: &mut impl Write, - spec: &Fixed, - locale: Option, - ) -> fmt::Result { + fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { use self::Fixed::*; - let locale = locale.unwrap_or(default_locale()); - let ret = match *spec { ShortMonthName => self.date.map(|d| { - w.write_str(short_months(locale)[d.month0() as usize])?; + w.write_str(short_months(self.locale)[d.month0() as usize])?; Ok(()) }), LongMonthName => self.date.map(|d| { - w.write_str(long_months(locale)[d.month0() as usize])?; + w.write_str(long_months(self.locale)[d.month0() as usize])?; Ok(()) }), ShortWeekdayName => self.date.map(|d| { - w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + )?; Ok(()) }), LongWeekdayName => self.date.map(|d| { - w.write_str(long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + w.write_str( + long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + )?; Ok(()) }), LowerAmPm => self.time.map(|t| { - let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; for c in ampm.chars().flat_map(|c| c.to_lowercase()) { w.write_char(c)? } Ok(()) }), UpperAmPm => self.time.map(|t| { - w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; + w.write_str(if t.hour12().0 { + am_pm(self.locale)[1] + } else { + am_pm(self.locale)[0] + })?; Ok(()) }), Nanosecond => self.time.map(|t| { @@ -240,7 +220,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { if nano == 0 { Ok(()) } else { - w.write_str(decimal_point(locale))?; + w.write_str(decimal_point(self.locale))?; if nano % 1_000_000 == 0 { write!(w, "{:03}", nano / 1_000_000) } else if nano % 1_000 == 0 { @@ -252,17 +232,17 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { }), Nanosecond3 => self.time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; + w.write_str(decimal_point(self.locale))?; write!(w, "{:03}", nano / 1_000_000) }), Nanosecond6 => self.time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; + w.write_str(decimal_point(self.locale))?; write!(w, "{:06}", nano / 1_000) }), Nanosecond9 => self.time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; + w.write_str(decimal_point(self.locale))?; write!(w, "{:09}", nano) }), Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { @@ -389,8 +369,7 @@ where time: time.copied(), off: off.cloned(), items, - #[cfg(feature = "unstable-locales")] - locale: None, + locale: default_locale(), } .fmt(w) } @@ -410,8 +389,7 @@ pub fn format_item( time: time.copied(), off: off.cloned(), items: [item].into_iter(), - #[cfg(feature = "unstable-locales")] - locale: None, + locale: default_locale(), } .fmt(w) } From 8df4ad295f021b4dfee1d0c2f478887f0f9d688b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:33:39 +0200 Subject: [PATCH 845/999] Inline `weeks_from_*` --- src/format/formatting.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index efa1004958..97fa868b77 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -115,9 +115,6 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: &Pad) -> fmt::Result { use self::Numeric::*; - let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); - let (width, v) = match *spec { Year => (4, self.date.map(|d| i64::from(d.year()))), YearDiv100 => (2, self.date.map(|d| i64::from(d.year()).div_euclid(100))), @@ -127,8 +124,8 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { IsoYearMod100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), Month => (2, self.date.map(|d| i64::from(d.month()))), Day => (2, self.date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, self.date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, self.date.map(|d| i64::from(week_from_mon(d)))), + WeekFromSun => (2, self.date.map(|d| i64::from(d.weeks_from(Weekday::Sun)))), + WeekFromMon => (2, self.date.map(|d| i64::from(d.weeks_from(Weekday::Mon)))), IsoWeek => (2, self.date.map(|d| i64::from(d.iso_week().week()))), NumDaysFromSun => (1, self.date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), WeekdayFromMon => (1, self.date.map(|d| i64::from(d.weekday().number_from_monday()))), From f3d76c7bb0902e269b902cf1bc9d986501675c52 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:36:35 +0200 Subject: [PATCH 846/999] Match on tuples in `format_numeric` --- src/format/formatting.rs | 86 +++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 97fa868b77..f72220d4f2 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -115,60 +115,48 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: &Pad) -> fmt::Result { use self::Numeric::*; - let (width, v) = match *spec { - Year => (4, self.date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, self.date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, self.date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, self.date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, self.date.map(|d| i64::from(d.month()))), - Day => (2, self.date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, self.date.map(|d| i64::from(d.weeks_from(Weekday::Sun)))), - WeekFromMon => (2, self.date.map(|d| i64::from(d.weeks_from(Weekday::Mon)))), - IsoWeek => (2, self.date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, self.date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, self.date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, self.date.map(|d| i64::from(d.ordinal()))), - Hour => (2, self.time.map(|t| i64::from(t.hour()))), - Hour12 => (2, self.time.map(|t| i64::from(t.hour12().1))), - Minute => (2, self.time.map(|t| i64::from(t.minute()))), - Second => { - (2, self.time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))) + let (width, v) = match (spec, self.date, self.time) { + (Year, Some(d), _) => (4, i64::from(d.year())), + (YearDiv100, Some(d), _) => (2, i64::from(d.year()).div_euclid(100)), + (YearMod100, Some(d), _) => (2, i64::from(d.year()).rem_euclid(100)), + (IsoYear, Some(d), _) => (4, i64::from(d.iso_week().year())), + (IsoYearDiv100, Some(d), _) => (2, i64::from(d.iso_week().year()).div_euclid(100)), + (IsoYearMod100, Some(d), _) => (2, i64::from(d.iso_week().year()).rem_euclid(100)), + (Month, Some(d), _) => (2, i64::from(d.month())), + (Day, Some(d), _) => (2, i64::from(d.day())), + (WeekFromSun, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Sun))), + (WeekFromMon, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Mon))), + (IsoWeek, Some(d), _) => (2, i64::from(d.iso_week().week())), + (NumDaysFromSun, Some(d), _) => (1, i64::from(d.weekday().num_days_from_sunday())), + (WeekdayFromMon, Some(d), _) => (1, i64::from(d.weekday().number_from_monday())), + (Ordinal, Some(d), _) => (3, i64::from(d.ordinal())), + (Hour, _, Some(t)) => (2, i64::from(t.hour())), + (Hour12, _, Some(t)) => (2, i64::from(t.hour12().1)), + (Minute, _, Some(t)) => (2, i64::from(t.minute())), + (Second, _, Some(t)) => (2, i64::from(t.second() + t.nanosecond() / 1_000_000_000)), + (Nanosecond, _, Some(t)) => (9, i64::from(t.nanosecond() % 1_000_000_000)), + (Timestamp, Some(d), Some(t)) => { + let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc())); + let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0); + (1, timestamp) } - Nanosecond => (9, self.time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (self.date, self.time, self.off.as_ref()) { - (Some(d), Some(t), None) => Some(d.and_time(t).and_utc().timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some(d.and_time(t).and_utc().timestamp() - i64::from(off.local_minus_utc())) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, + (Internal(_), _, _) => return Ok(()), // for future expansion + _ => return Err(fmt::Error), // insufficient arguments for given format }; - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), - } + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), } } else { - Err(fmt::Error) // insufficient arguments for given format + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), + } } } From 78e79dbabf30ba6fcf59ff6cc439c2dd944b0da0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 14:41:39 +0200 Subject: [PATCH 847/999] Match on tuples in `format_fixed` --- src/format/formatting.rs | 186 +++++++++++++++------------------------ 1 file changed, 69 insertions(+), 117 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f72220d4f2..6c3fc69692 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -162,45 +162,34 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { #[cfg(feature = "alloc")] fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { - use self::Fixed::*; + use Fixed::*; + use InternalInternal::*; - let ret = match *spec { - ShortMonthName => self.date.map(|d| { - w.write_str(short_months(self.locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => self.date.map(|d| { - w.write_str(long_months(self.locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => self.date.map(|d| { - w.write_str( - short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LongWeekdayName => self.date.map(|d| { - w.write_str( - long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LowerAmPm => self.time.map(|t| { + match (spec, self.date, self.time, self.off.as_ref()) { + (ShortMonthName, Some(d), _, _) => { + w.write_str(short_months(self.locale)[d.month0() as usize]) + } + (LongMonthName, Some(d), _, _) => { + w.write_str(long_months(self.locale)[d.month0() as usize]) + } + (ShortWeekdayName, Some(d), _, _) => w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + ), + (LongWeekdayName, Some(d), _, _) => { + w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize]) + } + (LowerAmPm, _, Some(t), _) => { let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; for c in ampm.chars().flat_map(|c| c.to_lowercase()) { w.write_char(c)? } Ok(()) - }), - UpperAmPm => self.time.map(|t| { - w.write_str(if t.hour12().0 { - am_pm(self.locale)[1] - } else { - am_pm(self.locale)[0] - })?; - Ok(()) - }), - Nanosecond => self.time.map(|t| { + } + (UpperAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + w.write_str(ampm) + } + (Nanosecond, _, Some(t), _) => { let nano = t.nanosecond() % 1_000_000_000; if nano == 0 { Ok(()) @@ -214,114 +203,77 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { write!(w, "{:09}", nano) } } - }), - Nanosecond3 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } + (Nanosecond3, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + } + (Nanosecond6, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + } + (Nanosecond9, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) - }) + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) } - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) - }) + (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { + write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) } - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) - }) + (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { + write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) } - TimezoneName => self.off.as_ref().map(|(name, _)| { - w.write_str(name)?; - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => self.off.as_ref().map(|&(_, off)| { - OffsetFormat { + (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{}", tz_name), + (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Maybe, allow_zulu: *spec == TimezoneOffsetZ, padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => self.off.as_ref().map(|&(_, off)| { - OffsetFormat { + }; + offset_format.format(w, *off) + } + (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, allow_zulu: *spec == TimezoneOffsetColonZ, padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetDoubleColon => self.off.as_ref().map(|&(_, off)| { - OffsetFormat { + }; + offset_format.format(w, *off) + } + (TimezoneOffsetDoubleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Seconds, colons: Colons::Colon, allow_zulu: false, padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetTripleColon => self.off.as_ref().map(|&(_, off)| { - OffsetFormat { + }; + offset_format.format(w, *off) + } + (TimezoneOffsetTripleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Hours, colons: Colons::None, allow_zulu: false, padding: Pad::Zero, - } - .format(w, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); + }; + offset_format.format(w, *off) } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = - (self.date, self.time, self.off.as_ref()) - { - Some(write_rfc2822(w, crate::NaiveDateTime::new(d, t), off)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = - (self.date, self.time, self.off.as_ref()) - { - Some(write_rfc3339( - w, - crate::NaiveDateTime::new(d, t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None - } + (RFC2822, Some(d), Some(t), Some((_, off))) => { + write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off) } - }; - - ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format + (RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + *off, + SecondsFormat::AutoSi, + false, + ), + _ => Err(fmt::Error), // insufficient arguments for given format + } } } From 74ba83ba27056e5bb610327d37c488f2b212af86 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 6 Apr 2024 13:30:21 +0200 Subject: [PATCH 848/999] Take `pad` by value --- src/format/formatting.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 6c3fc69692..5cc73a4cf6 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -103,7 +103,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { Item::Literal(s) | Item::Space(s) => w.write_str(s), #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), - Item::Numeric(ref spec, ref pad) => self.format_numeric(w, spec, pad), + Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), Item::Fixed(ref spec) => self.format_fixed(w, spec), Item::Error => Err(fmt::Error), }?; @@ -112,7 +112,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[cfg(feature = "alloc")] - fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: &Pad) -> fmt::Result { + fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { use self::Numeric::*; let (width, v) = match (spec, self.date, self.time) { From 0cfc405d3e93b1f4e8c6fd37a51449b6be7edb2c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:43:41 +0200 Subject: [PATCH 849/999] Optimize number formatting --- src/format/formatting.rs | 124 ++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 5cc73a4cf6..967f2d3a68 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -115,48 +115,88 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { use self::Numeric::*; - let (width, v) = match (spec, self.date, self.time) { - (Year, Some(d), _) => (4, i64::from(d.year())), - (YearDiv100, Some(d), _) => (2, i64::from(d.year()).div_euclid(100)), - (YearMod100, Some(d), _) => (2, i64::from(d.year()).rem_euclid(100)), - (IsoYear, Some(d), _) => (4, i64::from(d.iso_week().year())), - (IsoYearDiv100, Some(d), _) => (2, i64::from(d.iso_week().year()).div_euclid(100)), - (IsoYearMod100, Some(d), _) => (2, i64::from(d.iso_week().year()).rem_euclid(100)), - (Month, Some(d), _) => (2, i64::from(d.month())), - (Day, Some(d), _) => (2, i64::from(d.day())), - (WeekFromSun, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Sun))), - (WeekFromMon, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Mon))), - (IsoWeek, Some(d), _) => (2, i64::from(d.iso_week().week())), - (NumDaysFromSun, Some(d), _) => (1, i64::from(d.weekday().num_days_from_sunday())), - (WeekdayFromMon, Some(d), _) => (1, i64::from(d.weekday().number_from_monday())), - (Ordinal, Some(d), _) => (3, i64::from(d.ordinal())), - (Hour, _, Some(t)) => (2, i64::from(t.hour())), - (Hour12, _, Some(t)) => (2, i64::from(t.hour12().1)), - (Minute, _, Some(t)) => (2, i64::from(t.minute())), - (Second, _, Some(t)) => (2, i64::from(t.second() + t.nanosecond() / 1_000_000_000)), - (Nanosecond, _, Some(t)) => (9, i64::from(t.nanosecond() % 1_000_000_000)), - (Timestamp, Some(d), Some(t)) => { - let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc())); - let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0); - (1, timestamp) + fn write_one(w: &mut impl Write, v: u8) -> fmt::Result { + w.write_char((b'0' + v) as char) + } + + fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result { + let ones = b'0' + v % 10; + match (v / 10, pad) { + (0, Pad::None) => {} + (0, Pad::Space) => w.write_char(' ')?, + (tens, _) => w.write_char((b'0' + tens) as char)?, } - (Internal(_), _, _) => return Ok(()), // for future expansion - _ => return Err(fmt::Error), // insufficient arguments for given format - }; + w.write_char(ones as char) + } - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), + #[inline] + fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result { + if (1000..=9999).contains(&year) { + // fast path + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8) + } else { + write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year)) } - } else { - match pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), + } + + fn write_n( + w: &mut impl Write, + n: usize, + v: i64, + pad: Pad, + always_sign: bool, + ) -> fmt::Result { + if always_sign { + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, n + 1), + Pad::Space => write!(w, "{:+1$}", v, n + 1), + } + } else { + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, n), + Pad::Space => write!(w, "{:1$}", v, n), + } + } + } + + match (spec, self.date, self.time) { + (Year, Some(d), _) => write_year(w, d.year(), pad), + (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad), + (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad), + (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad), + (IsoYearDiv100, Some(d), _) => { + write_two(w, d.iso_week().year().div_euclid(100) as u8, pad) + } + (IsoYearMod100, Some(d), _) => { + write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad) + } + (Month, Some(d), _) => write_two(w, d.month() as u8, pad), + (Day, Some(d), _) => write_two(w, d.day() as u8, pad), + (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad), + (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad), + (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad), + (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8), + (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8), + (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false), + (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad), + (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad), + (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad), + (Second, _, Some(t)) => { + write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad) + } + (Nanosecond, _, Some(t)) => { + write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false) + } + (Timestamp, Some(d), Some(t)) => { + let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc())); + let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0); + write_n(w, 9, timestamp, pad, false) } + (Internal(_), _, _) => Ok(()), // for future expansion + _ => Err(fmt::Error), // insufficient arguments for given format } } @@ -206,21 +246,21 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } (Nanosecond3, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000) } (Nanosecond6, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) } (Nanosecond9, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; write!(w, "{:09}", t.nanosecond() % 1_000_000_000) } (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { - write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000) } (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { - write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) } (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { write!(w, "{:09}", t.nanosecond() % 1_000_000_000) From f8cecbe57ea6c2d7ce69c7e17ec989258a515ee0 Mon Sep 17 00:00:00 2001 From: Clar Charr Date: Mon, 29 Jan 2018 18:26:57 -0500 Subject: [PATCH 850/999] Make `Weekday::num_days_from public`, rename to `days_since`. Co-authored-by: Paul Dicker --- src/format/parsed.rs | 4 +-- src/naive/date/mod.rs | 2 +- src/weekday.rs | 76 +++++++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 8d1cde2d5a..b394214d23 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -1152,9 +1152,9 @@ fn resolve_week_date( let first_day_of_year = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; // Ordinal of the day at which week 1 starts. - let first_week_start = 1 + week_start_day.num_days_from(first_day_of_year.weekday()) as i32; + let first_week_start = 1 + week_start_day.days_since(first_day_of_year.weekday()) as i32; // Number of the `weekday`, which is 0 for the first day of the week. - let weekday = weekday.num_days_from(week_start_day) as i32; + let weekday = weekday.days_since(week_start_day) as i32; let ordinal = first_week_start + (week as i32 - 1) * 7 + weekday; if ordinal <= 0 { return Err(IMPOSSIBLE); diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 75e4c30958..9257ddab32 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -122,7 +122,7 @@ impl arbitrary::Arbitrary<'_> for NaiveDate { 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 + (self.ordinal() as i32 - self.weekday().days_since(day) as i32 + 6) / 7 } /// Makes a new `NaiveDate` from year, ordinal and flags. diff --git a/src/weekday.rs b/src/weekday.rs index fb0f315565..f41b5845cf 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -101,7 +101,7 @@ impl Weekday { /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] pub const fn number_from_monday(&self) -> u32 { - self.num_days_from(Weekday::Mon) + 1 + self.days_since(Weekday::Mon) + 1 } /// Returns a day-of-week number starting from Sunday = 1. @@ -111,7 +111,7 @@ impl Weekday { /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] pub const fn number_from_sunday(&self) -> u32 { - self.num_days_from(Weekday::Sun) + 1 + self.days_since(Weekday::Sun) + 1 } /// Returns a day-of-week number starting from Monday = 0. @@ -135,7 +135,7 @@ impl Weekday { /// ``` #[inline] pub const fn num_days_from_monday(&self) -> u32 { - self.num_days_from(Weekday::Mon) + self.days_since(Weekday::Mon) } /// Returns a day-of-week number starting from Sunday = 0. @@ -145,17 +145,27 @@ impl Weekday { /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] pub const fn num_days_from_sunday(&self) -> u32 { - self.num_days_from(Weekday::Sun) + self.days_since(Weekday::Sun) } - /// Returns a day-of-week number starting from the parameter `day` (D) = 0. + /// The number of days since the given day. /// - /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 - #[inline] - pub(crate) const fn num_days_from(&self, day: Weekday) -> u32 { - (*self as u32 + 7 - day as u32) % 7 + /// # Examples + /// + /// ``` + /// use chrono::Weekday::*; + /// assert_eq!(Mon.days_since(Mon), 0); + /// assert_eq!(Sun.days_since(Tue), 5); + /// assert_eq!(Wed.days_since(Sun), 3); + /// ``` + pub const fn days_since(&self, other: Weekday) -> u32 { + let lhs = *self as u32; + let rhs = other as u32; + if lhs < rhs { + 7 + lhs - rhs + } else { + lhs - rhs + } } } @@ -296,34 +306,28 @@ mod tests { use super::Weekday; #[test] - fn test_num_days_from() { + fn test_days_since() { 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 - ); + assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun)); + + assert_eq!(base_day.days_since(base_day), 0); + + assert_eq!(base_day.days_since(base_day.pred()), 1); + assert_eq!(base_day.days_since(base_day.pred().pred()), 2); + assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6); + + assert_eq!(base_day.days_since(base_day.succ()), 6); + assert_eq!(base_day.days_since(base_day.succ().succ()), 5); + assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1); } } From ffc75e57054182603436b56e67c284d9fb7dec78 Mon Sep 17 00:00:00 2001 From: Moritz Oberhauser Date: Mon, 8 Apr 2024 14:13:43 +0200 Subject: [PATCH 851/999] Add `TimeDelta::checked_mul` and `TimeDelta::checked_div` --- src/time_delta.rs | 59 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index ddab0d57a7..6cf28eecde 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -372,6 +372,40 @@ impl TimeDelta { TimeDelta::new(secs, nanos as u32) } + /// Multiply a `TimeDelta` with a i32, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_mul(&self, rhs: i32) -> Option { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanos as i64 * rhs as i64; + let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); + // Multiply seconds as i128 to prevent overflow + let secs: i128 = self.secs as i128 * rhs as i128 + extra_secs as i128; + if secs <= i64::MIN as i128 || secs >= i64::MAX as i128 { + return None; + }; + Some(TimeDelta { secs: secs as i64, nanos: nanos as i32 }) + } + + /// Divide a `TimeDelta` with a i32, returning `None` if dividing by 0. + #[must_use] + pub const fn checked_div(&self, rhs: i32) -> Option { + if rhs == 0 { + return None; + } + let secs = self.secs / rhs as i64; + let carry = self.secs % rhs as i64; + let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; + let nanos = self.nanos / rhs + extra_nanos as i32; + + let (secs, nanos) = match nanos { + i32::MIN..=-1 => (secs - 1, nanos + NANOS_PER_SEC), + NANOS_PER_SEC..=i32::MAX => (secs + 1, nanos - NANOS_PER_SEC), + _ => (secs, nanos), + }; + + Some(TimeDelta { secs, nanos }) + } + /// Returns the `TimeDelta` as an absolute (non-negative) value. #[inline] pub const fn abs(&self) -> TimeDelta { @@ -489,11 +523,7 @@ impl Mul for TimeDelta { type Output = TimeDelta; fn mul(self, rhs: i32) -> TimeDelta { - // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanos as i64 * rhs as i64; - let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); - let secs = self.secs * rhs as i64 + extra_secs; - TimeDelta { secs, nanos: nanos as i32 } + self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed") } } @@ -501,19 +531,7 @@ impl Div for TimeDelta { type Output = TimeDelta; fn div(self, rhs: i32) -> TimeDelta { - let mut secs = self.secs / rhs as i64; - let carry = self.secs - secs * rhs as i64; - let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; - let mut nanos = self.nanos / rhs + extra_nanos as i32; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - TimeDelta { secs, nanos } + self.checked_div(rhs).expect("`i32` is zero") } } @@ -1034,6 +1052,7 @@ mod tests { #[test] fn test_duration_checked_ops() { let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); assert_eq!( milliseconds(i64::MAX).checked_add(&milliseconds(0)), @@ -1056,6 +1075,10 @@ mod tests { ); assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none()); assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none()); + + assert!(seconds(i64::MAX / 1000).checked_mul(2000).is_none()); + assert!(seconds(i64::MIN / 1000).checked_mul(2000).is_none()); + assert!(seconds(1).checked_div(0).is_none()); } #[test] From 391187fff3be8ac345edb0fd859d36cb99c24316 Mon Sep 17 00:00:00 2001 From: David Herberth Date: Tue, 9 Apr 2024 12:56:27 +0200 Subject: [PATCH 852/999] Return error when rounding with zero duration --- src/round.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/round.rs b/src/round.rs index b5030a9412..9b19a64ee5 100644 --- a/src/round.rs +++ b/src/round.rs @@ -102,7 +102,7 @@ const fn span_for_digits(digits: u16) -> u32 { /// Both rounding and truncating are done via [`TimeDelta::num_nanoseconds`] and /// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the /// `TimeDelta` or the `DateTime` are too big to represented as nanoseconds. They -/// will also fail if the `TimeDelta` is bigger than the timestamp. +/// will also fail if the `TimeDelta` is bigger than the timestamp, negative or zero. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating #[cfg(feature = "std")] @@ -188,14 +188,11 @@ where T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { - if span < 0 { + if span <= 0 { return Err(RoundingError::DurationExceedsLimit); } let stamp = naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; - if span == 0 { - return Ok(original); - } let delta_down = stamp % span; if delta_down == 0 { Ok(original) @@ -225,7 +222,7 @@ where T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { - if span < 0 { + if span <= 0 { return Err(RoundingError::DurationExceedsLimit); } let stamp = @@ -453,9 +450,10 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_round(TimeDelta::zero()).unwrap().to_string(), - "2016-12-31 23:59:59.175500 UTC" + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); assert_eq!( dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), @@ -544,9 +542,10 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_round(TimeDelta::zero()).unwrap().to_string(), - "2016-12-31 23:59:59.175500" + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); assert_eq!( dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), @@ -620,6 +619,12 @@ mod tests { ) .unwrap(); + assert_eq!( + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + assert_eq!( dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" @@ -705,6 +710,12 @@ mod tests { .unwrap() .naive_utc(); + assert_eq!( + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + assert_eq!( dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170" From 760eb660d3b6ff9c5e0d34cfa1daa83d0a815e9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 04:39:09 +0000 Subject: [PATCH 853/999] Update windows-bindgen requirement from 0.55 to 0.56 Updates the requirements on [windows-bindgen](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.55.0...0.56.0) --- updated-dependencies: - dependency-name: windows-bindgen dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e612c7b415..4a555d21ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.55" } # The MSRV of its windows-metatada dependency is 1.70 +windows-bindgen = { version = "0.56" } # The MSRV of its windows-metatada dependency is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } From 46d44d6074ebdfddc62778bff24551f8d7bf9695 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 15 Apr 2024 10:58:14 +0200 Subject: [PATCH 854/999] Manually implement `Copy` for `DateTime` if offset is `Copy` --- src/datetime/mod.rs | 11 ++++++++++- src/datetime/tests.rs | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 366bbe6ad7..28cc64ab79 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -46,7 +46,7 @@ mod tests; /// There are some constructors implemented here (the `from_*` methods), but /// the general-purpose constructors are all via the methods on the /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. -#[derive(Copy, Clone)] +#[derive(Clone)] #[cfg_attr( any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), derive(Archive, Deserialize, Serialize), @@ -1408,6 +1408,15 @@ impl Timelike for DateTime { } } +// We don't store a field with the `Tz` type, so it doesn't need to influence whether `DateTime` can +// be `Copy`. Implement it manually if the two types we do have are `Copy`. +impl Copy for DateTime +where + ::Offset: Copy, + NaiveDateTime: Copy, +{ +} + impl PartialEq> for DateTime { fn eq(&self, other: &DateTime) -> bool { self.datetime == other.datetime diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 126e1d34d1..c890863d8b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,8 +1,8 @@ use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; -use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] -use crate::offset::{Local, Offset}; +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::{Datelike, Days, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; #[derive(Clone)] @@ -1318,9 +1318,44 @@ fn test_datetime_format_with_local() { #[test] fn test_datetime_is_send_and_copy() { + #[derive(Clone)] + struct Tz { + _not_send: *const i32, + } + impl TimeZone for Tz { + type Offset = Off; + + fn from_offset(_: &Self::Offset) -> Self { + unimplemented!() + } + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_local_datetime( + &self, + _: &NaiveDateTime, + ) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + fn offset_from_utc_datetime(&self, _: &NaiveDateTime) -> Self::Offset { + unimplemented!() + } + } + + #[derive(Copy, Clone, Debug)] + struct Off(()); + impl Offset for Off { + fn fix(&self) -> FixedOffset { + unimplemented!() + } + } + fn _assert_send_copy() {} - // UTC is known to be `Send + Copy`. - _assert_send_copy::>(); + // `DateTime` is `Send + Copy` if the offset is. + _assert_send_copy::>(); } #[test] From 352a35203a140e2352e5a5be62ba8dfd03b616e1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 15 Apr 2024 11:08:33 +0200 Subject: [PATCH 855/999] Prepare 0.4.38 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 0e44adfac8..96cbf16cdd 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-03-27 -version: 0.4.37 +date-released: 2024-04-15 +version: 0.4.38 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 4a555d21ac..e84c7eb8a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.37" +version = "0.4.38" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 7e25ffb373e6d2744beb8190df1ee5c075ec926d Mon Sep 17 00:00:00 2001 From: David Date: Mon, 29 Apr 2024 12:56:22 +0200 Subject: [PATCH 856/999] #1577: Changed years_since documentation to match its implementation --- src/datetime/mod.rs | 2 +- src/naive/date/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 28cc64ab79..4783e87253 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -594,7 +594,7 @@ impl DateTime { /// /// # Errors /// - /// Returns `None` if `base < self`. + /// Returns `None` if `base > self`. #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 9257ddab32..34101e0d84 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1136,7 +1136,7 @@ impl NaiveDate { /// /// # Errors /// - /// Returns `None` if `base < self`. + /// Returns `None` if `base > self`. #[must_use] pub const fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); From ce383862ffd84bf2f0f4a63d8760898f294519f6 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 23 May 2024 09:42:48 +0200 Subject: [PATCH 857/999] Remove obsolete weird feature guard --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 18b9226e40..a7c603a3e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,7 +501,6 @@ //! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 #![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] -#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![warn(unreachable_pub)] From 0a058ea70d31d508fa8359868d81ff21a31f74cf Mon Sep 17 00:00:00 2001 From: frederikhors <41120635+frederikhors@users.noreply.github.com> Date: Mon, 20 May 2024 16:23:14 +0200 Subject: [PATCH 858/999] Fix format::strftime docs link --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 4783e87253..9a2fea3ebb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1027,7 +1027,7 @@ impl DateTime { /// for a version that does not require a timezone in the to-be-parsed str. The returned /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. /// - /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// See the [`format::strftime` module](../format/strftime/index.html) for supported format /// sequences. /// /// # Example From f129a8ba35411fa79f15f9bf291552839ed2c333 Mon Sep 17 00:00:00 2001 From: frederikhors <41120635+frederikhors@users.noreply.github.com> Date: Mon, 20 May 2024 21:53:10 +0200 Subject: [PATCH 859/999] Update mod.rs --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 9a2fea3ebb..2e9ce150bb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1027,7 +1027,7 @@ impl DateTime { /// for a version that does not require a timezone in the to-be-parsed str. The returned /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. /// - /// See the [`format::strftime` module](../format/strftime/index.html) for supported format + /// See the [`format::strftime` module](crate::format::strftime) for supported format /// sequences. /// /// # Example From 865b0ad78021d301ebc17ef82ffcae5b671b171e Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Thu, 30 May 2024 12:00:08 +0300 Subject: [PATCH 860/999] Fix micros (optional) limit in and_hms_micro_opt --- src/naive/date/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 34101e0d84..ebf5a993af 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -834,7 +834,7 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// The microsecond part is allowed to exceed 1,000,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors From 20856278929d4768f91a781602b6621651918cd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 04:55:33 +0000 Subject: [PATCH 861/999] Update windows-bindgen requirement from 0.56 to 0.57 Updates the requirements on [windows-bindgen](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.56.0...0.57.0) --- updated-dependencies: - dependency-name: windows-bindgen dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 28 ++++------------------------ 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e84c7eb8a7..182cf88d1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.56" } # The MSRV of its windows-metatada dependency is 1.70 +windows-bindgen = { version = "0.57" } # MSRV is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index ca2def1e65..e78a95566f 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -6,6 +6,7 @@ windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTim pub type BOOL = i32; pub type BOOLEAN = u8; #[repr(C)] +#[derive(Clone, Copy)] pub struct DYNAMIC_TIME_ZONE_INFORMATION { pub Bias: i32, pub StandardName: [u16; 32], @@ -17,24 +18,14 @@ pub struct DYNAMIC_TIME_ZONE_INFORMATION { pub TimeZoneKeyName: [u16; 128], pub DynamicDaylightTimeDisabled: BOOLEAN, } -impl Copy for DYNAMIC_TIME_ZONE_INFORMATION {} -impl Clone for DYNAMIC_TIME_ZONE_INFORMATION { - fn clone(&self) -> Self { - *self - } -} #[repr(C)] +#[derive(Clone, Copy)] pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } -impl Copy for FILETIME {} -impl Clone for FILETIME { - fn clone(&self) -> Self { - *self - } -} #[repr(C)] +#[derive(Clone, Copy)] pub struct SYSTEMTIME { pub wYear: u16, pub wMonth: u16, @@ -45,13 +36,8 @@ pub struct SYSTEMTIME { pub wSecond: u16, pub wMilliseconds: u16, } -impl Copy for SYSTEMTIME {} -impl Clone for SYSTEMTIME { - fn clone(&self) -> Self { - *self - } -} #[repr(C)] +#[derive(Clone, Copy)] pub struct TIME_ZONE_INFORMATION { pub Bias: i32, pub StandardName: [u16; 32], @@ -61,9 +47,3 @@ pub struct TIME_ZONE_INFORMATION { pub DaylightDate: SYSTEMTIME, pub DaylightBias: i32, } -impl Copy for TIME_ZONE_INFORMATION {} -impl Clone for TIME_ZONE_INFORMATION { - fn clone(&self) -> Self { - *self - } -} From de2f1f4cee53191baa15195667641c13401362ef Mon Sep 17 00:00:00 2001 From: Behnam Esfahbod Date: Mon, 10 Jun 2024 13:25:12 -0700 Subject: [PATCH 862/999] native/date: Improve DelayedFormat doc re Panics --- src/naive/date/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index ebf5a993af..bd70d52b31 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1200,9 +1200,11 @@ impl NaiveDate { /// or just feed it into `print!` and other formatting macros. /// (In this way it avoids the redundant memory allocation.) /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. + /// # Panics + /// + /// Converting or formatting the returned `DelayedFormat` panics if the format string is wrong. + /// Because of this delayed failure, you are recommended to immediately use the `DelayedFormat` + /// value. /// /// # Example /// From 51c2d4b5efeee571bb58dfeab4c3e6f90b52e184 Mon Sep 17 00:00:00 2001 From: Sebastian Goll <1277035+sgoll@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:09:15 +0200 Subject: [PATCH 863/999] Fix typo in rustdoc of from_timestamp_nanos() --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2e9ce150bb..f463c2ab5b 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -822,7 +822,7 @@ impl DateTime { Self::from_timestamp(secs, nsecs) } - /// Creates a new [`DateTime`] from the number of non-leap microseconds + /// Creates a new [`DateTime`] from the number of non-leap nanoseconds /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). /// /// This is guaranteed to round-trip with [`timestamp_nanos`](DateTime::timestamp_nanos). From e6f5d515cc42e22946e550b60771fa4cecfe6e1a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Jul 2024 09:54:21 +0200 Subject: [PATCH 864/999] Apply clippy suggestions from Rust 1.79 --- src/format/parse.rs | 1 - src/offset/local/tz_info/rule.rs | 6 +++--- src/offset/local/tz_info/timezone.rs | 14 +++++++------- src/offset/mod.rs | 4 ++-- src/time_delta.rs | 2 +- tests/dateutils.rs | 5 ++++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index ee26e66334..9c08bdb67f 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -6,7 +6,6 @@ use core::borrow::Borrow; use core::str; -use core::usize; use super::scan; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index daaed416f1..b74eeed156 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -156,7 +156,7 @@ impl AlternateTime { }; // Check if the current year is valid for the following computations - if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) { + if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) { return Err(Error::OutOfRange("out of range date time")); } @@ -233,7 +233,7 @@ impl AlternateTime { current_year: i32, ) -> Result, Error> { // Check if the current year is valid for the following computations - if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) { + if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) { return Err(Error::OutOfRange("out of range date time")); } @@ -687,7 +687,7 @@ impl UtcDateTime { let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; let second = remaining_seconds % SECONDS_PER_MINUTE; - let year = match year >= i32::min_value() as i64 && year <= i32::max_value() as i64 { + let year = match year >= i32::MIN as i64 && year <= i32::MAX as i64 { true => year as i32, false => return Err(Error::OutOfRange("i64 is out of range for i32")), }; diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index dbb2def931..1b089902ed 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -415,7 +415,7 @@ impl<'a> TimeZoneRef<'a> { /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { - if unix_leap_time == i64::min_value() { + if unix_leap_time == i64::MIN { return Err(Error::OutOfRange("out of range operation")); } @@ -572,7 +572,7 @@ pub(crate) struct LocalTimeType { impl LocalTimeType { /// Construct a local time type pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result { - if ut_offset == i32::min_value() { + if ut_offset == i32::MIN { return Err(Error::LocalTimeType("invalid UTC offset")); } @@ -586,7 +586,7 @@ impl LocalTimeType { /// Construct a local time type with the specified UTC offset in seconds pub(super) const fn with_offset(ut_offset: i32) -> Result { - if ut_offset == i32::min_value() { + if ut_offset == i32::MIN { return Err(Error::LocalTimeType("invalid UTC offset")); } @@ -818,7 +818,7 @@ mod tests { let time_zone_3 = TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?; let time_zone_4 = TimeZone::new( - vec![Transition::new(i32::min_value().into(), 0), Transition::new(0, 1)], + vec![Transition::new(i32::MIN.into(), 0), Transition::new(0, 1)], vec![utc, cet], Vec::new(), Some(fixed_extra_rule), @@ -926,7 +926,7 @@ mod tests { #[test] fn test_leap_seconds_overflow() -> Result<(), Error> { let time_zone_err = TimeZone::new( - vec![Transition::new(i64::min_value(), 0)], + vec![Transition::new(i64::MIN, 0)], vec![LocalTimeType::UTC], vec![LeapSecond::new(0, 1)], Some(TransitionRule::from(LocalTimeType::UTC)), @@ -934,13 +934,13 @@ mod tests { assert!(time_zone_err.is_err()); let time_zone = TimeZone::new( - vec![Transition::new(i64::max_value(), 0)], + vec![Transition::new(i64::MAX, 0)], vec![LocalTimeType::UTC], vec![LeapSecond::new(0, 1)], None, )?; assert!(matches!( - time_zone.find_local_time_type(i64::max_value()), + time_zone.find_local_time_type(i64::MAX), Err(Error::FindLocalTimeType(_)) )); diff --git a/src/offset/mod.rs b/src/offset/mod.rs index c8f9c59c20..c04b9ef908 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -674,9 +674,9 @@ mod tests { #[test] fn test_nanos_never_panics() { - Utc.timestamp_nanos(i64::max_value()); + Utc.timestamp_nanos(i64::MAX); Utc.timestamp_nanos(i64::default()); - Utc.timestamp_nanos(i64::min_value()); + Utc.timestamp_nanos(i64::MIN); } #[test] diff --git a/src/time_delta.rs b/src/time_delta.rs index 6cf28eecde..608d43561f 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -10,9 +10,9 @@ //! Temporal quantification +use core::fmt; use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration; -use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 5aa00da6bf..2dfca90023 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -1,8 +1,11 @@ #![cfg(all(unix, feature = "clock", feature = "std"))] -use chrono::{Datelike, Days, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; use std::{path, process, thread}; +#[cfg(target_os = "linux")] +use chrono::Days; +use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; + fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { let output = process::Command::new(path) .arg("-d") From 2cec1d3a6bdb436af06887492f69ba68814c53ee Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Jul 2024 09:56:36 +0200 Subject: [PATCH 865/999] Add TimeDelta::{MIN, MAX} const values --- src/naive/date/tests.rs | 4 ++-- src/naive/datetime/tests.rs | 4 ++-- src/time_delta.rs | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index ce5ffa4a68..6ae6867fc1 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -471,14 +471,14 @@ fn test_date_checked_add_signed() { ymd(MAX_YEAR, 12, 31), ); check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); - check(ymd(0, 1, 1), TimeDelta::max_value(), None); + check(ymd(0, 1, 1), TimeDelta::MAX, None); check( ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), ymd(MIN_YEAR, 1, 1), ); check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); - check(ymd(0, 1, 1), TimeDelta::min_value(), None); + check(ymd(0, 1, 1), TimeDelta::MIN, None); } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index b5a3d5cf4f..161245813d 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -36,13 +36,13 @@ fn test_datetime_add() { Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MAX, None); let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None); - check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MIN, None); } #[test] diff --git a/src/time_delta.rs b/src/time_delta.rs index 608d43561f..88aa31c6e3 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -417,12 +417,14 @@ impl TimeDelta { } /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MIN` instead")] #[inline] pub const fn min_value() -> TimeDelta { MIN } /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MAX` instead")] #[inline] pub const fn max_value() -> TimeDelta { MAX @@ -474,6 +476,12 @@ impl TimeDelta { }; TimeDelta { secs: -self.secs - secs_diff, nanos } } + + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + pub const MIN: Self = MIN; + + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + pub const MAX: Self = MAX; } impl Neg for TimeDelta { From 4401f399ab1dad9187ebf8a50178927deac8b0f5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Jul 2024 11:40:01 +0200 Subject: [PATCH 866/999] Use environment secrets for Codecov token --- .github/workflows/codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 8c84ea1c14..22f2dd9dcb 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -13,6 +13,7 @@ jobs: job_code_coverage: name: llvm-cov runs-on: ubuntu-latest + environment: Coverage env: CARGO_TERM_COLOR: always steps: From c46b18d088165b69305e7d1f405ebbf75b98c515 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:20:45 +0000 Subject: [PATCH 867/999] Update windows-bindgen requirement from 0.57 to 0.58 Updates the requirements on [windows-bindgen](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/0.57.0...0.58.0) --- updated-dependencies: - dependency-name: windows-bindgen dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 182cf88d1c..ab4b045c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.57" } # MSRV is 1.70 +windows-bindgen = { version = "0.58" } # MSRV is 1.70 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } From 1eb3174428d8745dd06b645aefb3403445d74d06 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:35:00 +0100 Subject: [PATCH 868/999] docs: document century cutoff for %y --- src/format/strftime.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 555e36f9c0..e446c68c02 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -99,6 +99,8 @@ Notes: [^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. + For `%y`, values greater or equal to 70 are interpreted as being in the 20th century, + values smaller than 70 in the 21st century. [^2]: `%U`: Week 1 starts with the first Sunday in that year. From b18c1c1d8e94f3a11e98968872af80c7b4ab0fd3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 25 Jul 2024 15:07:48 +0200 Subject: [PATCH 869/999] Fix line continuation in Parsed::to_datetime() --- src/format/parsed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index b394214d23..e611dfdafa 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -876,7 +876,7 @@ impl Parsed { /// including offset from UTC. /// - `OUT_OF_RANGE` /// - if any of the fields of `Parsed` are set to a value beyond their acceptable - /// range. + /// range. /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. /// - if the date does not exist. pub fn to_datetime(&self) -> ParseResult> { From fb9eebc970b89962e1fb72ae752575933f47228c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 25 Jul 2024 15:25:31 +0200 Subject: [PATCH 870/999] benches: add feature unstable-locales --- bench/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 4c14068877..bdc9afcdb2 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" # But not when it lives in a separate crate :-). # See https://github.com/chronotope/chrono/pull/1104. -[lib] -name = "benches" +[features] +unstable-locales = ["chrono/unstable-locales"] [dependencies] chrono = { path = "..", features = ["__internal_bench", "serde"] } From 50938ab45465fa780cb21bbc35761e53cf6ae857 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 5 Aug 2024 14:12:11 +0200 Subject: [PATCH 871/999] deny: update config for 0.16 --- bench/Cargo.toml | 1 + deny.toml | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bench/Cargo.toml b/bench/Cargo.toml index bdc9afcdb2..ff0f95bc28 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -2,6 +2,7 @@ name = "benches" version = "0.1.0" edition = "2021" +publish = false # Even as a `dev-dependency` Criterion and its dependencies can affect the MSRV of chrono. # But not when it lives in a separate crate :-). diff --git a/deny.toml b/deny.toml index 5c70069aee..105cc4ebc6 100644 --- a/deny.toml +++ b/deny.toml @@ -1,8 +1,6 @@ [licenses] -allow-osi-fsf-free = "either" -copyleft = "deny" +allow = ["Apache-2.0", "MIT", "Unicode-DFS-2016"] +private = { ignore = true } [advisories] -unmaintained = "deny" -unsound = "deny" yanked = "deny" From 37ec019f8671d4c2c66d9b836d7eeea7b4709f89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:31:21 +0000 Subject: [PATCH 872/999] Bump EmbarkStudios/cargo-deny-action from 1 to 2 Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 1 to 2. - [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases) - [Commits](https://github.com/embarkstudios/cargo-deny-action/compare/v1...v2) --- updated-dependencies: - dependency-name: EmbarkStudios/cargo-deny-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 173883b433..736b977097 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 check-doc: runs-on: ubuntu-latest From 68afd5b4075f580a93f8d95a20e432bb7221f2df Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Mon, 22 Jul 2024 20:13:24 +0200 Subject: [PATCH 873/999] Add `checked_first_day` --- src/naive/mod.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index ca76c0a3bc..cdbe85bc3d 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -60,13 +60,37 @@ impl NaiveWeek { #[inline] #[must_use] pub const fn first_day(&self) -> NaiveDate { + expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the first day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MIN; + /// let week = date.week(Weekday::Mon); + /// if let Some(first_day) = week.checked_first_day() { + /// assert!(first_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_first_day(&self) -> Option { let start = self.start.num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to subtract from `self.date`. // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - expect(self.date.add_days(days), "first weekday out of range for `NaiveDate`") + self.date.add_days(days) } /// Returns a date representing the last day of the week. From ccb04bc449a2aa81a8000940c4d395e71146760b Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Mon, 22 Jul 2024 20:14:32 +0200 Subject: [PATCH 874/999] Add `checked_last_day` --- src/naive/mod.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index cdbe85bc3d..f0e5eb1fcf 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -112,13 +112,37 @@ impl NaiveWeek { #[inline] #[must_use] pub const fn last_day(&self) -> NaiveDate { + expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the last day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// if let Some(last_day) = week.checked_last_day() { + /// assert!(last_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_last_day(&self) -> Option { let end = self.start.pred().num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to add to `self.date`. // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - expect(self.date.add_days(days), "last weekday out of range for `NaiveDate`") + self.date.add_days(days) } /// Returns a [`RangeInclusive`] representing the whole week bounded by From f765936ee35ad3881d8705ae56714f3d805a198f Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Thu, 8 Aug 2024 13:56:54 +0300 Subject: [PATCH 875/999] Add `checked_days` --- src/naive/mod.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f0e5eb1fcf..56ff8d4a69 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -166,7 +166,43 @@ impl NaiveWeek { #[inline] #[must_use] pub const fn days(&self) -> RangeInclusive { - self.first_day()..=self.last_day() + // `expect` doesn't work because `RangeInclusive` is not `Copy` + match self.checked_days() { + Some(val) => val, + None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"), + } + } + + /// Returns an [`Option>`] representing the whole week bounded by + /// [checked_first_day](NaiveWeek::checked_first_day) and + /// [checked_last_day](NaiveWeek::checked_last_day) functions. + /// + /// Returns `None` if either of the boundaries are out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// let _days = match week.checked_days() { + /// Some(d) => d, + /// None => { + /// // error handling code + /// return; + /// } + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_days(&self) -> Option> { + match (self.checked_first_day(), self.checked_last_day()) { + (Some(first), Some(last)) => Some(first..=last), + (_, _) => None, + } } } From 081c6482bc1ca1a3f0e54532a892a377d28bb999 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Mon, 22 Jul 2024 20:15:02 +0200 Subject: [PATCH 876/999] Test that `NaiveWeek` checked functions don't panic --- src/naive/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 56ff8d4a69..2e7d6fd1d0 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -264,4 +264,18 @@ mod test { let date_min = NaiveDate::MIN; assert!(date_min.week(Weekday::Mon).last_day() >= date_min); } + + #[test] + fn test_naiveweek_checked_no_panic() { + let date_max = NaiveDate::MAX; + if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() { + assert!(last == date_max); + } + let date_min = NaiveDate::MIN; + if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() { + assert!(first == date_min); + } + let _ = date_min.week(Weekday::Mon).checked_days(); + let _ = date_max.week(Weekday::Mon).checked_days(); + } } From c8defc7766cba248a12915e8020dd85d3ffb421c Mon Sep 17 00:00:00 2001 From: Ihor Ranchynskyi Date: Mon, 16 Sep 2024 11:10:29 +0100 Subject: [PATCH 877/999] impl serde::Serialize and serde::Deserialize for TimeDelta (#1599) --- src/time_delta.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/time_delta.rs b/src/time_delta.rs index 88aa31c6e3..d030689695 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -638,6 +638,47 @@ impl arbitrary::Arbitrary<'_> for TimeDelta { } } +#[cfg(feature = "serde")] +mod serde { + use super::TimeDelta; + use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + + impl Serialize for TimeDelta { + fn serialize(&self, serializer: S) -> Result { + <(i64, i32) as Serialize>::serialize(&(self.secs, self.nanos), serializer) + } + } + + impl<'de> Deserialize<'de> for TimeDelta { + fn deserialize>(deserializer: D) -> Result { + let (secs, nanos) = <(i64, i32) as Deserialize>::deserialize(deserializer)?; + TimeDelta::new(secs, nanos as u32).ok_or(Error::custom("TimeDelta out of bounds")) + } + } + + #[cfg(test)] + mod tests { + use super::{super::MAX, TimeDelta}; + + #[test] + fn test_serde() { + let duration = TimeDelta::new(123, 456).unwrap(); + assert_eq!( + serde_json::from_value::(serde_json::to_value(duration).unwrap()) + .unwrap(), + duration + ); + } + + #[test] + #[should_panic(expected = "TimeDelta out of bounds")] + fn test_serde_oob_panic() { + let _ = + serde_json::from_value::(serde_json::json!([MAX.secs + 1, 0])).unwrap(); + } + } +} + #[cfg(test)] mod tests { use super::OutOfRangeError; From 05a6ce68cf18a01274cef211b080a7170c7c1a1f Mon Sep 17 00:00:00 2001 From: DSeeLP <46624152+DSeeLP@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:09:37 +0200 Subject: [PATCH 878/999] Derive `PartialEq`,`Eq`,`Hash`,`Copy` and `Clone` on `NaiveWeek` --- src/naive/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 2e7d6fd1d0..83ab69c4ce 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -29,7 +29,7 @@ pub use self::internals::YearFlags as __BenchYearFlags; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct NaiveWeek { date: NaiveDate, start: Weekday, From d8a177e4f5cc7512b8cbe8a5d27e68d6cfcfb8fd Mon Sep 17 00:00:00 2001 From: Mirage Lyu Date: Thu, 12 Sep 2024 16:41:06 +0800 Subject: [PATCH 879/999] Support ohos tzdata since oh35 --- src/offset/local/tz_info/parser.rs | 15 +++++++ src/offset/local/tz_info/timezone.rs | 61 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 47cc0377e4..509c4fef99 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -247,6 +247,21 @@ impl<'a> Cursor<'a> { Ok(u32::from_be_bytes(buf)) } + #[cfg(target_env = "ohos")] + pub(crate) fn seek_after(&mut self, offset: usize) -> Result { + if offset < self.read_count { + return Err(io::Error::from(ErrorKind::UnexpectedEof)); + } + match self.remaining.get((offset - self.read_count)..) { + Some(remaining) => { + self.remaining = remaining; + self.read_count = offset; + Ok(offset) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + /// Read exactly `count` bytes, reducing remaining data and incrementing read count pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> { match (self.remaining.get(..count), self.remaining.get(count..)) { diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 1b089902ed..15123256f4 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -8,6 +8,9 @@ use std::{cmp::Ordering, fmt, str}; use super::rule::{AlternateTime, TransitionRule}; use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; +#[cfg(target_env = "ohos")] +use crate::offset::local::tz_info::parser::Cursor; + /// Time zone #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) struct TimeZone { @@ -50,6 +53,12 @@ impl TimeZone { } } + // ohos merge all file into tzdata since ver35 + #[cfg(target_env = "ohos")] + { + return Self::from_tz_data(&find_ohos_tz_data(tz_string)?); + } + let mut chars = tz_string.chars(); if chars.next() == Some(':') { return Self::from_file(&mut find_tz_file(chars.as_str())?); @@ -629,6 +638,58 @@ fn find_tz_file(path: impl AsRef) -> Result { } } +#[cfg(target_env = "ohos")] +fn from_tzdata_bytes(bytes: &mut Vec, tz_string: &str) -> Result, Error> { + const VERSION_SIZE: usize = 12; + const OFFSET_SIZE: usize = 4; + const INDEX_CHUNK_SIZE: usize = 48; + const ZONENAME_SIZE: usize = 40; + + let mut cursor = Cursor::new(&bytes); + // version head + let _ = cursor.read_exact(VERSION_SIZE)?; + let index_offset_offset = cursor.read_be_u32()?; + let data_offset_offset = cursor.read_be_u32()?; + // final offset + let _ = cursor.read_be_u32()?; + + cursor.seek_after(index_offset_offset as usize)?; + let mut idx = index_offset_offset; + while idx < data_offset_offset { + let index_buf = cursor.read_exact(ZONENAME_SIZE)?; + let offset = cursor.read_be_u32()?; + let length = cursor.read_be_u32()?; + let zone_name = str::from_utf8(index_buf)?.trim_end_matches('\0'); + if zone_name != tz_string { + idx += INDEX_CHUNK_SIZE as u32; + continue; + } + cursor.seek_after((data_offset_offset + offset) as usize)?; + return match cursor.read_exact(length as usize) { + Ok(result) => Ok(result.to_vec()), + Err(_err) => Err(Error::InvalidTzFile("invalid ohos tzdata chunk")), + }; + } + + Err(Error::InvalidTzString("cannot find tz string within tzdata")) +} + +#[cfg(target_env = "ohos")] +fn from_tzdata_file(file: &mut File, tz_string: &str) -> Result, Error> { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + from_tzdata_bytes(&mut bytes, tz_string) +} + +#[cfg(target_env = "ohos")] +fn find_ohos_tz_data(tz_string: &str) -> Result, Error> { + const TZDATA_PATH: &str = "/system/etc/zoneinfo/tzdata"; + match File::open(TZDATA_PATH) { + Ok(mut file) => from_tzdata_file(&mut file, tz_string), + Err(err) => Err(err.into()), + } +} + // Possible system timezone directories #[cfg(unix)] const ZONE_INFO_DIRECTORIES: [&str; 4] = From 771c0477bf4fa3febc74f3c8cd8e7a25d9463a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Mon, 14 Oct 2024 12:42:49 +0200 Subject: [PATCH 880/999] Use Formatter::pad (instead of write_str) for Weekdays That way, width, alignment and fill from the format string are respected. Fixes #1620. --- src/weekday.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/weekday.rs b/src/weekday.rs index f41b5845cf..5c62ea3ed9 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -171,7 +171,7 @@ impl Weekday { impl fmt::Display for Weekday { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { + f.pad(match *self { Weekday::Mon => "Mon", Weekday::Tue => "Tue", Weekday::Wed => "Wed", @@ -331,6 +331,16 @@ mod tests { } } + #[test] + fn test_formatting_alignment() { + // No exhaustive testing here as we just delegate the + // implementation to Formatter::pad. Just some basic smoke + // testing to ensure that it's in fact being done. + assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon"); + assert_eq!(format!("{:^7}", Weekday::Mon), " Mon "); + assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ"); + } + #[test] #[cfg(feature = "serde")] fn test_serde_serialize() { From 77d50b1fc1e28d592346532620c07622da3aa5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sun, 20 Oct 2024 07:38:15 +0000 Subject: [PATCH 881/999] Fix typos --- CHANGELOG.md | 6 +++--- src/format/mod.rs | 2 +- src/format/parse.rs | 2 +- src/format/parsed.rs | 2 +- src/offset/local/tz_info/rule.rs | 4 ++-- src/offset/local/tz_info/timezone.rs | 2 +- tests/dateutils.rs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6f6f935e..e43ab571e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,7 +143,7 @@ release notes on [GitHub](https://github.com/chronotope/chrono/releases). ### Fixes -* Make Datetime arithmatic adjust their offsets after discovering their new +* Make Datetime arithmetic adjust their offsets after discovering their new timestamps (@quodlibetor #337) * Put wasm-bindgen related code and dependencies behind a `wasmbind` feature gate. (@quodlibetor #335) @@ -599,7 +599,7 @@ and replaced by 0.2.25 very shortly. Duh.) ### Added -- `Offset` is splitted into `TimeZone` (constructor) and `Offset` (storage) types. +- `Offset` is split into `TimeZone` (constructor) and `Offset` (storage) types. You would normally see only the former, as the latter is mostly an implementation detail. Most importantly, `Local` now can be used to directly construct timezone-aware values. @@ -661,7 +661,7 @@ and replaced by 0.2.25 very shortly. Duh.) so we simply let it go. In the case that `Time` is really required, one can use a simpler `NaiveTime`. - `NaiveTime` and `NaiveDate` can be freely combined and splitted, + `NaiveTime` and `NaiveDate` can be freely combined and split, and `TimeZone::from_{local,utc}_datetime` can be used to convert from/to the local time. - `with_offset` method has been removed. Use `with_timezone` method instead. diff --git a/src/format/mod.rs b/src/format/mod.rs index 447efde817..95d86dcd8d 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -422,7 +422,7 @@ pub enum ParseErrorKind { /// All formatting items have been read but there is a remaining input. TooLong, - /// There was an error on the formatting string, or there were non-supported formating items. + /// There was an error on the formatting string, or there were non-supported formatting items. BadFormat, // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. diff --git a/src/format/parse.rs b/src/format/parse.rs index 9c08bdb67f..5a3a702734 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -508,7 +508,7 @@ where } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time +/// A space or a 'T' are accepted as the separator between the date and time /// parts. Additional spaces are allowed between each component. /// /// All of these examples are equivalent: diff --git a/src/format/parsed.rs b/src/format/parsed.rs index e611dfdafa..e14f03f425 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -536,7 +536,7 @@ impl Parsed { /// /// # Errors /// - /// Returns `OUT_OF_RANGE` if `value` is ouside the range of an `i32`. + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. /// /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index b74eeed156..5e45682799 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -589,11 +589,11 @@ impl RuleDay { let week_day_of_first_month_day = (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK); - let first_week_day_occurence_in_month = + let first_week_day_occurrence_in_month = 1 + (week_day as i64 - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK); let mut month_day = - first_week_day_occurence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; + first_week_day_occurrence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; if month_day > day_in_month { month_day -= DAYS_PER_WEEK } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 15123256f4..268739c21a 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -252,7 +252,7 @@ impl<'a> TimeZoneRef<'a> { match transition_start.cmp(&transition_end) { Ordering::Greater => { - // bakwards transition, eg from DST to regular + // backwards transition, eg from DST to regular // this means a given local time could have one of two possible offsets if local_leap_time < transition_end { return Ok(crate::MappedLocalTime::Single(prev)); diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 2dfca90023..a0a445aa99 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -144,8 +144,8 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim let ldt = Local .from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap()) .unwrap(); - let formated_date = format!("{}\n", ldt.format(required_format)); - assert_eq!(date_command_str, formated_date); + let formatted_date = format!("{}\n", ldt.format(required_format)); + assert_eq!(date_command_str, formatted_date); } #[test] From 7974c60649351553e57727ea02f88b66f8835f0e Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Wed, 23 Oct 2024 21:13:17 -0700 Subject: [PATCH 882/999] Fix comment. --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index f463c2ab5b..db91561450 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1727,7 +1727,7 @@ impl Sub<&DateTime> for DateTime { /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. /// -/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. +/// Strongly consider using `DateTime::checked_add_days` to get an `Option` instead. impl Add for DateTime { type Output = DateTime; From f9ffd6fbde2a2dbc02d42afdcbec5da84ba4e1e0 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Sat, 9 Nov 2024 18:23:17 +0100 Subject: [PATCH 883/999] add `#[inline]` to `num_days` --- src/time_delta.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/time_delta.rs b/src/time_delta.rs index d030689695..3eb041ad57 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -281,6 +281,7 @@ impl TimeDelta { } /// Returns the total number of whole days in the `TimeDelta`. + #[inline] pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } From 1c7567b34d9f45c1a694add4424efa5718d1f9df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 04:52:56 +0000 Subject: [PATCH 884/999] Bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 22f2dd9dcb..b6a98e4a12 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -26,7 +26,7 @@ jobs: - name: Generate code coverage run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: From 1456fa0977e054ae7bd893936a1cace2acc5d0e2 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Nov 2024 10:41:32 +0100 Subject: [PATCH 885/999] Apply suggestions from clippy 1.83 --- src/datetime/serde.rs | 12 ++++++------ src/format/mod.rs | 2 +- src/month.rs | 3 +-- src/naive/date/mod.rs | 4 ++-- src/naive/datetime/serde.rs | 12 ++++++------ src/naive/time/serde.rs | 2 +- src/weekday.rs | 2 +- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 6f048a4568..e3c6bf57db 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -38,7 +38,7 @@ impl ser::Serialize for DateTime { inner: &'a DateTime, } - impl<'a, Tz: TimeZone> fmt::Display for FormatIso8601<'a, Tz> { + impl fmt::Display for FormatIso8601<'_, Tz> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let naive = self.inner.naive_local(); let offset = self.inner.offset.fix(); @@ -52,7 +52,7 @@ impl ser::Serialize for DateTime { struct DateTimeVisitor; -impl<'de> de::Visitor<'de> for DateTimeVisitor { +impl de::Visitor<'_> for DateTimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -230,7 +230,7 @@ pub mod ts_nanoseconds { d.deserialize_i64(NanoSecondsTimestampVisitor) } - impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -512,7 +512,7 @@ pub mod ts_microseconds { d.deserialize_i64(MicroSecondsTimestampVisitor) } - impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -786,7 +786,7 @@ pub mod ts_milliseconds { d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) } - impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -1052,7 +1052,7 @@ pub mod ts_seconds { d.deserialize_i64(SecondsTimestampVisitor) } - impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { + impl de::Visitor<'_> for SecondsTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/format/mod.rs b/src/format/mod.rs index 95d86dcd8d..cd4520c217 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -366,7 +366,7 @@ const fn internal_fixed(val: InternalInternal) -> Item<'static> { Item::Fixed(Fixed::Internal(InternalFixed { val })) } -impl<'a> Item<'a> { +impl Item<'_> { /// Convert items that contain a reference to the format string into an owned variant. #[cfg(any(feature = "alloc", feature = "std"))] pub fn to_owned(self) -> Item<'static> { diff --git a/src/month.rs b/src/month.rs index f8033fccd6..f964f85d88 100644 --- a/src/month.rs +++ b/src/month.rs @@ -191,7 +191,6 @@ impl num_traits::FromPrimitive for Month { /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` /// ---------------------------| -------------------- | --------------------- | ... | ----- /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) - #[inline] fn from_u64(n: u64) -> Option { Self::from_u32(n as u32) @@ -279,7 +278,7 @@ mod month_serde { struct MonthVisitor; - impl<'de> de::Visitor<'de> for MonthVisitor { + impl de::Visitor<'_> for MonthVisitor { type Value = Month; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index bd70d52b31..1514518ab2 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2406,7 +2406,7 @@ mod serde { inner: &'a D, } - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + impl fmt::Display for FormatWrapped<'_, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } @@ -2418,7 +2418,7 @@ mod serde { struct NaiveDateVisitor; - impl<'de> de::Visitor<'de> for NaiveDateVisitor { + impl de::Visitor<'_> for NaiveDateVisitor { type Value = NaiveDate; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index ae06578b5c..85fcb94f29 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -15,7 +15,7 @@ impl ser::Serialize for NaiveDateTime { inner: &'a D, } - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + impl fmt::Display for FormatWrapped<'_, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } @@ -27,7 +27,7 @@ impl ser::Serialize for NaiveDateTime { struct NaiveDateTimeVisitor; -impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { +impl de::Visitor<'_> for NaiveDateTimeVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -161,7 +161,7 @@ pub mod ts_nanoseconds { pub(super) struct NanoSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -443,7 +443,7 @@ pub mod ts_microseconds { pub(super) struct MicroSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for MicroSecondsTimestampVisitor { + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -713,7 +713,7 @@ pub mod ts_milliseconds { pub(super) struct MilliSecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -968,7 +968,7 @@ pub mod ts_seconds { pub(super) struct SecondsTimestampVisitor; - impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { + impl de::Visitor<'_> for SecondsTimestampVisitor { type Value = NaiveDateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index c1df410292..f9b1dde215 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -16,7 +16,7 @@ impl ser::Serialize for NaiveTime { struct NaiveTimeVisitor; -impl<'de> de::Visitor<'de> for NaiveTimeVisitor { +impl de::Visitor<'_> for NaiveTimeVisitor { type Value = NaiveTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/weekday.rs b/src/weekday.rs index 5c62ea3ed9..92644e8f6a 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -276,7 +276,7 @@ mod weekday_serde { struct WeekdayVisitor; - impl<'de> de::Visitor<'de> for WeekdayVisitor { + impl de::Visitor<'_> for WeekdayVisitor { type Value = Weekday; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { From ca8232ff4e51cd918b3a9d84bdca8c466ded3cdc Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Nov 2024 10:42:22 +0100 Subject: [PATCH 886/999] Update licenses for unicode-ident 1.0.14 --- deny.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index 105cc4ebc6..c9121c1f0e 100644 --- a/deny.toml +++ b/deny.toml @@ -1,5 +1,5 @@ [licenses] -allow = ["Apache-2.0", "MIT", "Unicode-DFS-2016"] +allow = ["Apache-2.0", "MIT", "Unicode-3.0"] private = { ignore = true } [advisories] From 65c47f377d5e0a2505cbbff46c8eec5ff2a13da9 Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:35:37 +0200 Subject: [PATCH 887/999] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e43ab571e5..c61a13f549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -238,7 +238,7 @@ release notes on [GitHub](https://github.com/chronotope/chrono/releases). ### Features -* New `timestamp_millis` method on `DateTime` and `NaiveDateTim` that returns +* New `timestamp_millis` method on `DateTime` and `NaiveDateTime` that returns number of milliseconds since the epoch. (@quodlibetor) * Support exact decimal width on subsecond display for RFC3339 via a new `to_rfc3339_opts` method on `DateTime` (@dekellum) From 33aaebfc35794553960ed86f77a47d1d16fab988 Mon Sep 17 00:00:00 2001 From: Noisy <125606576+donatik27@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:09:37 +0100 Subject: [PATCH 888/999] Update mod.rs --- src/offset/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index c04b9ef908..346f809732 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -89,7 +89,7 @@ impl MappedLocalTime { } } - /// Returns the earliest possible result of a the time zone mapping. + /// Returns the earliest possible result of the time zone mapping. /// /// # Errors /// @@ -102,7 +102,7 @@ impl MappedLocalTime { } } - /// Returns the latest possible result of a the time zone mapping. + /// Returns the latest possible result of the time zone mapping. /// /// # Errors /// From 8b863490d88ba098038392c8aa930012ffd0c439 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 3 Dec 2024 15:23:24 +0100 Subject: [PATCH 889/999] Bump version to 0.4.39 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab4b045c48..95f3b39072 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.38" +version = "0.4.39" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 07a42f9f49e9e89ffb97988f06fa724c3dcc8911 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 6 Jan 2025 14:28:41 +0100 Subject: [PATCH 890/999] Add Month::num_days() --- src/month.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/month.rs b/src/month.rs index f964f85d88..ee5aa910f1 100644 --- a/src/month.rs +++ b/src/month.rs @@ -3,6 +3,7 @@ use core::fmt; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; +use crate::naive::NaiveDate; use crate::OutOfRange; /// The month of the year. @@ -161,6 +162,29 @@ impl Month { Month::December => "December", } } + + /// Get the length in days of the month + /// + /// Yields `None` if `year` is out of range for `NaiveDate`. + pub fn num_days(&self, year: i32) -> Option { + Some(match *self { + Month::January => 31, + Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() { + true => 29, + false => 28, + }, + Month::March => 31, + Month::April => 30, + Month::May => 31, + Month::June => 30, + Month::July => 31, + Month::August => 31, + Month::September => 30, + Month::October => 31, + Month::November => 30, + Month::December => 31, + }) + } } impl TryFrom for Month { @@ -438,4 +462,11 @@ mod tests { let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap(); assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), month); } + + #[test] + fn num_days() { + assert_eq!(Month::January.num_days(2020), Some(31)); + assert_eq!(Month::February.num_days(2020), Some(29)); + assert_eq!(Month::February.num_days(2019), Some(28)); + } } From 97f758f46d5068170b458636cb35345ca31edbbf Mon Sep 17 00:00:00 2001 From: Kenny Kerr Date: Tue, 7 Jan 2025 13:17:55 -0600 Subject: [PATCH 891/999] Update Windows dependencies --- Cargo.toml | 4 ++-- src/offset/local/win_bindings.rs | 4 ++-- src/offset/local/win_bindings.txt | 10 +++++----- tests/win_bindings.rs | 3 +-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95f3b39072..5dc8aee5bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,10 +49,10 @@ wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] -windows-targets = { version = "0.52", optional = true } +windows-targets = { version = "0.53", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.58" } # MSRV is 1.70 +windows-bindgen = { version = "0.59" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index e78a95566f..f2def25e9c 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,10 +1,10 @@ #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] + windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); pub type BOOL = i32; -pub type BOOLEAN = u8; #[repr(C)] #[derive(Clone, Copy)] pub struct DYNAMIC_TIME_ZONE_INFORMATION { @@ -16,7 +16,7 @@ pub struct DYNAMIC_TIME_ZONE_INFORMATION { pub DaylightDate: SYSTEMTIME, pub DaylightBias: i32, pub TimeZoneKeyName: [u16; 128], - pub DynamicDaylightTimeDisabled: BOOLEAN, + pub DynamicDaylightTimeDisabled: bool, } #[repr(C)] #[derive(Clone, Copy)] diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt index fc00509cfb..2efa4314fc 100644 --- a/src/offset/local/win_bindings.txt +++ b/src/offset/local/win_bindings.txt @@ -1,7 +1,7 @@ --out src/offset/local/win_bindings.rs ---config flatten sys no-bindgen-comment +--flat --sys --no-comment --filter - Windows.Win32.System.Time.GetTimeZoneInformationForYear - Windows.Win32.System.Time.SystemTimeToFileTime - Windows.Win32.System.Time.SystemTimeToTzSpecificLocalTime - Windows.Win32.System.Time.TzSpecificLocalTimeToSystemTime + GetTimeZoneInformationForYear + SystemTimeToFileTime + SystemTimeToTzSpecificLocalTime + TzSpecificLocalTimeToSystemTime diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index bfb110c38e..053239fe19 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -9,8 +9,7 @@ fn gen_bindings() { let output = "src/offset/local/win_bindings.rs"; let existing = fs::read_to_string(output).unwrap(); - let log = bindgen(["--etc", input]).unwrap(); - eprintln!("{}", log); + bindgen(["--etc", input]); // Check the output is the same as before. // Depending on the git configuration the file may have been checked out with `\r\n` newlines or From 12a814f860ad041227aa511739f94c2a9077c9b9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 10 Jan 2025 10:04:46 +0100 Subject: [PATCH 892/999] Add weekly scheduled CI runs --- .github/workflows/lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 736b977097..f6f9a62719 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,8 @@ on: push: branches: [main, 0.5.x] pull_request: + schedule: + - cron: "38 6 * * 5" jobs: lint: From 82bacb85bd87b31a097e5aa19cd1beac0f2965f8 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 10 Jan 2025 10:08:57 +0100 Subject: [PATCH 893/999] wasm32-wasi was renamed to wasm32-wasip1 cargo-wasi seems unmaintained and does not support the new target. --- .github/workflows/test.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d866cbf7e0..86c44e26a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,25 +145,20 @@ jobs: # with the host system. - run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind - test_wasi: + test_wasip1: strategy: matrix: os: [ubuntu-latest] - target: - - wasm32-wasi + target: [wasm32-wasip1] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - targets: wasm32-wasi + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - run: cargo install cargo-wasi - - uses: mwilliamson/setup-wasmtime-action@v2 - with: - wasmtime-version: "12.0.1" # We can't use `--all-features` because rkyv uses the mutually-exclusive-feature pattern - - run: cargo wasi test --features=serde,unstable-locales --color=always -- --color=always + - run: cargo check --target ${{ matrix.target }} --all-targets --features=serde,unstable-locales cross-targets: strategy: From 5f2f5a83329c0a5501f1ad99f398ff1ede0a5d8a Mon Sep 17 00:00:00 2001 From: MagnumT <94921355+MagnumTrader@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:21:13 +0100 Subject: [PATCH 894/999] Feature/round_up method on DurationRound trait (#1651) --- src/round.rs | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/src/round.rs b/src/round.rs index 9b19a64ee5..6751562a62 100644 --- a/src/round.rs +++ b/src/round.rs @@ -153,6 +153,32 @@ pub trait DurationRound: Sized { /// ); /// ``` fn duration_trunc(self, duration: TimeDelta) -> Result; + + /// Return a copy rounded **up** by TimeDelta. + /// + /// # Example + /// ``` rust + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::milliseconds(10)).unwrap().to_string(), + /// "2018-01-11 12:00:00.160 UTC" + /// ); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::hours(1)).unwrap().to_string(), + /// "2018-01-11 13:00:00 UTC" + /// ); + /// + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::days(1)).unwrap().to_string(), + /// "2018-01-12 00:00:00 UTC" + /// ); + /// ``` + fn duration_round_up(self, duration: TimeDelta) -> Result; } impl DurationRound for DateTime { @@ -165,6 +191,10 @@ impl DurationRound for DateTime { fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self.naive_local(), self, duration) } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self.naive_local(), self, duration) + } } impl DurationRound for NaiveDateTime { @@ -177,6 +207,10 @@ impl DurationRound for NaiveDateTime { fn duration_trunc(self, duration: TimeDelta) -> Result { duration_trunc(self, self, duration) } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self, self, duration) + } } fn duration_round( @@ -238,6 +272,31 @@ where } } +fn duration_round_up( + naive: NaiveDateTime, + original: T, + duration: TimeDelta, +) -> Result +where + T: Timelike + Add + Sub, +{ + if let Some(span) = duration.num_nanoseconds() { + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); + } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let delta_down = stamp % span; + match delta_down.cmp(&0) { + Ordering::Equal => Ok(original), + Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)), + Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())), + } + } else { + Err(RoundingError::DurationExceedsLimit) + } +} + /// An error from rounding by `TimeDelta` /// /// See: [`DurationRound`] @@ -835,4 +894,189 @@ mod tests { let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1); assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); } + + #[test] + fn test_duration_round_up() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180 UTC" + ); + + // round up + let dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00 UTC" + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00 UTC" + ); + + // timezone east + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 +01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 +01:00" + ); + + // timezone west + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 -01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 -01:00" + ); + } + + #[test] + fn test_duration_round_up_naive() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180" + ); + + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00" + ); + } + + #[test] + fn test_duration_round_up_pre_epoch() { + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "1969-12-12 12:20:00 UTC" + ); + + let time_delta = TimeDelta::minutes(30); + assert_eq!( + DateTime::UNIX_EPOCH.duration_round_up(time_delta).unwrap().to_string(), + "1970-01-01 00:00:00 UTC" + ) + } + + #[test] + fn test_duration_round_up_close_to_min_max() { + let mut dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + let span = TimeDelta::nanoseconds(i64::MAX); + + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + DateTime::from_timestamp_nanos(i64::MAX).to_string() + ); + + dt = DateTime::UNIX_EPOCH + TimeDelta::nanoseconds(1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::from_timestamp_nanos(i64::MAX)); + + let dt = DateTime::from_timestamp_nanos(1); + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(-1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + + // Rounds to 1677-09-21 00:12:43.145224193 UTC if at i64::MIN. + // because i64::MIN is 1677-09-21 00:12:43.145224192 UTC. + // + // v + // We add 2 to get to 1677-09-21 00:12:43.145224194 UTC + // this issue is because abs(i64::MIN) == i64::MAX + 1 + let dt = DateTime::from_timestamp_nanos(i64::MIN + 2); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + } } From 4c42ee5cebf9c26ea902e7ca3f0f21156261a76d Mon Sep 17 00:00:00 2001 From: Steven Tang Date: Tue, 21 Jan 2025 14:21:02 -0500 Subject: [PATCH 895/999] Expose `write_to` for `DelayedFormat` Request for #1649 - renamed format to write-to and made it public. - added unittests - added benchmarks to compare and show `Display` is slower than `write_to` in that specific use case. --- bench/benches/chrono.rs | 20 +++++++++++++++++ src/format/formatting.rs | 48 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 925c2939f1..91c4e45520 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -185,6 +185,25 @@ fn bench_format_with_items(c: &mut Criterion) { }); } +fn benches_delayed_format(c: &mut Criterion) { + let mut group = c.benchmark_group("delayed_format"); + let dt = Local::now(); + group.bench_function(BenchmarkId::new("with_display", dt), |b| { + b.iter_batched( + || dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), + |df| black_box(df).to_string(), + criterion::BatchSize::SmallInput, + ) + }); + group.bench_function(BenchmarkId::new("with_string_buffer", dt), |b| { + b.iter_batched( + || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), + |(df, string)| black_box(df).write_to(&mut black_box(string)), + criterion::BatchSize::SmallInput, + ) + }); +} + fn bench_format_manual(c: &mut Criterion) { let dt = Local::now(); c.bench_function("bench_format_manual", |b| { @@ -237,6 +256,7 @@ criterion_group!( bench_format, bench_format_with_items, bench_format_manual, + benches_delayed_format, bench_naivedate_add_signed, bench_datetime_with, ); diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 967f2d3a68..a0e156c130 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -97,7 +97,19 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { DelayedFormat { date, time, off: Some(name_and_diff), items, locale } } - fn format(&self, w: &mut impl Write) -> fmt::Result { + /// Formats `DelayedFormat` into a `core::fmt::Write` instance. + /// # Errors + /// This function returns a `core::fmt::Error` if formatting into the `core::fmt::Write` instance fails. + /// + /// # Example + /// ### Writing to a String + /// ``` + /// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + /// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + /// let mut buffer = String::new(); + /// let _ = df.write_to(&mut buffer); + /// ``` + pub fn write_to(&self, w: &mut impl Write) -> fmt::Result { for item in self.items.clone() { match *item.borrow() { Item::Literal(s) | Item::Space(s) => w.write_str(s), @@ -321,7 +333,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut result = String::new(); - self.format(&mut result)?; + self.write_to(&mut result)?; f.pad(&result) } } @@ -329,7 +341,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> Display for Delayed /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "alloc")] -#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -353,7 +365,7 @@ where /// Formats single formatting item #[cfg(feature = "alloc")] -#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -611,6 +623,34 @@ mod tests { #[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; + #[cfg(feature = "alloc")] + #[test] + fn test_delayed_write_to() { + let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + assert_eq!(dt_str, "2022-02-01 13:50:00.123456789"); + } + + #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] + #[test] + fn test_with_locale_delayed_write_to() { + use crate::format::locales::Locale; + use crate::DateTime; + + let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + + assert_eq!(dt_str, "火曜日, 2月 01, 2022"); + } + #[test] #[cfg(feature = "alloc")] fn test_date_format() { From 7cdca4b006fba93278ba3ef532495315cf6f8724 Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:26:36 +0100 Subject: [PATCH 896/999] Update LICENSE.txt --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 924ff57f22..0f9e3cfda8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ Rust-chrono is dual-licensed under The MIT License [1] and -Apache 2.0 License [2]. Copyright (c) 2014--2017, Kang Seonghoon and +Apache 2.0 License [2]. Copyright (c) 2014--2025, Kang Seonghoon and contributors. Nota Bene: This is same as the Rust Project's own license. From 8317e7ca41ac2c1a2c7293673015b41aca05773d Mon Sep 17 00:00:00 2001 From: Sam Folorunsho Date: Sat, 15 Feb 2025 16:30:30 +0000 Subject: [PATCH 897/999] docs: fix minor typo --- src/naive/date/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 1514518ab2..945bb4c272 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -60,7 +60,7 @@ mod tests; /// on the same calendar date---April 23, 1616---but in the different calendar. /// Britain used the Julian calendar at that time, so Shakespeare's death is later.) /// -/// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). +/// * ISO 8601 calendars have the year 0, which is 1 BCE (a year before 1 CE). /// If you need a typical BCE/BC and CE/AD notation for year numbers, /// use the [`Datelike::year_ce`] method. /// From 15e287b4fbbf198de84ba50eaac478fcf34c9ea5 Mon Sep 17 00:00:00 2001 From: AVee Date: Tue, 18 Feb 2025 10:54:53 +0100 Subject: [PATCH 898/999] Use NaiveDateTime for internal tz_info methods. (#1658) This replaces passing a timestamp and year to several internal functions with passing a NaiveDateTime, making the function signature slightly clearer. The values originated from in the a NaiveDateTime in first place, so it basically just postpones the conversion. Co-authored-by: Arjan --- src/offset/local/tz_info/rule.rs | 19 ++++++++----------- src/offset/local/tz_info/timezone.rs | 21 +++++++++------------ src/offset/local/unix.rs | 4 ++-- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 5e45682799..a99fe253af 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; - use super::parser::Cursor; use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; use super::{ Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, SECONDS_PER_DAY, }; +use crate::{Datelike, NaiveDateTime}; +use std::cmp::Ordering; /// Transition rule #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -81,15 +81,14 @@ impl TransitionRule { /// Find the local time type associated to the transition rule at the specified Unix time in seconds pub(super) fn find_local_time_type_from_local( &self, - local_time: i64, - year: i32, + local_time: NaiveDateTime, ) -> Result, Error> { match self { TransitionRule::Fixed(local_time_type) => { Ok(crate::MappedLocalTime::Single(*local_time_type)) } TransitionRule::Alternate(alternate_time) => { - alternate_time.find_local_time_type_from_local(local_time, year) + alternate_time.find_local_time_type_from_local(local_time) } } } @@ -229,13 +228,11 @@ impl AlternateTime { fn find_local_time_type_from_local( &self, - local_time: i64, - current_year: i32, + local_time: NaiveDateTime, ) -> Result, Error> { - // Check if the current year is valid for the following computations - if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) { - return Err(Error::OutOfRange("out of range date time")); - } + // Year must be between i32::MIN + 2 and i32::MAX - 2, year in NaiveDate is always smaller. + let current_year = local_time.year(); + let local_time = local_time.and_utc().timestamp(); let dst_start_transition_start = self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time); diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 268739c21a..d33608d4aa 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -1,13 +1,13 @@ //! Types related to a time zone. +use super::rule::{AlternateTime, TransitionRule}; +use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; +use crate::NaiveDateTime; use std::fs::{self, File}; use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::{cmp::Ordering, fmt, str}; -use super::rule::{AlternateTime, TransitionRule}; -use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; - #[cfg(target_env = "ohos")] use crate::offset::local::tz_info::parser::Cursor; @@ -133,13 +133,11 @@ impl TimeZone { self.as_ref().find_local_time_type(unix_time) } - // should we pass NaiveDateTime all the way through to this fn? pub(crate) fn find_local_time_type_from_local( &self, - local_time: i64, - year: i32, + local_time: NaiveDateTime, ) -> Result, Error> { - self.as_ref().find_local_time_type_from_local(local_time, year) + self.as_ref().find_local_time_type_from_local(local_time) } /// Returns a reference to the time zone @@ -225,8 +223,7 @@ impl<'a> TimeZoneRef<'a> { pub(crate) fn find_local_time_type_from_local( &self, - local_time: i64, - year: i32, + local_time: NaiveDateTime, ) -> Result, Error> { // #TODO: this is wrong as we need 'local_time_to_local_leap_time ? // but ... does the local time even include leap seconds ?? @@ -235,10 +232,10 @@ impl<'a> TimeZoneRef<'a> { // Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), // Err(err) => return Err(err), // }; - let local_leap_time = local_time; + let local_leap_time = local_time.and_utc().timestamp(); // if we have at least one transition, - // we must check _all_ of them, incase of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions + // we must check _all_ of them, in case of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions let offset_after_last = if !self.transitions.is_empty() { let mut prev = self.local_time_types[0]; @@ -301,7 +298,7 @@ impl<'a> TimeZoneRef<'a> { }; if let Some(extra_rule) = self.extra_rule { - match extra_rule.find_local_time_type_from_local(local_time, year) { + match extra_rule.find_local_time_type_from_local(local_time) { Ok(local_time_type) => Ok(local_time_type), Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), err => err, diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index c1942eba7e..b5fb342b95 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,7 +12,7 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::Sys use super::tz_info::TimeZone; use super::{FixedOffset, NaiveDateTime}; -use crate::{Datelike, MappedLocalTime}; +use crate::MappedLocalTime; pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { offset(utc, false) @@ -164,7 +164,7 @@ impl Cache { // we pass through the year as the year of a local point in time must either be valid in that locale, or // the entire time was skipped in which case we will return MappedLocalTime::None anyway. self.zone - .find_local_time_type_from_local(d.and_utc().timestamp(), d.year()) + .find_local_time_type_from_local(d) .expect("unable to select local time type") .and_then(|o| FixedOffset::east_opt(o.offset())) } From 11d227a22bf7a1818e5f32cc7076443d0e932976 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 21 Feb 2025 10:32:15 +0100 Subject: [PATCH 899/999] Apply suggestions from clippy 1.85 --- src/naive/date/mod.rs | 2 +- src/naive/date/tests.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 945bb4c272..4977b7daa8 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1142,7 +1142,7 @@ impl NaiveDate { let mut years = self.year() - base.year(); // Comparing tuples is not (yet) possible in const context. Instead we combine month and // day into one `u32` for easy comparison. - if (self.month() << 5 | self.day()) < (base.month() << 5 | base.day()) { + if ((self.month() << 5) | self.day()) < ((base.month() << 5) | base.day()) { years -= 1; } diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 6ae6867fc1..2e2c2c396b 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -181,7 +181,7 @@ fn test_date_from_yo() { assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26))); assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31))); assert_eq!(from_yo(2012, 367), None); - assert_eq!(from_yo(2012, 1 << 28 | 60), None); + assert_eq!(from_yo(2012, (1 << 28) | 60), None); assert_eq!(from_yo(2014, 0), None); assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1))); @@ -406,7 +406,7 @@ fn test_date_with_ordinal() { assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); assert_eq!(d.with_ordinal(367), None); - assert_eq!(d.with_ordinal(1 << 28 | 60), None); + assert_eq!(d.with_ordinal((1 << 28) | 60), None); let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); assert_eq!(d.with_ordinal(366), None); assert_eq!(d.with_ordinal(u32::MAX), None); From 7f6cf5e5048a4049c265938ce75c14714e7c142f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 21 Feb 2025 10:32:44 +0100 Subject: [PATCH 900/999] Switch to 2024 style --- bench/benches/chrono.rs | 8 +-- bench/benches/serde.rs | 2 +- rustfmt.toml | 1 + src/datetime/mod.rs | 20 +++----- src/datetime/serde.rs | 6 ++- src/datetime/tests.rs | 11 ++-- src/format/formatting.rs | 2 +- src/format/locales.rs | 2 +- src/format/mod.rs | 4 +- src/format/parse.rs | 16 +++--- src/format/parsed.rs | 18 ++----- src/format/scan.rs | 10 ++-- src/format/strftime.rs | 12 ++--- src/month.rs | 6 +-- src/naive/date/mod.rs | 6 +-- src/naive/date/tests.rs | 4 +- src/naive/datetime/mod.rs | 6 +-- src/naive/datetime/tests.rs | 9 ++-- src/naive/internals.rs | 2 +- src/naive/isoweek.rs | 2 +- src/naive/mod.rs | 6 +-- src/naive/time/mod.rs | 6 +-- src/offset/fixed.rs | 2 +- src/offset/local/mod.rs | 6 +-- src/offset/local/tz_info/parser.rs | 4 +- src/offset/local/tz_info/rule.rs | 10 ++-- src/offset/local/tz_info/timezone.rs | 6 +-- src/offset/local/windows.rs | 4 +- src/offset/mod.rs | 4 +- src/round.rs | 2 +- src/time_delta.rs | 76 +++++++++++++++------------- src/traits.rs | 6 +-- src/weekday.rs | 10 ++-- 33 files changed, 135 insertions(+), 154 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 91c4e45520..c74d6ae382 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -1,12 +1,12 @@ //! Benchmarks for chrono that just depend on std -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; -use chrono::format::StrftimeItems; -use chrono::prelude::*; #[cfg(feature = "unstable-locales")] use chrono::Locale; -use chrono::{DateTime, FixedOffset, Local, TimeDelta, Utc, __BenchYearFlags}; +use chrono::format::StrftimeItems; +use chrono::prelude::*; +use chrono::{__BenchYearFlags, DateTime, FixedOffset, Local, TimeDelta, Utc}; fn bench_date_from_ymd(c: &mut Criterion) { c.bench_function("bench_date_from_ymd", |b| { diff --git a/bench/benches/serde.rs b/bench/benches/serde.rs index ee4deb5a49..8791019f60 100644 --- a/bench/benches/serde.rs +++ b/bench/benches/serde.rs @@ -1,4 +1,4 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use chrono::NaiveDateTime; diff --git a/rustfmt.toml b/rustfmt.toml index 2a35f0230c..d6b90a3179 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ +style_edition = "2024" use_small_heuristics = "Max" diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index db91561450..ccb6c953b2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -14,22 +14,22 @@ use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; +#[allow(deprecated)] +use crate::Date; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] use crate::format::Locale; +#[cfg(feature = "alloc")] +use crate::format::{DelayedFormat, SecondsFormat, write_rfc2822, write_rfc3339}; use crate::format::{ - parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed, - StrftimeItems, TOO_LONG, + Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, TOO_LONG, parse, + parse_and_remainder, parse_rfc3339, }; -#[cfg(feature = "alloc")] -use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; -#[allow(deprecated)] -use crate::Date; -use crate::{expect, try_opt}; use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; +use crate::{expect, try_opt}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -1849,11 +1849,7 @@ impl From for DateTime { // unlikely but should be handled let dur = e.duration(); let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); - if nsec == 0 { - (-sec, 0) - } else { - (-sec - 1, 1_000_000_000 - nsec) - } + if nsec == 0 { (-sec, 0) } else { (-sec - 1, 1_000_000_000 - nsec) } } }; Utc.timestamp_opt(sec, nsec).unwrap() diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index e3c6bf57db..1492fe3f0b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -2,7 +2,7 @@ use core::fmt; use serde::{de, ser}; use super::DateTime; -use crate::format::{write_rfc3339, SecondsFormat}; +use crate::format::{SecondsFormat, write_rfc3339}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; @@ -1289,7 +1289,9 @@ mod tests { } assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!( + serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err() + ); } #[test] diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index c890863d8b..b7f7106928 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -734,10 +734,7 @@ fn test_datetime_rfc2822() { "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ), Ok( - ymdhms( - &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), - 1969, 2, 13, 23, 32, 0, - ) + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) ) ); // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" @@ -1099,8 +1096,10 @@ fn test_parse_from_str() { Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") - .is_err()); + assert!( + DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); assert_eq!( DateTime::parse_from_str("0", "%s").unwrap(), DateTime::from_timestamp(0, 0).unwrap().fixed_offset() diff --git a/src/format/formatting.rs b/src/format/formatting.rs index a0e156c130..1b7df6db0c 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -638,8 +638,8 @@ mod tests { #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] #[test] fn test_with_locale_delayed_write_to() { - use crate::format::locales::Locale; use crate::DateTime; + use crate::format::locales::Locale; let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap(); let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); diff --git a/src/format/locales.rs b/src/format/locales.rs index 4cf4d4149a..c615476c0c 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,6 +1,6 @@ #[cfg(feature = "unstable-locales")] mod localized { - use pure_rust_locales::{locale_match, Locale}; + use pure_rust_locales::{Locale, locale_match}; pub(crate) const fn default_locale() -> Locale { Locale::POSIX diff --git a/src/format/mod.rs b/src/format/mod.rs index cd4520c217..fb608cc8a7 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -54,15 +54,15 @@ pub mod strftime; // not require `alloc`. pub(crate) mod locales; +pub use formatting::SecondsFormat; pub(crate) use formatting::write_hundreds; #[cfg(feature = "alloc")] pub(crate) use formatting::write_rfc2822; #[cfg(any(feature = "alloc", feature = "serde"))] pub(crate) use formatting::write_rfc3339; -pub use formatting::SecondsFormat; #[cfg(feature = "alloc")] #[allow(deprecated)] -pub use formatting::{format, format_item, DelayedFormat}; +pub use formatting::{DelayedFormat, format, format_item}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; pub(crate) use parse::parse_rfc3339; diff --git a/src/format/parse.rs b/src/format/parse.rs index 5a3a702734..91a586548d 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -8,9 +8,9 @@ use core::borrow::Borrow; use core::str; use super::scan; +use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; use super::{ParseError, ParseResult}; -use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { @@ -640,15 +640,17 @@ mod tests { // most unicode whitespace characters parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - &[Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")] + &[Space( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + )], ); // most unicode whitespace characters parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", &[ Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), - Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") - ] + Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}"), + ], ); check("a", &[Space("")], Err(TOO_LONG)); check("a", &[Space(" ")], Err(TOO_LONG)); @@ -1835,8 +1837,10 @@ mod tests { #[test] fn test_issue_1010() { - let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", - "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); + let dt = crate::NaiveDateTime::parse_from_str( + "\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a", + ); assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); } } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index e14f03f425..b6a5291390 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -4,7 +4,7 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; +use super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE, ParseResult}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; @@ -176,11 +176,7 @@ pub struct Parsed { #[inline] fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<()> { if let Some(ref old) = *old { - if *old == new { - Ok(()) - } else { - Err(IMPOSSIBLE) - } + if *old == new { Ok(()) } else { Err(IMPOSSIBLE) } } else { *old = Some(new); Ok(()) @@ -701,11 +697,7 @@ impl Parsed { (_, _, _) => return Err(NOT_ENOUGH), }; - if verified { - Ok(parsed_date) - } else { - Err(IMPOSSIBLE) - } + if verified { Ok(parsed_date) } else { Err(IMPOSSIBLE) } } /// Returns a parsed naive time out of given fields. @@ -1166,10 +1158,10 @@ fn resolve_week_date( mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; - use crate::naive::{NaiveDate, NaiveTime}; - use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Datelike; use crate::Weekday::*; + use crate::naive::{NaiveDate, NaiveTime}; + use crate::offset::{FixedOffset, TimeZone, Utc}; #[test] fn test_parsed_set_fields() { diff --git a/src/format/scan.rs b/src/format/scan.rs index 1ab87b9dd5..68958fd25f 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -5,7 +5,7 @@ * Various scanning routines for the parser. */ -use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; +use super::{INVALID, OUT_OF_RANGE, ParseResult, TOO_SHORT}; use crate::Weekday; /// Tries to parse the non-negative number from `min` to `max` digits. @@ -217,11 +217,7 @@ where const fn digits(s: &str) -> ParseResult<(u8, u8)> { let b = s.as_bytes(); - if b.len() < 2 { - Err(TOO_SHORT) - } else { - Ok((b[0], b[1])) - } + if b.len() < 2 { Err(TOO_SHORT) } else { Ok((b[0], b[1])) } } let negative = match s.chars().next() { Some('+') => { @@ -359,8 +355,8 @@ mod tests { comment_2822, nanosecond, nanosecond_fixed, short_or_long_month0, short_or_long_weekday, timezone_offset_2822, }; - use crate::format::{INVALID, TOO_SHORT}; use crate::Weekday; + use crate::format::{INVALID, TOO_SHORT}; #[test] fn test_rfc2822_comments() { diff --git a/src/format/strftime.rs b/src/format/strftime.rs index e446c68c02..e2bd4f5b57 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -161,12 +161,12 @@ Notes: #[cfg(feature = "alloc")] extern crate alloc; -use super::{fixed, internal_fixed, num, num0, nums}; -#[cfg(feature = "unstable-locales")] -use super::{locales, Locale}; -use super::{Fixed, InternalInternal, Item, Numeric, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] -use super::{ParseError, BAD_FORMAT}; +use super::{BAD_FORMAT, ParseError}; +use super::{Fixed, InternalInternal, Item, Numeric, Pad}; +#[cfg(feature = "unstable-locales")] +use super::{Locale, locales}; +use super::{fixed, internal_fixed, num, num0, nums}; #[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::vec::Vec; @@ -686,8 +686,8 @@ mod tests { use crate::format::Item::{self, Literal, Space}; #[cfg(feature = "unstable-locales")] use crate::format::Locale; - use crate::format::{fixed, internal_fixed, num, num0, nums}; use crate::format::{Fixed, InternalInternal, Numeric::*}; + use crate::format::{fixed, internal_fixed, num, num0, nums}; #[cfg(feature = "alloc")] use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; diff --git a/src/month.rs b/src/month.rs index ee5aa910f1..1b4dbfd7b8 100644 --- a/src/month.rs +++ b/src/month.rs @@ -3,8 +3,8 @@ use core::fmt; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use crate::naive::NaiveDate; use crate::OutOfRange; +use crate::naive::NaiveDate; /// The month of the year. /// @@ -395,8 +395,8 @@ mod tests { #[test] #[cfg(feature = "serde")] fn test_serde_serialize() { - use serde_json::to_string; use Month::*; + use serde_json::to_string; let cases: Vec<(Month, &str)> = vec![ (January, "\"January\""), @@ -422,8 +422,8 @@ mod tests { #[test] #[cfg(feature = "serde")] fn test_serde_deserialize() { - use serde_json::from_str; use Month::*; + use serde_json::from_str; let cases: Vec<(&str, Month)> = vec![ ("\"january\"", January), diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 4977b7daa8..62efd45459 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -30,13 +30,13 @@ use pure_rust_locales::Locale; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ - parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, - Parsed, StrftimeItems, + Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder, + write_hundreds, }; use crate::month::Months; use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; -use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; +use crate::{expect, try_opt}; use super::internals::{Mdf, YearFlags}; diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 2e2c2c396b..5c944d59db 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -1,5 +1,5 @@ -use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; -use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; +use super::{Days, MAX_YEAR, MIN_YEAR, Months, NaiveDate}; +use crate::naive::internals::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; use crate::{Datelike, TimeDelta, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index a2ffc69674..b634f8f432 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -15,14 +15,14 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; -use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::format::{ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; use crate::time_delta::NANOS_PER_SEC; use crate::{ - expect, try_opt, DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, - Timelike, Weekday, + DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, + Weekday, expect, try_opt, }; /// Tools to help serializing/deserializing `NaiveDateTime`s diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 161245813d..33eb57bdaf 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -199,11 +199,10 @@ fn test_datetime_parse_from_str() { NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(ymdhms(2013, 8, 9, 23, 54, 35)) ); - assert!(NaiveDateTime::parse_from_str( - "Sat, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); + assert!( + NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient assert_eq!( diff --git a/src/naive/internals.rs b/src/naive/internals.rs index da591f8d08..765a930be8 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -381,7 +381,7 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { use super::Mdf; - use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; + use super::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 93d0dc423f..c572011aed 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -164,8 +164,8 @@ impl fmt::Debug for IsoWeek { mod tests { #[cfg(feature = "rkyv-validation")] use super::IsoWeek; - use crate::naive::date::{self, NaiveDate}; use crate::Datelike; + use crate::naive::date::{self, NaiveDate}; #[test] fn test_iso_week_extremes() { diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 83ab69c4ce..5be9a50858 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -6,8 +6,8 @@ use core::ops::RangeInclusive; -use crate::expect; use crate::Weekday; +use crate::expect; pub(crate) mod date; pub(crate) mod datetime; @@ -15,11 +15,11 @@ mod internals; pub(crate) mod isoweek; pub(crate) mod time; -pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; #[allow(deprecated)] pub use self::date::{MAX_DATE, MIN_DATE}; +pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; #[allow(deprecated)] -pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; +pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime}; pub use self::isoweek::IsoWeek; pub use self::time::NaiveTime; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f32d828080..a0b92836f1 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -15,11 +15,11 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ - parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, - Parsed, StrftimeItems, + Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, + parse_and_remainder, write_hundreds, }; -use crate::{expect, try_opt}; use crate::{FixedOffset, TimeDelta, Timelike}; +use crate::{expect, try_opt}; #[cfg(feature = "serde")] mod serde; diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index e7382bed1d..f32f0f05e9 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -10,7 +10,7 @@ use core::str::FromStr; use rkyv::{Archive, Deserialize, Serialize}; use super::{MappedLocalTime, Offset, TimeZone}; -use crate::format::{scan, ParseError, OUT_OF_RANGE}; +use crate::format::{OUT_OF_RANGE, ParseError, scan}; use crate::naive::{NaiveDate, NaiveDateTime}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 611fe18d8d..a1ba3fac65 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -11,9 +11,9 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{MappedLocalTime, TimeZone}; -use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::Date; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::{DateTime, Utc}; #[cfg(unix)] @@ -274,9 +274,9 @@ fn lookup_with_dst_transitions( #[cfg(test)] mod tests { use super::Local; - #[cfg(windows)] - use crate::offset::local::{lookup_with_dst_transitions, Transition}; use crate::offset::TimeZone; + #[cfg(windows)] + use crate::offset::local::{Transition, lookup_with_dst_transitions}; use crate::{Datelike, Days, Utc}; #[cfg(windows)] use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime}; diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 509c4fef99..84c99063c1 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -3,9 +3,9 @@ use std::iter; use std::num::ParseIntError; use std::str::{self, FromStr}; +use super::Error; use super::rule::TransitionRule; use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition}; -use super::Error; pub(super) fn parse(bytes: &[u8]) -> Result { let mut cursor = Cursor::new(bytes); @@ -14,7 +14,7 @@ pub(super) fn parse(bytes: &[u8]) -> Result { Version::V1 => match cursor.is_empty() { true => (state, None), false => { - return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")) + return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")); } }, Version::V2 | Version::V3 => { diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index a99fe253af..d428395db0 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -1,7 +1,7 @@ use super::parser::Cursor; use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; use super::{ - Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, + CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, Error, SECONDS_PER_DAY, }; use crate::{Datelike, NaiveDateTime}; @@ -39,7 +39,7 @@ impl TransitionRule { Some(&b',') => std_offset - 3600, Some(_) => parse_offset(&mut cursor)?, None => { - return Err(Error::UnsupportedTzString("DST start and end rules must be provided")) + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); } }; @@ -219,11 +219,7 @@ impl AlternateTime { } }; - if is_dst { - Ok(&self.dst) - } else { - Ok(&self.std) - } + if is_dst { Ok(&self.dst) } else { Ok(&self.std) } } fn find_local_time_type_from_local( diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index d33608d4aa..61d4498a94 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -1,13 +1,13 @@ //! Types related to a time zone. -use super::rule::{AlternateTime, TransitionRule}; -use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY}; -use crate::NaiveDateTime; use std::fs::{self, File}; use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::{cmp::Ordering, fmt, str}; +use super::rule::{AlternateTime, TransitionRule}; +use super::{DAYS_PER_WEEK, Error, SECONDS_PER_DAY, parser}; +use crate::NaiveDateTime; #[cfg(target_env = "ohos")] use crate::offset::local::tz_info::parser::Cursor; diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 100b1f1eef..dcab6c44e6 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -14,7 +14,7 @@ use std::ptr; use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; -use crate::offset::local::{lookup_with_dst_transitions, Transition}; +use crate::offset::local::{Transition, lookup_with_dst_transitions}; use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; // We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates @@ -213,7 +213,7 @@ fn naive_date_time_from_system_time( #[cfg(test)] mod tests { use crate::offset::local::win_bindings::{ - SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, FILETIME, SYSTEMTIME, + FILETIME, SYSTEMTIME, SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, }; use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta}; use crate::{Datelike, TimeZone, Timelike}; diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 346f809732..09f6821dce 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -20,9 +20,9 @@ use core::fmt; -use crate::format::{parse, ParseResult, Parsed, StrftimeItems}; -use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; +use crate::format::{ParseResult, Parsed, StrftimeItems, parse}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::{Date, DateTime}; diff --git a/src/round.rs b/src/round.rs index 6751562a62..f5ca2d0634 100644 --- a/src/round.rs +++ b/src/round.rs @@ -365,8 +365,8 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; - use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::Timelike; + use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::{DateTime, NaiveDate}; #[test] diff --git a/src/time_delta.rs b/src/time_delta.rs index 3eb041ad57..d891d8b6b2 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -301,22 +301,14 @@ impl TimeDelta { /// Returns the total number of whole seconds in the `TimeDelta`. pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. - if self.secs < 0 && self.nanos > 0 { - self.secs + 1 - } else { - self.secs - } + if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } } /// Returns the number of nanoseconds such that /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the `TimeDelta`. pub const fn subsec_nanos(&self) -> i32 { - if self.secs < 0 && self.nanos > 0 { - self.nanos - NANOS_PER_SEC - } else { - self.nanos - } + if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } } /// Returns the total number of whole milliseconds in the `TimeDelta`. @@ -642,7 +634,7 @@ impl arbitrary::Arbitrary<'_> for TimeDelta { #[cfg(feature = "serde")] mod serde { use super::TimeDelta; - use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; impl Serialize for TimeDelta { fn serialize(&self, serializer: S) -> Result { @@ -683,7 +675,7 @@ mod serde { #[cfg(test)] mod tests { use super::OutOfRangeError; - use super::{TimeDelta, MAX, MIN}; + use super::{MAX, MIN, TimeDelta}; use crate::expect; use core::time::Duration; @@ -803,10 +795,12 @@ mod tests { fn test_duration_milliseconds_max_overflow() { // Here we ensure that trying to add one millisecond to the maximum storable // value will fail. - assert!(TimeDelta::try_milliseconds(i64::MAX) - .unwrap() - .checked_add(&TimeDelta::try_milliseconds(1).unwrap()) - .is_none()); + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); } #[test] @@ -826,10 +820,12 @@ mod tests { fn test_duration_milliseconds_min_underflow() { // Here we ensure that trying to subtract one millisecond from the minimum // storable value will fail. - assert!(TimeDelta::try_milliseconds(-i64::MAX) - .unwrap() - .checked_sub(&TimeDelta::try_milliseconds(1).unwrap()) - .is_none()); + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); } #[test] @@ -905,10 +901,12 @@ mod tests { ); // Here we ensure that trying to add one microsecond to the maximum storable // value will fail. - assert!(TimeDelta::try_milliseconds(i64::MAX) - .unwrap() - .checked_add(&TimeDelta::microseconds(1)) - .is_none()); + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::microseconds(1)) + .is_none() + ); } #[test] fn test_duration_microseconds_min_allowed() { @@ -944,10 +942,12 @@ mod tests { ); // Here we ensure that trying to subtract one microsecond from the minimum // storable value will fail. - assert!(TimeDelta::try_milliseconds(-i64::MAX) - .unwrap() - .checked_sub(&TimeDelta::microseconds(1)) - .is_none()); + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::microseconds(1)) + .is_none() + ); } #[test] @@ -1009,10 +1009,12 @@ mod tests { ); // Here we ensure that trying to add one nanosecond to the maximum storable // value will fail. - assert!(TimeDelta::try_milliseconds(i64::MAX) - .unwrap() - .checked_add(&TimeDelta::nanoseconds(1)) - .is_none()); + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::nanoseconds(1)) + .is_none() + ); } #[test] @@ -1049,10 +1051,12 @@ mod tests { ); // Here we ensure that trying to subtract one nanosecond from the minimum // storable value will fail. - assert!(TimeDelta::try_milliseconds(-i64::MAX) - .unwrap() - .checked_sub(&TimeDelta::nanoseconds(1)) - .is_none()); + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::nanoseconds(1)) + .is_none() + ); } #[test] diff --git a/src/traits.rs b/src/traits.rs index e582093b0b..0771b5c1a5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -37,11 +37,7 @@ pub trait Datelike: Sized { #[inline] fn year_ce(&self) -> (bool, u32) { let year = self.year(); - if year < 1 { - (false, (1 - year) as u32) - } else { - (true, year as u32) - } + if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) } } /// Returns the month number starting from 1. diff --git a/src/weekday.rs b/src/weekday.rs index 92644e8f6a..d2ffa4b624 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -161,11 +161,7 @@ impl Weekday { pub const fn days_since(&self, other: Weekday) -> u32 { let lhs = *self as u32; let rhs = other as u32; - if lhs < rhs { - 7 + lhs - rhs - } else { - lhs - rhs - } + if lhs < rhs { 7 + lhs - rhs } else { lhs - rhs } } } @@ -344,8 +340,8 @@ mod tests { #[test] #[cfg(feature = "serde")] fn test_serde_serialize() { - use serde_json::to_string; use Weekday::*; + use serde_json::to_string; let cases: Vec<(Weekday, &str)> = vec![ (Mon, "\"Mon\""), @@ -366,8 +362,8 @@ mod tests { #[test] #[cfg(feature = "serde")] fn test_serde_deserialize() { - use serde_json::from_str; use Weekday::*; + use serde_json::from_str; let cases: Vec<(&str, Weekday)> = vec![ ("\"mon\"", Mon), From 1f345fd7f9ae4f3aa0b32775289e6791f34f9628 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 21 Feb 2025 10:35:37 +0100 Subject: [PATCH 901/999] Switch to 2024 idiom --- src/format/parsed.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index b6a5291390..c9fdd67953 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -175,11 +175,12 @@ pub struct Parsed { /// and if it is empty, set `old` to `new` as well. #[inline] fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<()> { - if let Some(ref old) = *old { - if *old == new { Ok(()) } else { Err(IMPOSSIBLE) } - } else { - *old = Some(new); - Ok(()) + match old { + Some(old) if *old != new => Err(IMPOSSIBLE), + _ => { + *old = Some(new); + Ok(()) + } } } From d0f8b599b7bca43f2676611a97392fee4743ea78 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Feb 2025 13:58:17 +0100 Subject: [PATCH 902/999] Restrict MSRV to check --lib --- .github/workflows/test.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86c44e26a0..a57b3dd3f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,15 +39,7 @@ jobs: # run --lib and --doc to avoid the long running integration tests # which are run elsewhere - run: | - cargo test --lib \ - --features \ - unstable-locales,wasmbind,oldtime,clock,winapi,serde \ - --color=always -- --color=always - - run: | - cargo test --doc \ - --features \ - unstable-locales,wasmbind,oldtime,clock,winapi,serde \ - --color=always -- --color=always + cargo check --lib --features unstable-locales,wasmbind,oldtime,clock,winapi,serde rust_versions: strategy: From bf1973ccc776695b18ac28a9168a07e85c130eb1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Feb 2025 13:47:36 +0100 Subject: [PATCH 903/999] Use similar-asserts to show bindgen diff --- Cargo.toml | 1 + tests/win_bindings.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5dc8aee5bd..78d79fb826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ android-tzdata = { version = "0.1.1", optional = true } [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } +similar-asserts = { version = "1.6.1" } bincode = { version = "1.3.0" } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index 053239fe19..ea8913bb82 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -14,7 +14,14 @@ fn gen_bindings() { // Check the output is the same as before. // Depending on the git configuration the file may have been checked out with `\r\n` newlines or // with `\n`. Compare line-by-line to ignore this difference. - let new = fs::read_to_string(output).unwrap(); + let mut new = fs::read_to_string(output).unwrap(); + if existing.contains("\r\n") && !new.contains("\r\n") { + new = new.replace("\n", "\r\n"); + } else if !existing.contains("\r\n") && new.contains("\r\n") { + new = new.replace("\r\n", "\n"); + } + + similar_asserts::assert_eq!(existing, new); if !new.lines().eq(existing.lines()) { panic!("generated file `{}` is changed.", output); } From 07216ae8fdc29073969920cb4957793dd47565e4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Feb 2025 13:47:49 +0100 Subject: [PATCH 904/999] Upgrade to windows-bindgen 0.60 --- Cargo.toml | 6 +++--- src/offset/local/win_bindings.rs | 8 ++++---- tests/win_bindings.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 78d79fb826..8c3d312571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ name = "chrono" default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] -winapi = ["windows-targets"] +winapi = ["windows-link"] std = ["alloc"] clock = ["winapi", "iana-time-zone", "android-tzdata", "now"] now = ["std"] @@ -49,10 +49,10 @@ wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] -windows-targets = { version = "0.53", optional = true } +windows-link = { version = "0.1", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.59" } # MSRV is 1.74 +windows-bindgen = { version = "0.60" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index f2def25e9c..b8e6b6d2a2 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,9 +1,9 @@ #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] -windows_targets::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); -windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); -windows_targets::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); -windows_targets::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); pub type BOOL = i32; #[repr(C)] #[derive(Clone, Copy)] diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index ea8913bb82..d3c2bc3ae3 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -9,7 +9,7 @@ fn gen_bindings() { let output = "src/offset/local/win_bindings.rs"; let existing = fs::read_to_string(output).unwrap(); - bindgen(["--etc", input]); + bindgen(["--no-deps", "--etc", input]); // Check the output is the same as before. // Depending on the git configuration the file may have been checked out with `\r\n` newlines or From 6d29c8abe7d202d329fad7e59e284f6ee79c15b1 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Mon, 24 Feb 2025 19:37:53 +0100 Subject: [PATCH 905/999] Add quarter (%q) date string specifier GNU date supports %q as a date string specifier. This adds support for that in chrono. This is needed by uutils/coreutils for compability. --- src/format/formatting.rs | 2 ++ src/format/mod.rs | 2 ++ src/format/parse.rs | 14 +++++++--- src/format/parsed.rs | 53 ++++++++++++++++++++++++++++++++++++- src/format/strftime.rs | 3 +++ src/naive/date/tests.rs | 4 ++- src/naive/datetime/tests.rs | 2 +- src/traits.rs | 8 ++++++ tests/dateutils.rs | 2 +- 9 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 1b7df6db0c..e27b764062 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -185,6 +185,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { (IsoYearMod100, Some(d), _) => { write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad) } + (Quarter, Some(d), _) => write_one(w, d.quarter() as u8), (Month, Some(d), _) => write_two(w, d.month() as u8, pad), (Day, Some(d), _) => write_two(w, d.day() as u8, pad), (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad), @@ -657,6 +658,7 @@ mod tests { let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); + assert_eq!(d.format("%q").to_string(), "1"); assert_eq!(d.format("%d,%e").to_string(), "04, 4"); assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); diff --git a/src/format/mod.rs b/src/format/mod.rs index fb608cc8a7..241be7a1d9 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -115,6 +115,8 @@ pub enum Numeric { IsoYearDiv100, /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative. IsoYearMod100, + /// Quarter (FW=PW=1). + Quarter, /// Month (FW=PW=2). Month, /// Day of the month (FW=PW=2). diff --git a/src/format/parse.rs b/src/format/parse.rs index 91a586548d..40b5b05246 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -343,6 +343,7 @@ where IsoYear => (4, true, Parsed::set_isoyear), IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), + Quarter => (1, false, Parsed::set_quarter), Month => (2, false, Parsed::set_month), Day => (2, false, Parsed::set_day), WeekFromSun => (2, false, Parsed::set_week_from_sun), @@ -819,9 +820,16 @@ mod tests { parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), ); check( - "1 2 3 4 5", - &[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)], - parsed!(month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), + "1 1 2 3 4 5", + &[ + num(Quarter), + num(Month), + num(Day), + num(WeekFromSun), + num(NumDaysFromSun), + num(IsoWeek), + ], + parsed!(quarter: 1, month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), ); check( "6 7 89 01", diff --git a/src/format/parsed.rs b/src/format/parsed.rs index c9fdd67953..83975cb44b 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -140,6 +140,8 @@ pub struct Parsed { #[doc(hidden)] pub isoyear_mod_100: Option, #[doc(hidden)] + pub quarter: Option, + #[doc(hidden)] pub month: Option, #[doc(hidden)] pub week_from_sun: Option, @@ -304,6 +306,23 @@ impl Parsed { set_if_consistent(&mut self.isoyear_mod_100, value as i32) } + /// Set the [`quarter`](Parsed::quarter) field to the given value. + /// + /// Quarter 1 starts in January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-4. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_quarter(&mut self, value: i64) -> ParseResult<()> { + if !(1..=4).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.quarter, value as u32) + } + /// Set the [`month`](Parsed::month) field to the given value. /// /// # Errors @@ -698,7 +717,15 @@ impl Parsed { (_, _, _) => return Err(NOT_ENOUGH), }; - if verified { Ok(parsed_date) } else { Err(IMPOSSIBLE) } + if !verified { + return Err(IMPOSSIBLE); + } else if let Some(parsed) = self.quarter { + if parsed != parsed_date.quarter() { + return Err(IMPOSSIBLE); + } + } + + Ok(parsed_date) } /// Returns a parsed naive time out of given fields. @@ -1013,6 +1040,14 @@ impl Parsed { self.isoyear_mod_100 } + /// Get the `quarter` field if set. + /// + /// See also [`set_quarter()`](Parsed::set_quarter). + #[inline] + pub fn quarter(&self) -> Option { + self.quarter + } + /// Get the `month` field if set. /// /// See also [`set_month()`](Parsed::set_month). @@ -1267,6 +1302,11 @@ mod tests { assert!(Parsed::new().set_isoyear_mod_100(99).is_ok()); assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE)); + assert_eq!(Parsed::new().set_quarter(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_quarter(1).is_ok()); + assert!(Parsed::new().set_quarter(4).is_ok()); + assert_eq!(Parsed::new().set_quarter(5), Err(OUT_OF_RANGE)); + assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE)); assert!(Parsed::new().set_month(1).is_ok()); assert!(Parsed::new().set_month(12).is_ok()); @@ -1425,6 +1465,17 @@ mod tests { assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE)); assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE)); + // quarters + assert_eq!(parse!(year: 2000, quarter: 1), Err(NOT_ENOUGH)); + assert_eq!(parse!(year: 2000, quarter: 1, month: 1, day: 1), ymd(2000, 1, 1)); + assert_eq!(parse!(year: 2000, quarter: 2, month: 4, day: 1), ymd(2000, 4, 1)); + assert_eq!(parse!(year: 2000, quarter: 3, month: 7, day: 1), ymd(2000, 7, 1)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 10, day: 1), ymd(2000, 10, 1)); + + // quarter: conflicting inputs + assert_eq!(parse!(year: 2000, quarter: 2, month: 3, day: 31), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 3, day: 31), Err(IMPOSSIBLE)); + // weekdates assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); diff --git a/src/format/strftime.rs b/src/format/strftime.rs index e2bd4f5b57..f0478b43ae 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -15,6 +15,7 @@ The following specifiers are available both to formatting and parsing. | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | +| `%q` | `1` | Quarter of year (1-4) | | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. | @@ -538,6 +539,7 @@ impl<'a> StrftimeItems<'a> { 'm' => num0(Month), 'n' => Space("\n"), 'p' => fixed(Fixed::UpperAmPm), + 'q' => num(Quarter), #[cfg(not(feature = "unstable-locales"))] 'r' => queue_from_slice!(T_FMT_AMPM), #[cfg(feature = "unstable-locales")] @@ -866,6 +868,7 @@ mod tests { 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("%q").to_string(), "3"); assert_eq!(dt.format("%m").to_string(), "07"); assert_eq!(dt.format("%b").to_string(), "Jul"); assert_eq!(dt.format("%B").to_string(), "July"); diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 5c944d59db..35c9da24e7 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -666,7 +666,7 @@ fn test_date_parse_from_str() { Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), + NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"), Ok(ymd(2015, 2, 2)) ); assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9))); @@ -674,6 +674,8 @@ fn test_date_parse_from_str() { assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter + assert_eq!( NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), NaiveDate::from_ymd_opt(2020, 1, 12), diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 33eb57bdaf..75a168a719 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -203,7 +203,7 @@ fn test_datetime_parse_from_str() { NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") .is_err() ); - assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); + assert!(NaiveDateTime::parse_from_str("2014-5-7 Q2 12:3456", "%Y-%m-%d Q%q %H:%M:%S").is_err()); assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient assert_eq!( NaiveDateTime::parse_from_str("1441497364", "%s"), diff --git a/src/traits.rs b/src/traits.rs index 0771b5c1a5..21ed88e9ed 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -40,6 +40,14 @@ pub trait Datelike: Sized { if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) } } + /// Returns the quarter number starting from 1. + /// + /// The return value ranges from 1 to 4. + #[inline] + fn quarter(&self) -> u32 { + (self.month() - 1).div_euclid(3) + 1 + } + /// Returns the month number starting from 1. /// /// The return value ranges from 1 to 12. diff --git a/tests/dateutils.rs b/tests/dateutils.rs index a0a445aa99..849abc72f9 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -110,7 +110,7 @@ fn try_verify_against_date_command() { #[cfg(target_os = "linux")] fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) { let required_format = - "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; + "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; // a%a - depends from localization // A%A - depends from localization // b%b - depends from localization From 2b7a28ebe9c6be2d81579732f40ce14c8f5e104c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 26 Feb 2025 09:25:59 +0100 Subject: [PATCH 906/999] Bump version to 0.4.40 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 96cbf16cdd..21c47e7cde 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-04-15 -version: 0.4.38 +date-released: 2025-02-26 +version: 0.4.40 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 8c3d312571..d5a6333993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.39" +version = "0.4.40" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From f0f807d1b5272428d46991543690eded79ff6069 Mon Sep 17 00:00:00 2001 From: Georges Goetz Date: Wed, 26 Feb 2025 19:22:44 +0000 Subject: [PATCH 907/999] Improve documentation for subsec_nanos() and add a test for the method. Co-authored-by: Micha White --- src/time_delta.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index d891d8b6b2..f91f5aace4 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -304,8 +304,10 @@ impl TimeDelta { if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } } - /// Returns the number of nanoseconds such that - /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of + /// Returns the number of nanoseconds in the fractional part of the duration. + /// + /// This is the number of nanoseconds such that + /// `subsec_nanos() + num_seconds() * 1_000_000_000` is the total number of /// nanoseconds in the `TimeDelta`. pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } @@ -768,6 +770,15 @@ mod tests { let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); } + #[test] + fn test_duration_subsec_nanos() { + assert_eq!(TimeDelta::zero().subsec_nanos(), 0); + assert_eq!(TimeDelta::nanoseconds(1).subsec_nanos(), 1); + assert_eq!(TimeDelta::nanoseconds(-1).subsec_nanos(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_nanos(), 0); + assert_eq!(TimeDelta::nanoseconds(1_000_000_001).subsec_nanos(), 1); + } + #[test] fn test_duration_num_milliseconds() { assert_eq!(TimeDelta::zero().num_milliseconds(), 0); From 042109f4f8c18e0f507a2db74d602a6075fc04ad Mon Sep 17 00:00:00 2001 From: Georges Goetz Date: Wed, 26 Feb 2025 19:23:18 +0000 Subject: [PATCH 908/999] Add subsec_millis and subsec_micros methods for TimeDelta. Co-authored-by: Micha White --- src/time_delta.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/time_delta.rs b/src/time_delta.rs index f91f5aace4..e371f2eab7 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -313,6 +313,24 @@ impl TimeDelta { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } } + /// Returns the number of microseconds in the fractional part of the duration. + /// + /// This is the number of microseconds such that + /// `subsec_micros() + num_seconds() * 1_000_000` is the truncated number of + /// microseconds in the duration. + pub const fn subsec_micros(&self) -> i32 { + self.subsec_nanos() / NANOS_PER_MICRO + } + + /// Returns the number of milliseconds in the fractional part of the duration. + /// + /// This is the number of milliseconds such that + /// `subsec_millis() + num_seconds() * 1_000` is the truncated number of + /// milliseconds in the duration. + pub const fn subsec_millis(&self) -> i32 { + self.subsec_nanos() / NANOS_PER_MILLI + } + /// Returns the total number of whole milliseconds in the `TimeDelta`. pub const fn num_milliseconds(&self) -> i64 { // A proper TimeDelta will not overflow, because MIN and MAX are defined such @@ -779,6 +797,26 @@ mod tests { assert_eq!(TimeDelta::nanoseconds(1_000_000_001).subsec_nanos(), 1); } + #[test] + fn test_duration_subsec_micros() { + assert_eq!(TimeDelta::zero().subsec_micros(), 0); + assert_eq!(TimeDelta::microseconds(1).subsec_micros(), 1); + assert_eq!(TimeDelta::microseconds(-1).subsec_micros(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_micros(), 0); + assert_eq!(TimeDelta::microseconds(1_000_001).subsec_micros(), 1); + assert_eq!(TimeDelta::nanoseconds(1_000_001_999).subsec_micros(), 1); + } + + #[test] + fn test_duration_subsec_millis() { + assert_eq!(TimeDelta::zero().subsec_millis(), 0); + assert_eq!(TimeDelta::milliseconds(1).subsec_millis(), 1); + assert_eq!(TimeDelta::milliseconds(-1).subsec_millis(), -1); + assert_eq!(TimeDelta::seconds(1).subsec_millis(), 0); + assert_eq!(TimeDelta::milliseconds(1_001).subsec_millis(), 1); + assert_eq!(TimeDelta::microseconds(1_001_999).subsec_millis(), 1); + } + #[test] fn test_duration_num_milliseconds() { assert_eq!(TimeDelta::zero().num_milliseconds(), 0); From 2115206bc3b0c520fe6bcc041aa724f3ad5286ea Mon Sep 17 00:00:00 2001 From: Robert Bastian Date: Thu, 27 Feb 2025 18:26:01 +0100 Subject: [PATCH 909/999] Deprecate NaiveDateTime::UNIX_EPOCH --- src/datetime/mod.rs | 3 ++- src/naive/datetime/mod.rs | 25 ++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ccb6c953b2..51b416c583 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -858,7 +858,8 @@ impl DateTime { } /// The Unix Epoch, 1970-01-01 00:00:00 UTC. - pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; + pub const UNIX_EPOCH: Self = + expect(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN).and_utc(); } impl Default for DateTime { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index b634f8f432..e5c871148a 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -944,9 +944,13 @@ impl NaiveDateTime { /// The maximum possible `NaiveDateTime`. pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; - /// The Unix Epoch, 1970-01-01 00:00:00. - pub const UNIX_EPOCH: Self = - expect(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); + /// The datetime of the Unix Epoch, 1970-01-01 00:00:00. + /// + /// Note that while this may look like the UNIX epoch, it is missing the + /// time zone. The actual UNIX epoch cannot be expressed by this type, + /// however it is available as [`DateTime::UNIX_EPOCH`]. + #[deprecated(since = "0.4.41", note = "use `DateTime::UNIX_EPOCH` instead")] + pub const UNIX_EPOCH: Self = DateTime::UNIX_EPOCH.naive_utc(); } impl From for NaiveDateTime { @@ -2134,18 +2138,13 @@ impl str::FromStr for NaiveDateTime { } } -/// The default value for a NaiveDateTime is one with epoch 0 -/// that is, 1st of January 1970 at 00:00:00. -/// -/// # Example +/// The default value for a NaiveDateTime is 1st of January 1970 at 00:00:00. /// -/// ```rust -/// use chrono::NaiveDateTime; -/// -/// assert_eq!(NaiveDateTime::default(), NaiveDateTime::UNIX_EPOCH); -/// ``` +/// Note that while this may look like the UNIX epoch, it is missing the +/// time zone. The actual UNIX epoch cannot be expressed by this type, +/// however it is available as [`DateTime::UNIX_EPOCH`]. impl Default for NaiveDateTime { fn default() -> Self { - Self::UNIX_EPOCH + DateTime::UNIX_EPOCH.naive_local() } } From cc6801d3dc7bd52f95c6d1029c2c9252d35a1060 Mon Sep 17 00:00:00 2001 From: Georges Goetz Date: Fri, 28 Feb 2025 09:31:46 -0800 Subject: [PATCH 910/999] Group subsec duration methods with their corresponding whole methods. This makes for more logical grouping of the methods, and opens up a good landing spot for `as_seconds_f32` and `as_seconds`f64` next to `num_seconds`. --- src/time_delta.rs | 52 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/time_delta.rs b/src/time_delta.rs index e371f2eab7..70f0d4a246 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -304,22 +304,14 @@ impl TimeDelta { if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } } - /// Returns the number of nanoseconds in the fractional part of the duration. - /// - /// This is the number of nanoseconds such that - /// `subsec_nanos() + num_seconds() * 1_000_000_000` is the total number of - /// nanoseconds in the `TimeDelta`. - pub const fn subsec_nanos(&self) -> i32 { - if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } - } - - /// Returns the number of microseconds in the fractional part of the duration. - /// - /// This is the number of microseconds such that - /// `subsec_micros() + num_seconds() * 1_000_000` is the truncated number of - /// microseconds in the duration. - pub const fn subsec_micros(&self) -> i32 { - self.subsec_nanos() / NANOS_PER_MICRO + /// Returns the total number of whole milliseconds in the `TimeDelta`. + pub const fn num_milliseconds(&self) -> i64 { + // A proper TimeDelta will not overflow, because MIN and MAX are defined such + // that the range is within the bounds of an i64, from -i64::MAX through to + // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. + let secs_part = self.num_seconds() * MILLIS_PER_SEC; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; + secs_part + nanos_part as i64 } /// Returns the number of milliseconds in the fractional part of the duration. @@ -331,16 +323,6 @@ impl TimeDelta { self.subsec_nanos() / NANOS_PER_MILLI } - /// Returns the total number of whole milliseconds in the `TimeDelta`. - pub const fn num_milliseconds(&self) -> i64 { - // A proper TimeDelta will not overflow, because MIN and MAX are defined such - // that the range is within the bounds of an i64, from -i64::MAX through to - // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. - let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; - secs_part + nanos_part as i64 - } - /// Returns the total number of whole microseconds in the `TimeDelta`, /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { @@ -349,6 +331,15 @@ impl TimeDelta { secs_part.checked_add(nanos_part as i64) } + /// Returns the number of microseconds in the fractional part of the duration. + /// + /// This is the number of microseconds such that + /// `subsec_micros() + num_seconds() * 1_000_000` is the truncated number of + /// microseconds in the duration. + pub const fn subsec_micros(&self) -> i32 { + self.subsec_nanos() / NANOS_PER_MICRO + } + /// Returns the total number of whole nanoseconds in the `TimeDelta`, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { @@ -357,6 +348,15 @@ impl TimeDelta { secs_part.checked_add(nanos_part as i64) } + /// Returns the number of nanoseconds in the fractional part of the duration. + /// + /// This is the number of nanoseconds such that + /// `subsec_nanos() + num_seconds() * 1_000_000_000` is the total number of + /// nanoseconds in the `TimeDelta`. + pub const fn subsec_nanos(&self) -> i32 { + if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } + } + /// Add two `TimeDelta`s, returning `None` if overflow occurred. #[must_use] pub const fn checked_add(&self, rhs: &TimeDelta) -> Option { From fa957cc8dcd4c5a2233108f94a287aab6dedf0a9 Mon Sep 17 00:00:00 2001 From: Georges Goetz Date: Fri, 28 Feb 2025 09:56:29 -0800 Subject: [PATCH 911/999] Implement utility functions for converting TimeDelta to fractional seconds. Add `as_seconds_f64` and `as_seconds_f32` methods to `TimeDelta` for converting durations to a fractional number of seconds, with single or double precision. --- src/time_delta.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/time_delta.rs b/src/time_delta.rs index 70f0d4a246..0439778d42 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -304,6 +304,16 @@ impl TimeDelta { if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } } + /// Returns the fractional number of seconds in the `TimeDelta`. + pub fn as_seconds_f64(self) -> f64 { + self.secs as f64 + self.nanos as f64 / NANOS_PER_SEC as f64 + } + + /// Returns the fractional number of seconds in the `TimeDelta`. + pub fn as_seconds_f32(self) -> f32 { + self.secs as f32 + self.nanos as f32 / NANOS_PER_SEC as f32 + } + /// Returns the total number of whole milliseconds in the `TimeDelta`. pub const fn num_milliseconds(&self) -> i64 { // A proper TimeDelta will not overflow, because MIN and MAX are defined such @@ -788,6 +798,32 @@ mod tests { let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); } + #[test] + fn test_duration_as_seconds_f64() { + assert_eq!(TimeDelta::seconds(1).as_seconds_f64(), 1.0); + assert_eq!(TimeDelta::seconds(-1).as_seconds_f64(), -1.0); + assert_eq!(TimeDelta::seconds(100).as_seconds_f64(), 100.0); + assert_eq!(TimeDelta::seconds(-100).as_seconds_f64(), -100.0); + + assert_eq!(TimeDelta::milliseconds(500).as_seconds_f64(), 0.5); + assert_eq!(TimeDelta::milliseconds(-500).as_seconds_f64(), -0.5); + assert_eq!(TimeDelta::milliseconds(1_500).as_seconds_f64(), 1.5); + assert_eq!(TimeDelta::milliseconds(-1_500).as_seconds_f64(), -1.5); + } + + #[test] + fn test_duration_as_seconds_f32() { + assert_eq!(TimeDelta::seconds(1).as_seconds_f32(), 1.0); + assert_eq!(TimeDelta::seconds(-1).as_seconds_f32(), -1.0); + assert_eq!(TimeDelta::seconds(100).as_seconds_f32(), 100.0); + assert_eq!(TimeDelta::seconds(-100).as_seconds_f32(), -100.0); + + assert_eq!(TimeDelta::milliseconds(500).as_seconds_f32(), 0.5); + assert_eq!(TimeDelta::milliseconds(-500).as_seconds_f32(), -0.5); + assert_eq!(TimeDelta::milliseconds(1_500).as_seconds_f32(), 1.5); + assert_eq!(TimeDelta::milliseconds(-1_500).as_seconds_f32(), -1.5); + } + #[test] fn test_duration_subsec_nanos() { assert_eq!(TimeDelta::zero().subsec_nanos(), 0); From 7ebf64c2d15b3a424588bfbebefa9e441ea083a4 Mon Sep 17 00:00:00 2001 From: jimmycathy Date: Sun, 16 Mar 2025 13:51:26 +0800 Subject: [PATCH 912/999] chore: fix some comments Signed-off-by: jimmycathy --- src/datetime/mod.rs | 2 +- src/format/parsed.rs | 2 +- src/naive/time/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 51b416c583..3e2c273af0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -976,7 +976,7 @@ impl DateTime { /// [Appendix A.5] /// - Single letter 'military' time zone names are parsed as a `-0000` offset. /// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because - /// the meaning is now ambiguous, the standard says they should be be considered as `-0000` + /// the meaning is now ambiguous, the standard says they should be considered as `-0000` /// unless there is out-of-band information confirming their meaning. /// The exception is `Z`, which remains identical to `+0000`. /// diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 83975cb44b..fd5008f159 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -93,7 +93,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// # Ok::<(), chrono::ParseError>(()) /// ``` /// -/// The same using chrono's build-in parser for RFC 2822 (the [RFC2822 formatting item]) and +/// The same using chrono's built-in parser for RFC 2822 (the [RFC2822 formatting item]) and /// [`format::parse()`] showing how to inspect a field on failure. /// /// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index a0b92836f1..c1802bce13 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -95,7 +95,7 @@ mod tests; /// Practically speaking, though, by the time of the first leap second on 1972-06-30, /// every time zone offset around the world has standardized to the 5-minute alignment. /// -/// ## Date And Time Arithmetics +/// ## Date And Time Arithmetic /// /// As a concrete example, let's assume that `03:00:60` and `04:00:60` are leap seconds. /// In reality, of course, leap seconds are separated by at least 6 months. From 39fb201c08f0e1809c725b542415e936cba48ebd Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Sat, 8 Mar 2025 19:11:45 -0700 Subject: [PATCH 913/999] Add `num_days_in_month` method to `Datelike` trait --- src/traits.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index 21ed88e9ed..ada73b2258 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{IsoWeek, Weekday}; +use crate::{IsoWeek, Month, Weekday}; /// The common set of methods for date component. /// @@ -265,6 +265,18 @@ pub trait Datelike: Sized { ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); ndays + self.ordinal() as i32 } + + /// Get the length in days of the month + fn num_days_in_month(&self) -> u8 { + use num_traits::FromPrimitive; + // The value returned from `self.month()` is guaranteed to be in the + // range [1,12], which will never result in a `None` value here. + let month = Month::from_u32(self.month()).unwrap(); + // `Month::num_days` will only return `None` if the provided year is out + // of range. Since we are passing it directly from a verified date, we + // know it is in range, and the result will never be `None`. + month.num_days(self.year()).unwrap() + } } /// The common set of methods for time component. @@ -390,4 +402,14 @@ mod tests { ); } } + + #[test] + fn test_num_days_in_month() { + let feb_leap_year = NaiveDate::from_ymd_opt(2004, 2, 1).unwrap(); + assert_eq!(feb_leap_year.num_days_in_month(), 29); + let feb = feb_leap_year.with_year(2005).unwrap(); + assert_eq!(feb.num_days_in_month(), 28); + let march = feb.with_month(3).unwrap(); + assert_eq!(march.num_days_in_month(), 31); + } } From 4f4e95bee8f7276356c9106fef40462df1546b61 Mon Sep 17 00:00:00 2001 From: Ruslan Fadeev Date: Sun, 16 Mar 2025 01:29:20 +0400 Subject: [PATCH 914/999] add WeekdaySet - a collection of Weekday --- src/lib.rs | 3 + src/weekdays.rs | 515 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 518 insertions(+) create mode 100644 src/weekdays.rs diff --git a/src/lib.rs b/src/lib.rs index a7c603a3e9..e75e22dcca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -580,6 +580,9 @@ mod weekday; pub use weekday::ParseWeekdayError; pub use weekday::Weekday; +mod weekdays; +pub use weekdays::WeekdaySet; + mod month; #[doc(no_inline)] pub use month::ParseMonthError; diff --git a/src/weekdays.rs b/src/weekdays.rs new file mode 100644 index 0000000000..78c27d321a --- /dev/null +++ b/src/weekdays.rs @@ -0,0 +1,515 @@ +use core::{ + fmt::{self, Debug}, + iter::FusedIterator, + ops::Not, +}; + +use crate::Weekday; + +/// A collection of `Weekday`s stored as a single byte. +/// +/// This type is `Copy` and provides efficient set-like and slice-like operations. +/// Many operations are `const` as well. +/// +/// Implemented as a bitmask where bits 1-7 correspond to Monday-Sunday. +#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0. + +impl WeekdaySet { + /// Returns `Some(day)` if this collection contains exactly one day. + /// + /// Returns `None` otherwise. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).single_day(), Some(Mon)); + /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).single_day(), None); + /// assert_eq!(WeekdaySet::EMPTY.single_day(), None); + /// assert_eq!(WeekdaySet::ALL.single_day(), None); + /// ``` + pub const fn single_day(self) -> Option { + match self { + Self(0b000_0001) => Some(Weekday::Mon), + Self(0b000_0010) => Some(Weekday::Tue), + Self(0b000_0100) => Some(Weekday::Wed), + Self(0b000_1000) => Some(Weekday::Thu), + Self(0b001_0000) => Some(Weekday::Fri), + Self(0b010_0000) => Some(Weekday::Sat), + Self(0b100_0000) => Some(Weekday::Sun), + _ => None, + } + } + + /// Returns `true` if `other` contains all days in `self`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert!(WeekdaySet::single(Mon).is_subset(WeekdaySet::ALL)); + /// assert!(!WeekdaySet::single(Mon).is_subset(WeekdaySet::EMPTY)); + /// assert!(WeekdaySet::EMPTY.is_subset(WeekdaySet::single(Mon))); + /// ``` + pub const fn is_subset(self, other: Self) -> bool { + self.intersection(other).0 == self.0 + } + + /// Adds a day to the collection. + /// + /// Returns `true` if the day was new to the collection. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let mut weekdays = WeekdaySet::single(Mon); + /// assert!(weekdays.insert(Tue)); + /// assert!(!weekdays.insert(Tue)); + /// ``` + pub fn insert(&mut self, day: Weekday) -> bool { + if self.contains(day) { + return false; + } + + self.0 |= Self::single(day).0; + true + } + + /// Removes a day from the collection. + /// + /// Returns `true` if the collection did contain the day. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let mut weekdays = WeekdaySet::single(Mon); + /// assert!(weekdays.remove(Mon)); + /// assert!(!weekdays.remove(Mon)); + /// ``` + pub fn remove(&mut self, day: Weekday) -> bool { + if self.contains(day) { + self.0 &= !Self::single(day).0; + return true; + } + + false + } + + /// Get the first day in the collection, starting from Monday. + /// + /// Returns `None` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).first(), Some(Mon)); + /// assert_eq!(WeekdaySet::single(Tue).first(), Some(Tue)); + /// assert_eq!(WeekdaySet::ALL.first(), Some(Mon)); + /// assert_eq!(WeekdaySet::EMPTY.first(), None); + /// ``` + pub const fn first(self) -> Option { + if self.is_empty() { + return None; + } + + // Find the first non-zero bit. + let bit = 1 << self.0.trailing_zeros(); + + Self(bit).single_day() + } + + /// Get the last day in the collection, starting from Sunday. + /// + /// Returns `None` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).last(), Some(Mon)); + /// assert_eq!(WeekdaySet::single(Sun).last(), Some(Sun)); + /// assert_eq!(WeekdaySet::from_array([Mon, Tue]).last(), Some(Tue)); + /// assert_eq!(WeekdaySet::EMPTY.last(), None); + /// ``` + pub fn last(self) -> Option { + if self.is_empty() { + return None; + } + + // Find the last non-zero bit. + let bit = 1 << (7 - self.0.leading_zeros()); + + Self(bit).single_day() + } + + /// Split the collection in two at the given day. + /// + /// Returns a tuple `(before, after)`. `before` contains all days starting from Monday + /// up to but __not__ including `weekday`. `after` contains all days starting from `weekday` + /// up to and including Sunday. + /// + /// # Example + /// ```ignore + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let (before, after) = WeekdaySet::ALL.split_at(Fri); + /// assert_eq!(before, WeekdaySet::from_array([Mon, Tue, Wed, Thu])); + /// assert_eq!(after, WeekdaySet::from_array([Fri, Sat, Sun])); + /// ``` + const fn split_at(self, weekday: Weekday) -> (Self, Self) { + let days_after = 0b1000_0000 - Self::single(weekday).0; + let days_before = days_after ^ 0b0111_1111; + (Self(self.0 & days_before), Self(self.0 & days_after)) + } + + /// Iterate over the `Weekday`s in the collection, starting from a given day + /// and wrapping around from Sunday to Monday. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// let weekdays = WeekdaySet::from_array([Mon, Wed, Fri]); + /// let mut iter = weekdays.iter_from(Wed); + /// assert_eq!(iter.next(), Some(Wed)); + /// assert_eq!(iter.next(), Some(Fri)); + /// assert_eq!(iter.next(), Some(Mon)); + /// assert_eq!(iter.next(), None); + /// ``` + pub const fn iter_from(self, start: Weekday) -> WeekdaySetIterFrom { + WeekdaySetIterFrom { days: self, start } + } + + /// Returns days that are in both `self` and `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Tue)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::EMPTY), WeekdaySet::EMPTY); + /// ``` + pub const fn intersection(self, other: Self) -> Self { + Self(self.0 & other.0) + } + + /// Returns days that are in either `self` or `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::single(Mon)), WeekdaySet::ALL); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Returns days that are in `self` or `other` but not in both. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!( + /// WeekdaySet::ALL.symmetric_difference(WeekdaySet::single(Mon)), + /// WeekdaySet::from_array([Tue, Wed, Thu, Fri, Sat, Sun]), + /// ); + /// assert_eq!(WeekdaySet::ALL.symmetric_difference(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn symmetric_difference(self, other: Self) -> Self { + Self(self.0 ^ other.0) + } + + /// Returns days that are in `self` but not in `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Tue)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::EMPTY.difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// ``` + pub const fn difference(self, other: Self) -> Self { + Self(self.0 & !other.0) + } + + /// Returns `true` if the collection contains the given day. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert!(WeekdaySet::single(Mon).contains(Mon)); + /// assert!(WeekdaySet::from_array([Mon, Tue]).contains(Tue)); + /// assert!(!WeekdaySet::single(Mon).contains(Tue)); + /// ``` + pub const fn contains(self, day: Weekday) -> bool { + self.0 & Self::single(day).0 != 0 + } + + /// Returns `true` if the collection is empty. + /// + /// # Example + /// ``` + /// # use chrono::{Weekday, WeekdaySet}; + /// assert!(WeekdaySet::EMPTY.is_empty()); + /// assert!(!WeekdaySet::single(Weekday::Mon).is_empty()); + /// ``` + pub const fn is_empty(self) -> bool { + self.len() == 0 + } + /// Returns the number of days in the collection. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).len(), 1); + /// assert_eq!(WeekdaySet::from_array([Mon, Wed, Fri]).len(), 3); + /// assert_eq!(WeekdaySet::ALL.len(), 7); + /// ``` + pub const fn len(self) -> u8 { + self.0.count_ones() as u8 + } + + /// Create a `WeekdaySet` from a single `Weekday`. + pub const fn single(weekday: Weekday) -> Self { + match weekday { + Weekday::Mon => Self(0b000_0001), + Weekday::Tue => Self(0b000_0010), + Weekday::Wed => Self(0b000_0100), + Weekday::Thu => Self(0b000_1000), + Weekday::Fri => Self(0b001_0000), + Weekday::Sat => Self(0b010_0000), + Weekday::Sun => Self(0b100_0000), + } + } + + /// Create a `WeekdaySet` from an array of `Weekday`s. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::EMPTY, WeekdaySet::from_array([])); + /// assert_eq!(WeekdaySet::single(Mon), WeekdaySet::from_array([Mon])); + /// assert_eq!(WeekdaySet::ALL, WeekdaySet::from_array([Mon, Tue, Wed, Thu, Fri, Sat, Sun])); + /// ``` + pub const fn from_array(days: [Weekday; C]) -> Self { + let mut acc = Self::EMPTY; + let mut idx = 0; + while idx < days.len() { + acc.0 |= Self::single(days[idx]).0; + idx += 1; + } + acc + } + + /// An empty `WeekdaySet`. + pub const EMPTY: Self = Self(0b000_0000); + /// A `WeekdaySet` containing all seven `Weekday`s. + pub const ALL: Self = Self(0b111_1111); +} + +/// Print the underlying bitmask, padded to 7 bits. +/// +/// # Example +/// ``` +/// # use chrono::WeekdaySet; +/// use chrono::Weekday::*; +/// assert_eq!(format!("{:?}", WeekdaySet::single(Mon)), "WeekdaySet(0000001)"); +/// assert_eq!(format!("{:?}", WeekdaySet::single(Tue)), "WeekdaySet(0000010)"); +/// assert_eq!(format!("{:?}", WeekdaySet::ALL), "WeekdaySet(1111111)"); +/// ``` +impl Debug for WeekdaySet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WeekdaySet({:0>7b})", self.0) + } +} + +/// An iterator over a collection of weekdays, starting from a given day. +/// +/// See `WeekdaySet::iter_from`. +#[derive(Debug, Clone)] +pub struct WeekdaySetIterFrom { + pub days: WeekdaySet, + pub start: Weekday, +} + +impl Iterator for WeekdaySetIterFrom { + type Item = Weekday; + + fn next(&mut self) -> Option { + if self.days.is_empty() { + return None; + } + + // Split the collection in two at `start`. + // Look for the first day among the days after `start` first, including `start` itself. + // If there are no days after `start`, look for the first day among the days before `start`. + let (before, after) = self.days.split_at(self.start); + let days = if after.is_empty() { before } else { after }; + + let next = days.first().expect("the collection is not empty"); + self.days.remove(next); + Some(next) + } +} + +impl DoubleEndedIterator for WeekdaySetIterFrom { + fn next_back(&mut self) -> Option { + if self.days.is_empty() { + return None; + } + + // Split the collection in two at `start`. + // Look for the last day among the days before `start` first, NOT including `start` itself. + // If there are no days before `start`, look for the last day among the days after `start`. + let (before, after) = self.days.split_at(self.start); + let days = if before.is_empty() { after } else { before }; + + let next_back = days.last().expect("the collection is not empty"); + self.days.remove(next_back); + Some(next_back) + } +} + +impl ExactSizeIterator for WeekdaySetIterFrom { + fn len(&self) -> usize { + self.days.len().into() + } +} + +impl FusedIterator for WeekdaySetIterFrom {} + +/// Print the collection as a slice-like list of weekdays. +/// +/// # Example +/// ``` +/// # use chrono::WeekdaySet; +/// use chrono::Weekday::*; +/// assert_eq!("[]", WeekdaySet::EMPTY.to_string()); +/// assert_eq!("[Mon]", WeekdaySet::single(Mon).to_string()); +/// assert_eq!("[Mon, Fri, Sun]", WeekdaySet::from_array([Mon, Fri, Sun]).to_string()); +/// ``` +impl fmt::Display for WeekdaySet { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[")?; + let mut iter = self.iter_from(Weekday::Mon); + if let Some(first) = iter.next() { + write!(f, "{first}")?; + } + for weekday in iter { + write!(f, ", {weekday}")?; + } + write!(f, "]") + } +} + +impl Not for WeekdaySet { + type Output = Self; + + fn not(self) -> Self::Output { + Self(self.0 ^ 0b0111_1111) + } +} + +impl FromIterator for WeekdaySet { + fn from_iter>(iter: T) -> Self { + iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union) + } +} + +impl Not for Weekday { + type Output = WeekdaySet; + + fn not(self) -> Self::Output { + !WeekdaySet::single(self) + } +} + +#[cfg(test)] +mod tests { + use crate::Weekday; + + use super::WeekdaySet; + + impl WeekdaySet { + /// Iterate over all 128 possible sets, from `EMPTY` to `ALL`. + fn iter_all() -> impl Iterator { + (0b0000_0000..0b1000_0000).map(Self) + } + } + + /// Panics if the 8-th bit of `self` is not 0. + fn assert_8th_bit_invariant(days: WeekdaySet) { + assert!(days.0 & 0b1000_0000 == 0, "the 8-th bit of {days:?} is not 0"); + } + + #[test] + fn debug_prints_8th_bit_if_not_zero() { + assert_eq!(format!("{:?}", WeekdaySet(0b1000_0000)), "WeekdaySet(10000000)"); + } + + #[test] + fn bitwise_set_operations_preserve_8th_bit_invariant() { + for set1 in WeekdaySet::iter_all() { + for set2 in WeekdaySet::iter_all() { + assert_8th_bit_invariant(set1.union(set2)); + assert_8th_bit_invariant(set1.intersection(set2)); + assert_8th_bit_invariant(set1.symmetric_difference(set2)); + } + } + } + + #[test] + fn not_operation_preserves_8th_bit_invariant() { + for days in WeekdaySet::iter_all() { + assert_8th_bit_invariant(!days); + } + } + + /// Test `split_at` on all possible arguments. + #[test] + fn split_at_is_equivalent_to_iterating() { + use Weekday::*; + + // `split_at` is used in `iter_from`, so we must not iterate + // over all days with `WeekdaySet::ALL.iter_from(Mon)`. + const WEEK: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun]; + + for weekdays in WeekdaySet::iter_all() { + for split_day in WEEK { + let expected_before: WeekdaySet = WEEK + .into_iter() + .take_while(|&day| day != split_day) + .filter(|&day| weekdays.contains(day)) + .collect(); + let expected_after: WeekdaySet = WEEK + .into_iter() + .skip_while(|&day| day != split_day) + .filter(|&day| weekdays.contains(day)) + .collect(); + + assert_eq!( + (expected_before, expected_after), + weekdays.split_at(split_day), + "split_at({split_day}) failed for {weekdays}", + ); + } + } + } +} From cac71f3f4d95bb46fd3be9441bb88fc3438560ba Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 09:50:14 +0100 Subject: [PATCH 915/999] Rename weekdays module to weekday_set --- src/lib.rs | 4 ++-- src/{weekdays.rs => weekday_set.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{weekdays.rs => weekday_set.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index e75e22dcca..80e2fc41d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -580,8 +580,8 @@ mod weekday; pub use weekday::ParseWeekdayError; pub use weekday::Weekday; -mod weekdays; -pub use weekdays::WeekdaySet; +mod weekday_set; +pub use weekday_set::WeekdaySet; mod month; #[doc(no_inline)] diff --git a/src/weekdays.rs b/src/weekday_set.rs similarity index 100% rename from src/weekdays.rs rename to src/weekday_set.rs From cac4e8cf8d7dd51661c188c2666cd7074182571b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 09:52:00 +0100 Subject: [PATCH 916/999] Move WeekdaySet constructors to the top --- src/weekday_set.rs | 66 +++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index 78c27d321a..159408f3d5 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -16,6 +16,39 @@ use crate::Weekday; pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0. impl WeekdaySet { + /// Create a `WeekdaySet` from an array of `Weekday`s. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::EMPTY, WeekdaySet::from_array([])); + /// assert_eq!(WeekdaySet::single(Mon), WeekdaySet::from_array([Mon])); + /// assert_eq!(WeekdaySet::ALL, WeekdaySet::from_array([Mon, Tue, Wed, Thu, Fri, Sat, Sun])); + /// ``` + pub const fn from_array(days: [Weekday; C]) -> Self { + let mut acc = Self::EMPTY; + let mut idx = 0; + while idx < days.len() { + acc.0 |= Self::single(days[idx]).0; + idx += 1; + } + acc + } + + /// Create a `WeekdaySet` from a single `Weekday`. + pub const fn single(weekday: Weekday) -> Self { + match weekday { + Weekday::Mon => Self(0b000_0001), + Weekday::Tue => Self(0b000_0010), + Weekday::Wed => Self(0b000_0100), + Weekday::Thu => Self(0b000_1000), + Weekday::Fri => Self(0b001_0000), + Weekday::Sat => Self(0b010_0000), + Weekday::Sun => Self(0b100_0000), + } + } + /// Returns `Some(day)` if this collection contains exactly one day. /// /// Returns `None` otherwise. @@ -285,39 +318,6 @@ impl WeekdaySet { self.0.count_ones() as u8 } - /// Create a `WeekdaySet` from a single `Weekday`. - pub const fn single(weekday: Weekday) -> Self { - match weekday { - Weekday::Mon => Self(0b000_0001), - Weekday::Tue => Self(0b000_0010), - Weekday::Wed => Self(0b000_0100), - Weekday::Thu => Self(0b000_1000), - Weekday::Fri => Self(0b001_0000), - Weekday::Sat => Self(0b010_0000), - Weekday::Sun => Self(0b100_0000), - } - } - - /// Create a `WeekdaySet` from an array of `Weekday`s. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert_eq!(WeekdaySet::EMPTY, WeekdaySet::from_array([])); - /// assert_eq!(WeekdaySet::single(Mon), WeekdaySet::from_array([Mon])); - /// assert_eq!(WeekdaySet::ALL, WeekdaySet::from_array([Mon, Tue, Wed, Thu, Fri, Sat, Sun])); - /// ``` - pub const fn from_array(days: [Weekday; C]) -> Self { - let mut acc = Self::EMPTY; - let mut idx = 0; - while idx < days.len() { - acc.0 |= Self::single(days[idx]).0; - idx += 1; - } - acc - } - /// An empty `WeekdaySet`. pub const EMPTY: Self = Self(0b000_0000); /// A `WeekdaySet` containing all seven `Weekday`s. From 4094865116fb4c8c21ccfd0d03312a9e3a9dd926 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 09:53:38 +0100 Subject: [PATCH 917/999] Remove Not impls for WeekdaySet and Weekday --- src/weekday_set.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index 159408f3d5..395688271b 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -1,7 +1,6 @@ use core::{ fmt::{self, Debug}, iter::FusedIterator, - ops::Not, }; use crate::Weekday; @@ -419,28 +418,12 @@ impl fmt::Display for WeekdaySet { } } -impl Not for WeekdaySet { - type Output = Self; - - fn not(self) -> Self::Output { - Self(self.0 ^ 0b0111_1111) - } -} - impl FromIterator for WeekdaySet { fn from_iter>(iter: T) -> Self { iter.into_iter().map(Self::single).fold(Self::EMPTY, Self::union) } } -impl Not for Weekday { - type Output = WeekdaySet; - - fn not(self) -> Self::Output { - !WeekdaySet::single(self) - } -} - #[cfg(test)] mod tests { use crate::Weekday; @@ -475,13 +458,6 @@ mod tests { } } - #[test] - fn not_operation_preserves_8th_bit_invariant() { - for days in WeekdaySet::iter_all() { - assert_8th_bit_invariant(!days); - } - } - /// Test `split_at` on all possible arguments. #[test] fn split_at_is_equivalent_to_iterating() { From 64c65a92564703ae2b7104c813fca03cec93cedd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 09:53:56 +0100 Subject: [PATCH 918/999] Rename WeekdaySet::iter_from() to iter() --- src/weekday_set.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index 395688271b..8cd929c635 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -206,14 +206,14 @@ impl WeekdaySet { /// # use chrono::WeekdaySet; /// use chrono::Weekday::*; /// let weekdays = WeekdaySet::from_array([Mon, Wed, Fri]); - /// let mut iter = weekdays.iter_from(Wed); + /// let mut iter = weekdays.iter(Wed); /// assert_eq!(iter.next(), Some(Wed)); /// assert_eq!(iter.next(), Some(Fri)); /// assert_eq!(iter.next(), Some(Mon)); /// assert_eq!(iter.next(), None); /// ``` - pub const fn iter_from(self, start: Weekday) -> WeekdaySetIterFrom { - WeekdaySetIterFrom { days: self, start } + pub const fn iter(self, start: Weekday) -> WeekdaySetIter { + WeekdaySetIter { days: self, start } } /// Returns days that are in both `self` and `other`. @@ -341,14 +341,14 @@ impl Debug for WeekdaySet { /// An iterator over a collection of weekdays, starting from a given day. /// -/// See `WeekdaySet::iter_from`. +/// See [`WeekdaySet::iter()`]. #[derive(Debug, Clone)] -pub struct WeekdaySetIterFrom { +pub struct WeekdaySetIter { pub days: WeekdaySet, pub start: Weekday, } -impl Iterator for WeekdaySetIterFrom { +impl Iterator for WeekdaySetIter { type Item = Weekday; fn next(&mut self) -> Option { @@ -368,7 +368,7 @@ impl Iterator for WeekdaySetIterFrom { } } -impl DoubleEndedIterator for WeekdaySetIterFrom { +impl DoubleEndedIterator for WeekdaySetIter { fn next_back(&mut self) -> Option { if self.days.is_empty() { return None; @@ -386,13 +386,13 @@ impl DoubleEndedIterator for WeekdaySetIterFrom { } } -impl ExactSizeIterator for WeekdaySetIterFrom { +impl ExactSizeIterator for WeekdaySetIter { fn len(&self) -> usize { self.days.len().into() } } -impl FusedIterator for WeekdaySetIterFrom {} +impl FusedIterator for WeekdaySetIter {} /// Print the collection as a slice-like list of weekdays. /// @@ -407,7 +407,7 @@ impl FusedIterator for WeekdaySetIterFrom {} impl fmt::Display for WeekdaySet { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "[")?; - let mut iter = self.iter_from(Weekday::Mon); + let mut iter = self.iter(Weekday::Mon); if let Some(first) = iter.next() { write!(f, "{first}")?; } @@ -463,8 +463,8 @@ mod tests { fn split_at_is_equivalent_to_iterating() { use Weekday::*; - // `split_at` is used in `iter_from`, so we must not iterate - // over all days with `WeekdaySet::ALL.iter_from(Mon)`. + // `split_at()` is used in `iter()`, so we must not iterate + // over all days with `WeekdaySet::ALL.iter(Mon)`. const WEEK: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun]; for weekdays in WeekdaySet::iter_all() { From 97d6c637335649a759c4811150fa0484e4edfd07 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 09:54:19 +0100 Subject: [PATCH 919/999] Privatize WeekdaySetIter fields --- src/weekday_set.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index 8cd929c635..00e061d571 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -344,8 +344,8 @@ impl Debug for WeekdaySet { /// See [`WeekdaySet::iter()`]. #[derive(Debug, Clone)] pub struct WeekdaySetIter { - pub days: WeekdaySet, - pub start: Weekday, + days: WeekdaySet, + start: Weekday, } impl Iterator for WeekdaySetIter { From 57567b3e5652b826754b49645948eda8908a9ddf Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 10:01:47 +0100 Subject: [PATCH 920/999] Tweak WeekdaySet docs --- src/weekday_set.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index 00e061d571..bd4a287de7 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -5,7 +5,7 @@ use core::{ use crate::Weekday; -/// A collection of `Weekday`s stored as a single byte. +/// A collection of [`Weekday`]s stored as a single byte. /// /// This type is `Copy` and provides efficient set-like and slice-like operations. /// Many operations are `const` as well. @@ -15,7 +15,7 @@ use crate::Weekday; pub struct WeekdaySet(u8); // Invariant: the 8-th bit is always 0. impl WeekdaySet { - /// Create a `WeekdaySet` from an array of `Weekday`s. + /// Create a `WeekdaySet` from an array of [`Weekday`]s. /// /// # Example /// ``` @@ -35,7 +35,7 @@ impl WeekdaySet { acc } - /// Create a `WeekdaySet` from a single `Weekday`. + /// Create a `WeekdaySet` from a single [`Weekday`]. pub const fn single(weekday: Weekday) -> Self { match weekday { Weekday::Mon => Self(0b000_0001), @@ -183,23 +183,15 @@ impl WeekdaySet { /// Returns a tuple `(before, after)`. `before` contains all days starting from Monday /// up to but __not__ including `weekday`. `after` contains all days starting from `weekday` /// up to and including Sunday. - /// - /// # Example - /// ```ignore - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// let (before, after) = WeekdaySet::ALL.split_at(Fri); - /// assert_eq!(before, WeekdaySet::from_array([Mon, Tue, Wed, Thu])); - /// assert_eq!(after, WeekdaySet::from_array([Fri, Sat, Sun])); - /// ``` const fn split_at(self, weekday: Weekday) -> (Self, Self) { let days_after = 0b1000_0000 - Self::single(weekday).0; let days_before = days_after ^ 0b0111_1111; (Self(self.0 & days_before), Self(self.0 & days_after)) } - /// Iterate over the `Weekday`s in the collection, starting from a given day - /// and wrapping around from Sunday to Monday. + /// Iterate over the [`Weekday`]s in the collection starting from a given day. + /// + /// Wraps around from Sunday to Monday if necessary. /// /// # Example /// ``` From 265c79b2a50bbdf224d69f0d1f7e6c4266a997f5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 23 Mar 2025 10:04:55 +0100 Subject: [PATCH 921/999] Tweak WeekdaySet method order --- src/weekday_set.rs | 152 ++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/weekday_set.rs b/src/weekday_set.rs index bd4a287de7..ce60bf3ba4 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -74,20 +74,6 @@ impl WeekdaySet { } } - /// Returns `true` if `other` contains all days in `self`. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert!(WeekdaySet::single(Mon).is_subset(WeekdaySet::ALL)); - /// assert!(!WeekdaySet::single(Mon).is_subset(WeekdaySet::EMPTY)); - /// assert!(WeekdaySet::EMPTY.is_subset(WeekdaySet::single(Mon))); - /// ``` - pub const fn is_subset(self, other: Self) -> bool { - self.intersection(other).0 == self.0 - } - /// Adds a day to the collection. /// /// Returns `true` if the day was new to the collection. @@ -130,6 +116,82 @@ impl WeekdaySet { false } + /// Returns `true` if `other` contains all days in `self`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert!(WeekdaySet::single(Mon).is_subset(WeekdaySet::ALL)); + /// assert!(!WeekdaySet::single(Mon).is_subset(WeekdaySet::EMPTY)); + /// assert!(WeekdaySet::EMPTY.is_subset(WeekdaySet::single(Mon))); + /// ``` + pub const fn is_subset(self, other: Self) -> bool { + self.intersection(other).0 == self.0 + } + + /// Returns days that are in both `self` and `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Tue)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::EMPTY), WeekdaySet::EMPTY); + /// ``` + pub const fn intersection(self, other: Self) -> Self { + Self(self.0 & other.0) + } + + /// Returns days that are in either `self` or `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::single(Mon)), WeekdaySet::ALL); + /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Returns days that are in `self` or `other` but not in both. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); + /// assert_eq!( + /// WeekdaySet::ALL.symmetric_difference(WeekdaySet::single(Mon)), + /// WeekdaySet::from_array([Tue, Wed, Thu, Fri, Sat, Sun]), + /// ); + /// assert_eq!(WeekdaySet::ALL.symmetric_difference(WeekdaySet::EMPTY), WeekdaySet::ALL); + /// ``` + pub const fn symmetric_difference(self, other: Self) -> Self { + Self(self.0 ^ other.0) + } + + /// Returns days that are in `self` but not in `other`. + /// + /// # Example + /// ``` + /// # use chrono::WeekdaySet; + /// use chrono::Weekday::*; + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Tue)), WeekdaySet::single(Mon)); + /// assert_eq!(WeekdaySet::EMPTY.difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); + /// ``` + pub const fn difference(self, other: Self) -> Self { + Self(self.0 & !other.0) + } + /// Get the first day in the collection, starting from Monday. /// /// Returns `None` if the collection is empty. @@ -208,68 +270,6 @@ impl WeekdaySet { WeekdaySetIter { days: self, start } } - /// Returns days that are in both `self` and `other`. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); - /// assert_eq!(WeekdaySet::single(Mon).intersection(WeekdaySet::single(Tue)), WeekdaySet::EMPTY); - /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); - /// assert_eq!(WeekdaySet::ALL.intersection(WeekdaySet::EMPTY), WeekdaySet::EMPTY); - /// ``` - pub const fn intersection(self, other: Self) -> Self { - Self(self.0 & other.0) - } - - /// Returns days that are in either `self` or `other`. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Mon)), WeekdaySet::single(Mon)); - /// assert_eq!(WeekdaySet::single(Mon).union(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); - /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::single(Mon)), WeekdaySet::ALL); - /// assert_eq!(WeekdaySet::ALL.union(WeekdaySet::EMPTY), WeekdaySet::ALL); - /// ``` - pub const fn union(self, other: Self) -> Self { - Self(self.0 | other.0) - } - - /// Returns days that are in `self` or `other` but not in both. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); - /// assert_eq!(WeekdaySet::single(Mon).symmetric_difference(WeekdaySet::single(Tue)), WeekdaySet::from_array([Mon, Tue])); - /// assert_eq!( - /// WeekdaySet::ALL.symmetric_difference(WeekdaySet::single(Mon)), - /// WeekdaySet::from_array([Tue, Wed, Thu, Fri, Sat, Sun]), - /// ); - /// assert_eq!(WeekdaySet::ALL.symmetric_difference(WeekdaySet::EMPTY), WeekdaySet::ALL); - /// ``` - pub const fn symmetric_difference(self, other: Self) -> Self { - Self(self.0 ^ other.0) - } - - /// Returns days that are in `self` but not in `other`. - /// - /// # Example - /// ``` - /// # use chrono::WeekdaySet; - /// use chrono::Weekday::*; - /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); - /// assert_eq!(WeekdaySet::single(Mon).difference(WeekdaySet::single(Tue)), WeekdaySet::single(Mon)); - /// assert_eq!(WeekdaySet::EMPTY.difference(WeekdaySet::single(Mon)), WeekdaySet::EMPTY); - /// ``` - pub const fn difference(self, other: Self) -> Self { - Self(self.0 & !other.0) - } - /// Returns `true` if the collection contains the given day. /// /// # Example From 6a853015b7ccc09ae807e277a76d56de58d65139 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Mar 2025 10:41:08 +0100 Subject: [PATCH 922/999] Upgrade to windows-bindgen 0.61 --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5a6333993..79bc50d983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-link = { version = "0.1", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.60" } # MSRV is 1.74 +windows-bindgen = { version = "0.61" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index b8e6b6d2a2..cb563b4ebb 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -18,14 +18,19 @@ pub struct DYNAMIC_TIME_ZONE_INFORMATION { pub TimeZoneKeyName: [u16; 128], pub DynamicDaylightTimeDisabled: bool, } +impl Default for DYNAMIC_TIME_ZONE_INFORMATION { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct SYSTEMTIME { pub wYear: u16, pub wMonth: u16, @@ -47,3 +52,8 @@ pub struct TIME_ZONE_INFORMATION { pub DaylightDate: SYSTEMTIME, pub DaylightBias: i32, } +impl Default for TIME_ZONE_INFORMATION { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} From 104cdc73cb9318557e135acb7b47176a00905e69 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 3 Apr 2025 16:55:49 +0200 Subject: [PATCH 923/999] Bump MSRV to 1.62 --- .github/workflows/test.yml | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a57b3dd3f0..88ec503cf7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: "1.61.0" + toolchain: "1.62.0" - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests # which are run elsewhere diff --git a/Cargo.toml b/Cargo.toml index 79bc50d983..360654580a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" include = ["src/*", "tests/*.rs", "LICENSE.txt", "CITATION.cff"] edition = "2021" -rust-version = "1.61.0" +rust-version = "1.62.0" [lib] name = "chrono" From 7c0bd136d68414850a86380acc1ba11764d4eaf4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 3 Apr 2025 16:57:48 +0200 Subject: [PATCH 924/999] Apply suggestions from clippy 1.86 --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 80e2fc41d6..80a70d464c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,11 +37,11 @@ //! - `serde`: Enable serialization/deserialization via [serde]. //! - `rkyv`: Deprecated, use the `rkyv-*` features. //! - `rkyv-16`: Enable serialization/deserialization via [rkyv], -//! using 16-bit integers for integral `*size` types. +//! using 16-bit integers for integral `*size` types. //! - `rkyv-32`: Enable serialization/deserialization via [rkyv], -//! using 32-bit integers for integral `*size` types. +//! using 32-bit integers for integral `*size` types. //! - `rkyv-64`: Enable serialization/deserialization via [rkyv], -//! using 64-bit integers for integral `*size` types. +//! using 64-bit integers for integral `*size` types. //! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. //! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. //! - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. From b267a4fe909084b434398de7aff905018e6655e0 Mon Sep 17 00:00:00 2001 From: splashling Date: Fri, 4 Apr 2025 09:39:41 +0200 Subject: [PATCH 925/999] Implemented consistent Hash and Eq trait for NaiveWeek --- src/naive/mod.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index 5be9a50858..dbd95a8c17 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -29,7 +29,7 @@ pub use self::internals::YearFlags as __BenchYearFlags; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq)] pub struct NaiveWeek { date: NaiveDate, start: Weekday, @@ -206,6 +206,19 @@ impl NaiveWeek { } } +impl PartialEq for NaiveWeek { + fn eq(&self, other: &Self) -> bool { + self.first_day() == other.first_day() + } +} + +use core::hash::{Hash, Hasher}; +impl Hash for NaiveWeek { + fn hash(&self, state: &mut H) { + self.first_day().hash(state); + } +} + /// A duration in calendar days. /// /// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` @@ -235,7 +248,8 @@ pub mod serde { #[cfg(test)] mod test { - use crate::{NaiveDate, Weekday}; + use crate::{NaiveDate, NaiveWeek, Weekday}; + use std::hash::{DefaultHasher, Hash, Hasher}; #[test] fn test_naiveweek() { let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); @@ -278,4 +292,39 @@ mod test { let _ = date_min.week(Weekday::Mon).checked_days(); let _ = date_max.week(Weekday::Mon).checked_days(); } + + #[test] + fn test_naiveweek_eq() { + let a = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon }; + let b = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; + assert_eq!(a, b); + let c = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; + assert_ne!(a, c); + assert_ne!(b, c); + } + + #[test] + fn test_naiveweek_hash() { + let mut hasher = DefaultHasher::default(); + let a = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon }; + let b = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; + let c = + NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; + a.hash(&mut hasher); + let a_hash = hasher.finish(); + hasher = DefaultHasher::default(); + b.hash(&mut hasher); + let b_hash = hasher.finish(); + hasher = DefaultHasher::default(); + c.hash(&mut hasher); + let c_hash = hasher.finish(); + assert_eq!(a_hash, b_hash); + assert_ne!(b_hash, c_hash); + assert_ne!(a_hash, c_hash); + } } From dc068f0373a689876590aa6f683d5a1b8e5e7e47 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 4 Apr 2025 11:01:26 +0200 Subject: [PATCH 926/999] Tweak style on NaiveWeek fixes --- src/naive/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/naive/mod.rs b/src/naive/mod.rs index dbd95a8c17..efb9822859 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -4,6 +4,7 @@ //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), //! but can be also used for the simpler date and time handling. +use core::hash::{Hash, Hasher}; use core::ops::RangeInclusive; use crate::Weekday; @@ -212,7 +213,6 @@ impl PartialEq for NaiveWeek { } } -use core::hash::{Hash, Hasher}; impl Hash for NaiveWeek { fn hash(&self, state: &mut H) { self.first_day().hash(state); @@ -300,6 +300,7 @@ mod test { let b = NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; assert_eq!(a, b); + let c = NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; assert_ne!(a, c); @@ -308,21 +309,25 @@ mod test { #[test] fn test_naiveweek_hash() { - let mut hasher = DefaultHasher::default(); let a = NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon }; let b = NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon }; let c = NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun }; + + let mut hasher = DefaultHasher::default(); a.hash(&mut hasher); let a_hash = hasher.finish(); + hasher = DefaultHasher::default(); b.hash(&mut hasher); let b_hash = hasher.finish(); + hasher = DefaultHasher::default(); c.hash(&mut hasher); let c_hash = hasher.finish(); + assert_eq!(a_hash, b_hash); assert_ne!(b_hash, c_hash); assert_ne!(a_hash, c_hash); From ebeef99426a5d0450505a68dac0723a7efed6e7a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 5 Apr 2025 19:36:37 +0200 Subject: [PATCH 927/999] TimeZone::from_posix_tz: Treat empty TZ variable as UTC This is not technically POSIX, but glibc and musl behave that way: e.g. `TZ= date` returns a UTC date. --- src/offset/local/tz_info/timezone.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 61d4498a94..9cd37f4ff1 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -37,8 +37,9 @@ impl TimeZone { /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). fn from_posix_tz(tz_string: &str) -> Result { + // It is commonly agreed (but not standard) that setting an empty `TZ=` uses UTC. if tz_string.is_empty() { - return Err(Error::InvalidTzString("empty TZ string")); + return Ok(Self::utc()); } if tz_string == "localtime" { @@ -925,7 +926,7 @@ mod tests { } assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err()); - assert!(TimeZone::from_posix_tz("").is_err()); + assert_eq!(TimeZone::from_posix_tz("").unwrap().find_local_time_type(0)?.offset(), 0); Ok(()) } From 2c95b0aca8c1deae582b163c1b48816740df3942 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 9 Apr 2025 13:25:03 +0200 Subject: [PATCH 928/999] Tweak expression to avoid repetition --- src/format/strftime.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index f0478b43ae..fad84bcc3f 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -227,13 +227,15 @@ impl<'a> StrftimeItems<'a> { /// ``` #[must_use] pub const fn new(s: &'a str) -> StrftimeItems<'a> { - #[cfg(not(feature = "unstable-locales"))] { - StrftimeItems { remainder: s, queue: &[] } - } - #[cfg(feature = "unstable-locales")] - { - StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None } + StrftimeItems { + remainder: s, + queue: &[], + #[cfg(feature = "unstable-locales")] + locale_str: "", + #[cfg(feature = "unstable-locales")] + locale: None, + } } } From e2bd1d1d6785312d6a95dddcde342ad08f66b9c9 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Fri, 11 Apr 2025 02:38:40 -0400 Subject: [PATCH 929/999] Add support for lenient format strings (#1693) --- src/format/strftime.rs | 155 ++++++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 24 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index fad84bcc3f..cfe2a6beef 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -195,6 +195,7 @@ pub struct StrftimeItems<'a> { /// If the current specifier is composed of multiple formatting items (e.g. `%+`), /// `queue` stores a slice of `Item`s that have to be returned one by one. queue: &'static [Item<'static>], + lenient: bool, #[cfg(feature = "unstable-locales")] locale_str: &'a str, #[cfg(feature = "unstable-locales")] @@ -227,15 +228,47 @@ impl<'a> StrftimeItems<'a> { /// ``` #[must_use] pub const fn new(s: &'a str) -> StrftimeItems<'a> { - { - StrftimeItems { - remainder: s, - queue: &[], - #[cfg(feature = "unstable-locales")] - locale_str: "", - #[cfg(feature = "unstable-locales")] - locale: None, - } + StrftimeItems { + remainder: s, + queue: &[], + lenient: false, + #[cfg(feature = "unstable-locales")] + locale_str: "", + #[cfg(feature = "unstable-locales")] + locale: None, + } + } + + /// The same as [`StrftimeItems::new`], but returns [`Item::Literal`] instead of [`Item::Error`]. + /// + /// Useful for formatting according to potentially invalid format strings. + /// + /// # Example + /// + /// ``` + /// use chrono::format::*; + /// + /// let strftime_parser = StrftimeItems::new_lenient("%Y-%Q"); // %Y: year, %Q: invalid + /// + /// const ITEMS: &[Item<'static>] = &[ + /// Item::Numeric(Numeric::Year, Pad::Zero), + /// Item::Literal("-"), + /// Item::Literal("%"), + /// Item::Literal("Q"), + /// ]; + /// println!("{:?}", strftime_parser.clone().collect::>()); + /// assert!(strftime_parser.eq(ITEMS.iter().cloned())); + /// ``` + #[must_use] + pub const fn new_lenient(s: &'a str) -> StrftimeItems<'a> { + StrftimeItems { + remainder: s, + queue: &[], + lenient: true, + #[cfg(feature = "unstable-locales")] + locale_str: "", + #[cfg(feature = "unstable-locales")] + locale: None, } } @@ -288,7 +321,13 @@ impl<'a> StrftimeItems<'a> { #[cfg(feature = "unstable-locales")] #[must_use] pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { - StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } + StrftimeItems { + remainder: s, + queue: &[], + lenient: false, + locale_str: "", + locale: Some(locale), + } } /// Parse format string into a `Vec` of formatting [`Item`]'s. @@ -310,7 +349,7 @@ impl<'a> StrftimeItems<'a> { /// # Errors /// /// Returns an error if the format string contains an invalid or unrecognized formatting - /// specifier. + /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient]. /// /// # Example /// @@ -354,7 +393,7 @@ impl<'a> StrftimeItems<'a> { /// # Errors /// /// Returns an error if the format string contains an invalid or unrecognized formatting - /// specifier. + /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient]. /// /// # Example /// @@ -416,6 +455,22 @@ impl<'a> Iterator for StrftimeItems<'a> { } impl<'a> StrftimeItems<'a> { + fn error<'b>( + &mut self, + original: &'b str, + error_len: &mut usize, + ch: Option, + ) -> (&'b str, Item<'b>) { + if !self.lenient { + return (&original[*error_len..], Item::Error); + } + + if let Some(c) = ch { + *error_len -= c.len_utf8(); + } + (&original[*error_len..], Item::Literal(&original[..*error_len])) + } + fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { use InternalInternal::*; use Item::{Literal, Space}; @@ -456,16 +511,24 @@ impl<'a> StrftimeItems<'a> { // the next item is a specifier Some('%') => { + let original = remainder; remainder = &remainder[1..]; + let mut error_len = 0; + if self.lenient { + error_len += 1; + } macro_rules! next { () => { match remainder.chars().next() { Some(x) => { remainder = &remainder[x.len_utf8()..]; + if self.lenient { + error_len += x.len_utf8(); + } x } - None => return Some((remainder, Item::Error)), // premature end of string + None => return Some(self.error(original, &mut error_len, None)), // premature end of string } }; } @@ -480,7 +543,7 @@ impl<'a> StrftimeItems<'a> { let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some((remainder, Item::Error)); + return Some(self.error(original, &mut error_len, Some(spec))); } macro_rules! queue { @@ -592,39 +655,71 @@ impl<'a> StrftimeItems<'a> { remainder = &remainder[1..]; fixed(Fixed::TimezoneOffsetColon) } else { - Item::Error + self.error(original, &mut error_len, None).1 } } '.' => match next!() { '3' => match next!() { 'f' => fixed(Fixed::Nanosecond3), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '6' => match next!() { 'f' => fixed(Fixed::Nanosecond6), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '9' => match next!() { 'f' => fixed(Fixed::Nanosecond9), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, 'f' => fixed(Fixed::Nanosecond), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '3' => match next!() { 'f' => internal_fixed(Nanosecond3NoDot), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '6' => match next!() { 'f' => internal_fixed(Nanosecond6NoDot), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '9' => match next!() { 'f' => internal_fixed(Nanosecond9NoDot), - _ => Item::Error, + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }, '%' => Literal("%"), - _ => Item::Error, // no such specifier + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } }; // Adjust `item` if we have any padding modifier. @@ -635,7 +730,7 @@ impl<'a> StrftimeItems<'a> { Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { Some((remainder, Item::Numeric(kind.clone(), new_pad))) } - _ => Some((remainder, Item::Error)), + _ => Some(self.error(original, &mut error_len, None)), } } else { Some((remainder, item)) @@ -1139,4 +1234,16 @@ mod tests { let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000"); } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_strftime_parse_lenient() { + let fmt_str = StrftimeItems::new_lenient("%Y-%m-%dT%H:%M:%S%z%Q%.2f%%%"); + let fmt_items = fmt_str.parse().unwrap(); + let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); + assert_eq!( + &dt.format_with_items(fmt_items.iter()).to_string(), + "2014-05-07T12:34:56+0000%Q%.2f%%" + ); + } } From d1de1d95ead4e59472622761ad545dc4a940992b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 29 Apr 2025 10:35:56 +0200 Subject: [PATCH 930/999] Bump version to 0.4.41 --- CITATION.cff | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 21c47e7cde..19ae4f2357 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,7 +4,7 @@ message: Please cite this crate using these information. # Version information. date-released: 2025-02-26 -version: 0.4.40 +version: 0.4.41 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 360654580a..13eaceb84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.40" +version = "0.4.41" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 361b9fc61eb9d495b41fa8de6296866323cb53e7 Mon Sep 17 00:00:00 2001 From: misha Date: Tue, 29 Apr 2025 22:59:42 +0300 Subject: [PATCH 931/999] Add fuzzer for DateTime::parse_from_str --- fuzz/Cargo.toml | 4 ++++ fuzz/fuzz_targets/fuzz_format.rs | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_format.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d15c9a9f7f..20dae08e95 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -22,3 +22,7 @@ members = ["."] [[bin]] name = "fuzz_reader" path = "fuzz_targets/fuzz_reader.rs" + +[[bin]] +name = "fuzz_format" +path = "fuzz_targets/fuzz_format.rs" diff --git a/fuzz/fuzz_targets/fuzz_format.rs b/fuzz/fuzz_targets/fuzz_format.rs new file mode 100644 index 0000000000..6a7d395e29 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_format.rs @@ -0,0 +1,7 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: (String, String)| { + use chrono::prelude::*; + let _ = DateTime::parse_from_str(&data.0, &data.1); +}); From c5c22a357da0423156b21761c5bffc77b99e3aa9 Mon Sep 17 00:00:00 2001 From: Tilman Date: Thu, 8 May 2025 10:39:42 +0200 Subject: [PATCH 932/999] Fix wrong amount of micro/milliseconds --- src/naive/date/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 62efd45459..bb1adfd762 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -755,7 +755,7 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// The millisecond part is allowed to exceed 1,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics @@ -770,7 +770,7 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// The millisecond part is allowed to exceed 1,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors @@ -805,7 +805,7 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// The microsecond part is allowed to exceed 1,000,000 in order to represent a [leap second]( /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics From 3b4af71f0be876e6ed2d5c6d3559f2ffd842cd10 Mon Sep 17 00:00:00 2001 From: Ohad Lutzky Date: Mon, 5 May 2025 21:06:33 +0100 Subject: [PATCH 933/999] Add warning about MappedLocalTime and wasm Warns about https://github.com/chronotope/chrono/issues/1701. --- src/offset/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 09f6821dce..f24a15dfa8 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -46,6 +46,26 @@ pub use self::utc::Utc; /// DST. /// * No result when the clock is turned forwards during a transition due to for example DST. /// +///
+/// +/// In wasm, when using [`Local`], only the [`LocalResult::Single`] variant is returned. +/// Specifically: +/// +/// * When the clock is turned backwards, where `Ambiguous(earliest, latest)` would be expected, +/// `Single(earliest)` is returned instead. +/// * When the clock is turned forwards, where `None` would be expected, `Single(t)` is returned, +/// with `t` being the requested local time represented as though there is no transition on that +/// day (i.e. still "summer time") +/// +/// This is caused because of limitations in the JavaScript +/// [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +/// API, which always parses a local time as a single, valid time - even for an +/// input which describes a nonexistent or ambiguous time. +/// +/// See further discussion and workarounds in . +/// +///
+/// /// When the clock is turned backwards it creates a _fold_ in local time, during which the local /// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during /// which the local time is _missing_, or does not exist. From 2e1cd2f5fa942949a20735118379d0e48dced868 Mon Sep 17 00:00:00 2001 From: Chris Leach <7308018+chris-leach@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:48:36 +0100 Subject: [PATCH 934/999] Fix incorrect parsing of fixed-length second fractions (#1705) Co-authored-by: Chris Leach --- src/format/parse.rs | 47 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 40b5b05246..81417b1da3 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -417,13 +417,34 @@ where s = &s[2..]; } - &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { + &Nanosecond => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); parsed.set_nanosecond(nano)?; } } + &Nanosecond3 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 3)); + parsed.set_nanosecond(nano)?; + } + } + + &Nanosecond6 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 6)); + parsed.set_nanosecond(nano)?; + } + } + + &Nanosecond9 => { + if s.starts_with('.') { + let nano = try_consume!(scan::nanosecond_fixed(&s[1..], 9)); + parsed.set_nanosecond(nano)?; + } + } + &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { return Err(TOO_SHORT); @@ -910,7 +931,7 @@ mod tests { #[test] fn test_parse_fixed_nanosecond() { - use crate::format::Fixed::Nanosecond; + use crate::format::Fixed::{Nanosecond, Nanosecond3, Nanosecond6, Nanosecond9}; use crate::format::InternalInternal::*; use crate::format::Item::Literal; use crate::format::Numeric::Second; @@ -944,6 +965,28 @@ mod tests { check(". 4", &[fixed(Nanosecond)], Err(INVALID)); check(" .4", &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming + // fixed-length fractions of a second + check("", &[fixed(Nanosecond3)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond3)], Err(TOO_LONG)); // never consumes `4` + check(".12", &[fixed(Nanosecond3)], Err(TOO_SHORT)); + check(".123", &[fixed(Nanosecond3)], parsed!(nanosecond: 123_000_000)); + check(".1234", &[fixed(Nanosecond3)], Err(TOO_LONG)); + check(".1234", &[fixed(Nanosecond3), Literal("4")], parsed!(nanosecond: 123_000_000)); + + check("", &[fixed(Nanosecond6)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond6)], Err(TOO_LONG)); // never consumes `4` + check(".12345", &[fixed(Nanosecond6)], Err(TOO_SHORT)); + check(".123456", &[fixed(Nanosecond6)], parsed!(nanosecond: 123_456_000)); + check(".1234567", &[fixed(Nanosecond6)], Err(TOO_LONG)); + check(".1234567", &[fixed(Nanosecond6), Literal("7")], parsed!(nanosecond: 123_456_000)); + + check("", &[fixed(Nanosecond9)], parsed!()); // no field set, but not an error + check("4", &[fixed(Nanosecond9)], Err(TOO_LONG)); // never consumes `4` + check(".12345678", &[fixed(Nanosecond9)], Err(TOO_SHORT)); + check(".123456789", &[fixed(Nanosecond9)], parsed!(nanosecond: 123_456_789)); + check(".1234567890", &[fixed(Nanosecond9)], Err(TOO_LONG)); + check(".1234567890", &[fixed(Nanosecond9), Literal("0")], parsed!(nanosecond: 123_456_789)); + // fixed: nanoseconds without the dot check("", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); check(".", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); From 1762c717775601a63b1962b1b06f104c1436d5d0 Mon Sep 17 00:00:00 2001 From: Arjun Ramesh Date: Wed, 4 Jun 2025 15:40:46 -0400 Subject: [PATCH 935/999] Fix cfgs for `wasm32-linux` support --- src/datetime/mod.rs | 6 +++--- src/offset/local/mod.rs | 2 +- src/offset/utc.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3e2c273af0..80b040d081 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1881,7 +1881,7 @@ impl From> for SystemTime { #[cfg(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] impl From for DateTime { fn from(date: js_sys::Date) -> DateTime { @@ -1892,7 +1892,7 @@ impl From for DateTime { #[cfg(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] impl From<&js_sys::Date> for DateTime { fn from(date: &js_sys::Date) -> DateTime { @@ -1903,7 +1903,7 @@ impl From<&js_sys::Date> for DateTime { #[cfg(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] impl From> for js_sys::Date { /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index a1ba3fac65..27b2ff736d 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -56,7 +56,7 @@ mod inner { #[cfg(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] mod inner { use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike}; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 5ae26ed86d..ab925fbf16 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -9,7 +9,7 @@ use core::fmt; not(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) )) ))] use std::time::{SystemTime, UNIX_EPOCH}; @@ -89,7 +89,7 @@ impl Utc { #[cfg(not(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) )))] #[must_use] pub fn now() -> DateTime { @@ -102,7 +102,7 @@ impl Utc { #[cfg(all( target_arch = "wasm32", feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) + not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] #[must_use] pub fn now() -> DateTime { From bab97905ccfa5aa7ceae1ee131b41b7113ea4f6a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 16 Jun 2025 11:04:20 +0200 Subject: [PATCH 936/999] Upgrade to windows-bindgen 0.62 --- Cargo.toml | 2 +- tests/win_bindings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13eaceb84c..a65345ee2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-link = { version = "0.1", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.61" } # MSRV is 1.74 +windows-bindgen = { version = "0.62" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index d3c2bc3ae3..9039a497ee 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -9,7 +9,7 @@ fn gen_bindings() { let output = "src/offset/local/win_bindings.rs"; let existing = fs::read_to_string(output).unwrap(); - bindgen(["--no-deps", "--etc", input]); + bindgen(["--no-deps", "--etc", input]).unwrap(); // Check the output is the same as before. // Depending on the git configuration the file may have been checked out with `\r\n` newlines or From 9245a8f38f738460861208c771fcac32370e2852 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 27 Jun 2025 09:41:23 +0200 Subject: [PATCH 937/999] Apply suggestions from clippy 1.88 --- bench/benches/chrono.rs | 2 +- src/datetime/tests.rs | 25 +++++++++---------- src/format/formatting.rs | 44 ++++++++++++++++----------------- src/format/parse.rs | 8 +++--- src/format/scan.rs | 3 +-- src/format/strftime.rs | 2 +- src/lib.rs | 2 +- src/naive/date/mod.rs | 2 +- src/naive/date/tests.rs | 26 +++++++++---------- src/naive/datetime/tests.rs | 17 ++++++------- src/naive/internals.rs | 8 +++--- src/naive/isoweek.rs | 8 +++--- src/naive/time/mod.rs | 2 +- src/naive/time/tests.rs | 17 ++++++------- src/offset/fixed.rs | 4 +-- src/offset/local/mod.rs | 6 ++--- src/offset/local/tz_info/mod.rs | 12 ++++----- src/offset/local/unix.rs | 2 +- src/offset/mod.rs | 4 +-- src/time_delta.rs | 4 +-- src/traits.rs | 12 +++------ src/weekday.rs | 2 +- tests/dateutils.rs | 25 +++++++++---------- tests/win_bindings.rs | 2 +- 24 files changed, 106 insertions(+), 133 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index c74d6ae382..b2b96715a8 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -117,7 +117,7 @@ fn bench_get_local_time(c: &mut Criterion) { /// /// Panics if `div` is not positive. fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); + assert!(div > 0, "in_between: nonpositive div = {div}"); let start = (start.div_euclid(div), start.rem_euclid(div)); let end = (end.div_euclid(div), end.rem_euclid(div)); // The lowest multiple of `div` greater than or equal to `start`, divided. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b7f7106928..b443ec028e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -86,7 +86,7 @@ impl TimeZone for DstTester { { MappedLocalTime::None } else { - panic!("Unexpected local time {}", local) + panic!("Unexpected local time {local}") } } @@ -118,7 +118,7 @@ impl TimeZone for DstTester { } else if *utc >= utc_to_winter_transition && *utc < utc_to_summer_transition { DstTester::winter_offset() } else { - panic!("Unexpected utc time {}", utc) + panic!("Unexpected utc time {utc}") } } } @@ -1029,25 +1029,22 @@ fn test_parse_datetime_utc() { "+82701-05-6T15:9:60.898989898989Z", ]; for &s in &valid { - eprintln!("test_parse_datetime_utc valid {:?}", s); + eprintln!("test_parse_datetime_utc valid {s:?}"); let d = match s.parse::>() { Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), + Err(e) => panic!("parsing `{s}` has failed: {e}"), }; - let s_ = format!("{:?}", d); + let s_ = format!("{d:?}"); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::>() { Ok(d) => d, Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") } }; assert!( d == d_, - "`{}` is parsed into `{:?}`, but reparsed result `{:?}` does not match", - s, - d, - d_ + "`{s}` is parsed into `{d:?}`, but reparsed result `{d_:?}` does not match" ); } @@ -1081,7 +1078,7 @@ fn test_parse_datetime_utc() { " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; for &s in &invalid { - eprintln!("test_parse_datetime_utc invalid {:?}", s); + eprintln!("test_parse_datetime_utc invalid {s:?}"); assert!(s.parse::>().is_err()); } } @@ -1568,7 +1565,7 @@ fn test_min_max_getters() { let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); - assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); + assert_eq!(format!("{beyond_min:?}"), "-262144-12-31T22:00:00-02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_min.to_rfc2822(), ""); #[cfg(feature = "alloc")] @@ -1593,7 +1590,7 @@ fn test_min_max_getters() { assert_eq!(beyond_min.second(), 0); assert_eq!(beyond_min.nanosecond(), 0); - assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); + assert_eq!(format!("{beyond_max:?}"), "+262143-01-01T01:59:59.999999999+02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_max.to_rfc2822(), ""); #[cfg(feature = "alloc")] @@ -1894,7 +1891,7 @@ fn nano_roundrip() { i64::MAX - 1, i64::MAX, ] { - println!("nanos: {}", nanos); + println!("nanos: {nanos}"); let dt = Utc.timestamp_nanos(nanos); let nanos2 = dt.timestamp_nanos_opt().expect("value roundtrips"); assert_eq!(nanos, nanos2); diff --git a/src/format/formatting.rs b/src/format/formatting.rs index e27b764062..3b37a15f73 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -161,15 +161,15 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { ) -> fmt::Result { if always_sign { match pad { - Pad::None => write!(w, "{:+}", v), + Pad::None => write!(w, "{v:+}"), Pad::Zero => write!(w, "{:+01$}", v, n + 1), Pad::Space => write!(w, "{:+1$}", v, n + 1), } } else { match pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, n), - Pad::Space => write!(w, "{:1$}", v, n), + Pad::None => write!(w, "{v}"), + Pad::Zero => write!(w, "{v:0n$}"), + Pad::Space => write!(w, "{v:n$}"), } } } @@ -253,7 +253,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } else if nano % 1_000 == 0 { write!(w, "{:06}", nano / 1_000) } else { - write!(w, "{:09}", nano) + write!(w, "{nano:09}") } } } @@ -278,7 +278,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { write!(w, "{:09}", t.nanosecond() % 1_000_000_000) } - (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{}", tz_name), + (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{tz_name}"), (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => { let offset_format = OffsetFormat { precision: OffsetPrecision::Minutes, @@ -508,7 +508,7 @@ pub(crate) fn write_rfc3339( write_hundreds(w, (year % 100) as u8)?; } else { // ISO 8601 requires the explicit sign for out-of-range years - write!(w, "{:+05}", year)?; + write!(w, "{year:+05}")?; } w.write_char('-')?; write_hundreds(w, dt.date().month() as u8)?; @@ -534,7 +534,7 @@ pub(crate) fn write_rfc3339( SecondsFormat::Secs => {} SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?, SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?, - SecondsFormat::Nanos => write!(w, ".{:09}", nano)?, + SecondsFormat::Nanos => write!(w, ".{nano:09}")?, SecondsFormat::AutoSi => { if nano == 0 { } else if nano % 1_000_000 == 0 { @@ -542,7 +542,7 @@ pub(crate) fn write_rfc3339( } else if nano % 1_000 == 0 { write!(w, ".{:06}", nano / 1_000)? } else { - write!(w, ".{:09}", nano)? + write!(w, ".{nano:09}")? } } SecondsFormat::__NonExhaustive => unreachable!(), @@ -763,31 +763,31 @@ mod tests { // Item::Literal, odd number of padding bytes. let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>4}", percent)); - assert_eq!("% ", format!("{:<4}", percent)); - assert_eq!(" % ", format!("{:^4}", percent)); + assert_eq!(" %", format!("{percent:>4}")); + assert_eq!("% ", format!("{percent:<4}")); + assert_eq!(" % ", format!("{percent:^4}")); // Item::Numeric, custom non-ASCII padding character let year = datetime.format("%Y"); - assert_eq!("——2007", format!("{:—>6}", year)); - assert_eq!("2007——", format!("{:—<6}", year)); - assert_eq!("—2007—", format!("{:—^6}", year)); + assert_eq!("——2007", format!("{year:—>6}")); + assert_eq!("2007——", format!("{year:—<6}")); + assert_eq!("—2007—", format!("{year:—^6}")); // Item::Fixed let tz = datetime.format("%Z"); - assert_eq!(" UTC", format!("{:>5}", tz)); - assert_eq!("UTC ", format!("{:<5}", tz)); - assert_eq!(" UTC ", format!("{:^5}", tz)); + assert_eq!(" UTC", format!("{tz:>5}")); + assert_eq!("UTC ", format!("{tz:<5}")); + assert_eq!(" UTC ", format!("{tz:^5}")); // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] let ymd = datetime.format("%Y %B %d"); - assert_eq!(" 2007 January 02", format!("{:>17}", ymd)); - assert_eq!("2007 January 02 ", format!("{:<17}", ymd)); - assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd)); + assert_eq!(" 2007 January 02", format!("{ymd:>17}")); + assert_eq!("2007 January 02 ", format!("{ymd:<17}")); + assert_eq!(" 2007 January 02 ", format!("{ymd:^17}")); // Truncated let time = datetime.format("%T%.6f"); - assert_eq!("12:34:56.1234", format!("{:.13}", time)); + assert_eq!("12:34:56.1234", format!("{time:.13}")); } #[test] diff --git a/src/format/parse.rs b/src/format/parse.rs index 81417b1da3..2e3b29849d 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1720,13 +1720,12 @@ mod tests { // Test against test data above for &(date, checkdate) in testdates.iter() { #[cfg(feature = "std")] - eprintln!("Test input: {:?}\n Expect: {:?}", date, checkdate); + eprintln!("Test input: {date:?}\n Expect: {checkdate:?}"); let dt = rfc2822_to_datetime(date); // parse a date if dt != checkdate { // check for expected result panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate + "Date conversion failed for {date}\nReceived: {dt:?}\nExpected: {checkdate:?}" ); } } @@ -1879,8 +1878,7 @@ mod tests { if dt != checkdate { // check for expected result panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate + "Date conversion failed for {date}\nReceived: {dt:?}\nExpected: {checkdate:?}" ); } } diff --git a/src/format/scan.rs b/src/format/scan.rs index 68958fd25f..49a4f123a5 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -386,8 +386,7 @@ mod tests { let actual = comment_2822(test_in).map(|(s, _)| s); assert_eq!( *expected, actual, - "{:?} expected to produce {:?}, but produced {:?}.", - test_in, expected, actual + "{test_in:?} expected to produce {expected:?}, but produced {actual:?}." ); } } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index cfe2a6beef..1449eee5a3 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -794,7 +794,7 @@ mod tests { 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); + 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]) diff --git a/src/lib.rs b/src/lib.rs index 80a70d464c..2beeb40a14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -634,7 +634,7 @@ pub mod serde { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SerdeError::InvalidTimestamp(ts) => { - write!(f, "value is not a legal timestamp: {}", ts) + write!(f, "value is not a legal timestamp: {ts}") } } } diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index bb1adfd762..78d507b4dc 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2228,7 +2228,7 @@ impl fmt::Debug for NaiveDate { write_hundreds(f, (year % 100) as u8)?; } else { // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}", year)?; + write!(f, "{year:+05}")?; } f.write_char('-')?; diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 35c9da24e7..516e4cc8b1 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -26,8 +26,7 @@ fn test_date_bounds() { let maxsecs = maxsecs + 86401; // also take care of DateTime assert!( maxsecs < (1 << MAX_BITS), - "The entire `NaiveDate` range somehow exceeds 2^{} seconds", - MAX_BITS + "The entire `NaiveDate` range somehow exceeds 2^{MAX_BITS} seconds" ); const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; @@ -607,29 +606,26 @@ fn test_date_from_str() { "+00007-2-18", ]; for &s in &valid { - eprintln!("test_date_from_str valid {:?}", s); + eprintln!("test_date_from_str valid {s:?}"); let d = match s.parse::() { Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), + Err(e) => panic!("parsing `{s}` has failed: {e}"), }; - eprintln!("d {:?} (NaiveDate)", d); - let s_ = format!("{:?}", d); - eprintln!("s_ {:?}", s_); + eprintln!("d {d:?} (NaiveDate)"); + let s_ = format!("{d:?}"); + eprintln!("s_ {s_:?}"); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") } }; - eprintln!("d_ {:?} (NaiveDate)", d_); + eprintln!("d_ {d_:?} (NaiveDate)"); assert!( d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" ); } @@ -653,7 +649,7 @@ fn test_date_from_str() { "9999999-9-9", // invalid year (out of bounds) ]; for &s in &invalid { - eprintln!("test_date_from_str invalid {:?}", s); + eprintln!("test_date_from_str invalid {s:?}"); assert!(s.parse::().is_err()); } } diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 75a168a719..d6148d6799 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -125,26 +125,23 @@ fn test_datetime_from_str() { " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { - eprintln!("test_parse_naivedatetime valid {:?}", s); + eprintln!("test_parse_naivedatetime valid {s:?}"); let d = match s.parse::() { Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), + Err(e) => panic!("parsing `{s}` has failed: {e}"), }; - let s_ = format!("{:?}", d); + let s_ = format!("{d:?}"); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") } }; assert!( d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" ); } @@ -175,7 +172,7 @@ fn test_datetime_from_str() { "+802701-123-12T12:12:12", // out-of-bound year, invalid month ]; for &s in &invalid { - eprintln!("test_datetime_from_str invalid {:?}", s); + eprintln!("test_datetime_from_str invalid {s:?}"); assert!(s.parse::().is_err()); } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 765a930be8..547dc46ad5 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -123,7 +123,7 @@ impl fmt::Debug for YearFlags { 0o07 => "FE".fmt(f), 0o16 => "G".fmt(f), 0o06 => "GF".fmt(f), - _ => write!(f, "YearFlags({})", flags), + _ => write!(f, "YearFlags({flags})"), } } } @@ -430,7 +430,7 @@ mod tests { let mdf = match Mdf::new(month, day, flags) { Some(mdf) => mdf, None if !expected => continue, - None => panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags), + None => panic!("Mdf::new({month}, {day}, {flags:?}) returned None"), }; assert!( @@ -541,7 +541,7 @@ mod tests { let mdf = match mdf.with_month(month) { Some(mdf) => mdf, None if month > 12 => continue, - None => panic!("failed to create Mdf with month {}", month), + None => panic!("failed to create Mdf with month {month}"), }; if mdf.valid() { @@ -554,7 +554,7 @@ mod tests { let mdf = match mdf.with_day(day) { Some(mdf) => mdf, None if day > 31 => continue, - None => panic!("failed to create Mdf with month {}", month), + None => panic!("failed to create Mdf with month {month}"), }; if mdf.valid() { diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index c572011aed..f2296a23a9 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -152,10 +152,10 @@ impl fmt::Debug for IsoWeek { let year = self.year(); let week = self.week(); if (0..=9999).contains(&year) { - write!(f, "{:04}-W{:02}", year, week) + write!(f, "{year:04}-W{week:02}") } else { // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}-W{:02}", year, week) + write!(f, "{year:+05}-W{week:02}") } } } @@ -176,13 +176,13 @@ mod tests { assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); #[cfg(feature = "alloc")] - assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); + assert_eq!(format!("{minweek:?}"), NaiveDate::MIN.format("%G-W%V").to_string()); assert_eq!(maxweek.year(), date::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); #[cfg(feature = "alloc")] - assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); + assert_eq!(format!("{maxweek:?}"), NaiveDate::MAX.format("%G-W%V").to_string()); } #[test] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index c1802bce13..8891587cfb 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1524,7 +1524,7 @@ impl fmt::Debug for NaiveTime { } else if nano % 1_000 == 0 { write!(f, ".{:06}", nano / 1_000) } else { - write!(f, ".{:09}", nano) + write!(f, ".{nano:09}") } } } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index a8754aebd6..d1df20cf1e 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -283,26 +283,23 @@ fn test_time_from_str() { "23:59:60.373929310237", ]; for &s in &valid { - eprintln!("test_time_parse_from_str valid {:?}", s); + eprintln!("test_time_parse_from_str valid {s:?}"); let d = match s.parse::() { Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), + Err(e) => panic!("parsing `{s}` has failed: {e}"), }; - let s_ = format!("{:?}", d); + let s_ = format!("{d:?}"); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}") } }; assert!( d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ + "`{s}` is parsed into `{d:?}`, but reparsed result \ + `{d_:?}` does not match" ); } @@ -329,7 +326,7 @@ fn test_time_from_str() { "09:08:00000000007", // invalid second / invalid fraction format ]; for &s in &invalid { - eprintln!("test_time_parse_from_str invalid {:?}", s); + eprintln!("test_time_parse_from_str invalid {s:?}"); assert!(s.parse::().is_err()); } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index f32f0f05e9..da6d4f2a6e 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -161,9 +161,9 @@ impl fmt::Debug for FixedOffset { let min = mins.rem_euclid(60); let hour = mins.div_euclid(60); if sec == 0 { - write!(f, "{}{:02}:{:02}", sign, hour, min) + write!(f, "{sign}{hour:02}:{min:02}") } else { - write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + write!(f, "{sign}{hour:02}:{min:02}:{sec:02}") } } } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 27b2ff736d..5a015d4ccb 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -340,8 +340,7 @@ mod tests { // but there are only two sensible options. assert!( timestr == "15:02:60" || timestr == "15:03:00", - "unexpected timestr {:?}", - timestr + "unexpected timestr {timestr:?}" ); } @@ -349,8 +348,7 @@ mod tests { let timestr = dt.time().to_string(); assert!( timestr == "15:02:03.234" || timestr == "15:02:04.234", - "unexpected timestr {:?}", - timestr + "unexpected timestr {timestr:?}" ); } } diff --git a/src/offset/local/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs index 780e15ace9..a8e53010b3 100644 --- a/src/offset/local/tz_info/mod.rs +++ b/src/offset/local/tz_info/mod.rs @@ -54,21 +54,21 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { - DateTime(error) => write!(f, "invalid date time: {}", error), + DateTime(error) => write!(f, "invalid date time: {error}"), FindLocalTimeType(error) => error.fmt(f), - LocalTimeType(error) => write!(f, "invalid local time type: {}", error), + LocalTimeType(error) => write!(f, "invalid local time type: {error}"), InvalidSlice(error) => error.fmt(f), - InvalidTzString(error) => write!(f, "invalid TZ string: {}", error), + InvalidTzString(error) => write!(f, "invalid TZ string: {error}"), InvalidTzFile(error) => error.fmt(f), Io(error) => error.fmt(f), OutOfRange(error) => error.fmt(f), ParseInt(error) => error.fmt(f), ProjectDateTime(error) => error.fmt(f), SystemTime(error) => error.fmt(f), - TransitionRule(error) => write!(f, "invalid transition rule: {}", error), - TimeZone(error) => write!(f, "invalid time zone: {}", error), + TransitionRule(error) => write!(f, "invalid transition rule: {error}"), + TimeZone(error) => write!(f, "invalid time zone: {error}"), UnsupportedTzFile(error) => error.fmt(f), - UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error), + UnsupportedTzString(error) => write!(f, "unsupported TZ string: {error}"), Utf8(error) => error.fmt(f), } } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index b5fb342b95..bddc9878d2 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -80,7 +80,7 @@ const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; fn fallback_timezone() -> Option { let tz_name = iana_time_zone::get_timezone().ok()?; #[cfg(not(target_os = "android"))] - let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?; + let bytes = fs::read(format!("{TZDB_LOCATION}/{tz_name}")).ok()?; #[cfg(target_os = "android")] let bytes = android_tzdata::find_tz_data(&tz_name).ok()?; TimeZone::from_tz_data(&bytes).ok() diff --git a/src/offset/mod.rs b/src/offset/mod.rs index f24a15dfa8..38380c131c 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -281,7 +281,7 @@ impl MappedLocalTime { MappedLocalTime::None => panic!("No such local time"), MappedLocalTime::Single(t) => t, MappedLocalTime::Ambiguous(t1, t2) => { - panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) + panic!("Ambiguous local time, ranging from {t1:?} to {t2:?}") } } } @@ -673,7 +673,7 @@ mod tests { MappedLocalTime::Single(dt) => { assert_eq!(dt.to_string(), *expected); } - e => panic!("Got {:?} instead of an okay answer", e), + e => panic!("Got {e:?} instead of an okay answer"), } } } diff --git a/src/time_delta.rs b/src/time_delta.rs index 0439778d42..34147707f3 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -587,7 +587,7 @@ impl fmt::Display for TimeDelta { // but we need to print it anyway. let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; - write!(f, "{}P", sign)?; + write!(f, "{sign}P")?; // Plenty of ways to encode an empty string. `P0D` is short and not too strange. if abs.secs == 0 && abs.nanos == 0 { return f.write_str("0D"); @@ -608,7 +608,7 @@ impl fmt::Display for TimeDelta { fraction_digits = div; figures -= 1; } - f.write_fmt(format_args!(".{:01$}", fraction_digits, figures))?; + f.write_fmt(format_args!(".{fraction_digits:0figures$}"))?; } f.write_str("S")?; Ok(()) diff --git a/src/traits.rs b/src/traits.rs index ada73b2258..450f0a2259 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -366,7 +366,7 @@ mod tests { /// /// Panics if `div` is not positive. fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); + assert!(div > 0, "in_between: nonpositive div = {div}"); let start = (start.div_euclid(div), start.rem_euclid(div)); let end = (end.div_euclid(div), end.rem_euclid(div)); // The lowest multiple of `div` greater than or equal to `start`, divided. @@ -390,16 +390,10 @@ mod tests { assert_eq!( jan1_year.num_days_from_ce(), num_days_from_ce(&jan1_year), - "on {:?}", - jan1_year + "on {jan1_year:?}" ); let mid_year = jan1_year + Days::new(133); - assert_eq!( - mid_year.num_days_from_ce(), - num_days_from_ce(&mid_year), - "on {:?}", - mid_year - ); + assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {mid_year:?}"); } } diff --git a/src/weekday.rs b/src/weekday.rs index d2ffa4b624..1554ca78b8 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -243,7 +243,7 @@ impl std::error::Error for ParseWeekdayError {} impl fmt::Display for ParseWeekdayError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_fmt(format_args!("{:?}", self)) + f.write_fmt(format_args!("{self:?}")) } } diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 849abc72f9..474034e276 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -37,11 +37,11 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { - chrono::MappedLocalTime::Ambiguous(a, b) => assert!( - format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str - ), + chrono::MappedLocalTime::Ambiguous(a, b) => { + assert!(format!("{a}\n") == date_command_str || format!("{b}\n") == date_command_str) + } chrono::MappedLocalTime::Single(a) => { - assert_eq!(format!("{}\n", a), date_command_str); + assert_eq!(format!("{a}\n"), date_command_str); } chrono::MappedLocalTime::None => { assert_eq!("", date_command_str); @@ -66,29 +66,26 @@ const DATE_PATH: &str = "/opt/freeware/bin/date"; fn assert_run_date_version() { // note environment variable `LANG` match std::env::var_os("LANG") { - Some(lang) => eprintln!("LANG: {:?}", lang), + Some(lang) => eprintln!("LANG: {lang:?}"), None => eprintln!("LANG not set"), } let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap(); let stdout = String::from_utf8(out.stdout).unwrap(); let stderr = String::from_utf8(out.stderr).unwrap(); // note the `date` binary version - eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr); - assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH); + eprintln!("command: {DATE_PATH:?} --version\nstdout: {stdout:?}\nstderr: {stderr:?}"); + assert!(out.status.success(), "command failed: {DATE_PATH:?} --version"); } #[test] fn try_verify_against_date_command() { if !path::Path::new(DATE_PATH).exists() { - eprintln!("date command {:?} not found, skipping", DATE_PATH); + eprintln!("date command {DATE_PATH:?} not found, skipping"); return; } assert_run_date_version(); - eprintln!( - "Run command {:?} for every hour from 1975 to 2077, skipping some years...", - DATE_PATH, - ); + eprintln!("Run command {DATE_PATH:?} for every hour from 1975 to 2077, skipping some years...",); let mut children = vec![]; for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() { @@ -135,7 +132,7 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim dt.minute(), dt.second() )) - .arg(format!("+{}", required_format)) + .arg(format!("+{required_format}")) .output() .unwrap(); @@ -152,7 +149,7 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim #[cfg(target_os = "linux")] fn try_verify_against_date_command_format() { if !path::Path::new(DATE_PATH).exists() { - eprintln!("date command {:?} not found, skipping", DATE_PATH); + eprintln!("date command {DATE_PATH:?} not found, skipping"); return; } assert_run_date_version(); diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index 9039a497ee..2e281571b9 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -23,6 +23,6 @@ fn gen_bindings() { similar_asserts::assert_eq!(existing, new); if !new.lines().eq(existing.lines()) { - panic!("generated file `{}` is changed.", output); + panic!("generated file `{output}` is changed."); } } From 23c132fc4fdd62a7297bec34be116e80b4344fc8 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 8 Jul 2025 22:17:34 +0800 Subject: [PATCH 938/999] Fix OpenHarmony's local timezone fetching and `tzdata` parsing --- Cargo.toml | 5 +- src/offset/local/mod.rs | 3 + src/offset/local/tz_data.rs | 267 +++++++++++++++++++++++++++ src/offset/local/tz_info/timezone.rs | 64 +------ src/offset/local/unix.rs | 8 +- tests/android/tzdata | Bin 0 -> 491837 bytes tests/ohos/tzdata | Bin 0 -> 272428 bytes 7 files changed, 277 insertions(+), 70 deletions(-) create mode 100644 src/offset/local/tz_data.rs create mode 100644 tests/android/tzdata create mode 100644 tests/ohos/tzdata diff --git a/Cargo.toml b/Cargo.toml index a65345ee2d..0fb5306d16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ alloc = [] libc = [] winapi = ["windows-link"] std = ["alloc"] -clock = ["winapi", "iana-time-zone", "android-tzdata", "now"] +clock = ["winapi", "iana-time-zone", "now"] now = ["std"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] @@ -57,9 +57,6 @@ windows-bindgen = { version = "0.62" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } -[target.'cfg(target_os = "android")'.dependencies] -android-tzdata = { version = "0.1.1", optional = true } - [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 5a015d4ccb..2b49e692ac 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -28,6 +28,9 @@ mod inner; #[allow(unreachable_pub)] mod win_bindings; +#[cfg(all(any(target_os = "android", target_env = "ohos", test), feature = "clock"))] +mod tz_data; + #[cfg(all( not(unix), not(windows), diff --git a/src/offset/local/tz_data.rs b/src/offset/local/tz_data.rs new file mode 100644 index 0000000000..3533e56d56 --- /dev/null +++ b/src/offset/local/tz_data.rs @@ -0,0 +1,267 @@ +//! Rust parser of ZoneInfoDb(`tzdata`) on Android and OpenHarmony +//! +//! Ported from: https://android.googlesource.com/platform/prebuilts/fullsdk/sources/+/refs/heads/androidx-appcompat-release/android-34/com/android/i18n/timezone/ZoneInfoDb.java +use std::{ + ffi::CStr, + fmt::Debug, + fs::File, + io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}, +}; + +/// Get timezone data from the `tzdata` file of HarmonyOS NEXT. +#[cfg(target_env = "ohos")] +pub(crate) fn for_zone(tz_string: &str) -> Result>> { + let mut file = File::open("/system/etc/zoneinfo/tzdata")?; + find_tz_data::(&mut file, tz_string.as_bytes()) +} + +/// Get timezone data from the `tzdata` file of Android. +#[cfg(target_os = "android")] +pub(crate) fn for_zone(tz_string: &str) -> Result>> { + let mut file = open_android_tz_data_file()?; + find_tz_data::(&mut file, tz_string.as_bytes()) +} + +/// Open the `tzdata` file of Android from the environment variables. +#[cfg(target_os = "android")] +fn open_android_tz_data_file() -> Result { + for (env_var, path) in + [("ANDROID_DATA", "/misc/zoneinfo"), ("ANDROID_ROOT", "/usr/share/zoneinfo")] + { + if let Ok(env_value) = std::env::var(env_var) { + if let Ok(file) = File::open(format!("{}{}/tzdata", env_value, path)) { + return Ok(file); + } + } + } + Err(Error::from(ErrorKind::NotFound)) +} + +/// Get timezone data from the `tzdata` file reader +#[cfg(any(test, target_env = "ohos", target_os = "android"))] +fn find_tz_data( + mut reader: impl Read + Seek, + tz_name: &[u8], +) -> Result>> { + let header = TzDataHeader::new(&mut reader)?; + let index = TzDataIndexes::new::(&mut reader, &header)?; + Ok(if let Some(entry) = index.find_timezone(tz_name) { + Some(index.find_tzdata(reader, &header, entry)?) + } else { + None + }) +} + +/// Header of the `tzdata` file. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct TzDataHeader { + version: [u8; 5], + index_offset: u32, + data_offset: u32, + zonetab_offset: u32, +} + +impl TzDataHeader { + /// Parse the header of the `tzdata` file. + fn new(mut data: impl Read) -> Result { + let version = { + let mut magic = [0; TZDATA_VERSION_LEN]; + data.read_exact(&mut magic)?; + if !magic.starts_with(b"tzdata") || magic[TZDATA_VERSION_LEN - 1] != 0 { + return Err(Error::new(ErrorKind::Other, "invalid tzdata header magic")); + } + let mut version = [0; 5]; + version.copy_from_slice(&magic[6..11]); + version + }; + + let mut offset = [0; 4]; + data.read_exact(&mut offset)?; + let index_offset = u32::from_be_bytes(offset); + data.read_exact(&mut offset)?; + let data_offset = u32::from_be_bytes(offset); + data.read_exact(&mut offset)?; + let zonetab_offset = u32::from_be_bytes(offset); + + Ok(Self { version, index_offset, data_offset, zonetab_offset }) + } +} + +/// Indexes of the `tzdata` file. +struct TzDataIndexes { + indexes: Vec, +} + +impl TzDataIndexes { + /// Create a new `TzDataIndexes` from the `tzdata` file reader. + fn new(mut reader: impl Read, header: &TzDataHeader) -> Result { + let mut buf = vec![0; header.data_offset.saturating_sub(header.index_offset) as usize]; + reader.read_exact(&mut buf)?; + // replace chunks with array_chunks when it's stable + Ok(TzDataIndexes { + indexes: buf + .chunks(ENTRY_LEN) + .filter_map(|chunk| { + from_bytes_until_nul(&chunk[..TZ_NAME_LEN]).map(|name| { + let name = name.to_bytes().to_vec().into_boxed_slice(); + let offset = u32::from_be_bytes( + chunk[TZ_NAME_LEN..TZ_NAME_LEN + 4].try_into().unwrap(), + ); + let length = u32::from_be_bytes( + chunk[TZ_NAME_LEN + 4..TZ_NAME_LEN + 8].try_into().unwrap(), + ); + TzDataIndex { name, offset, length } + }) + }) + .collect(), + }) + } + + /// Find a timezone by name. + fn find_timezone(&self, timezone: &[u8]) -> Option<&TzDataIndex> { + // timezones in tzdata are sorted by name. + self.indexes.binary_search_by_key(&timezone, |x| &x.name).map(|x| &self.indexes[x]).ok() + } + + /// Retrieve a chunk of timezone data by the index. + fn find_tzdata( + &self, + mut reader: impl Read + Seek, + header: &TzDataHeader, + index: &TzDataIndex, + ) -> Result> { + reader.seek(SeekFrom::Start(index.offset as u64 + header.data_offset as u64))?; + let mut buffer = vec![0; index.length as usize]; + reader.read_exact(&mut buffer)?; + Ok(buffer) + } +} + +/// Index entry of the `tzdata` file. +struct TzDataIndex { + name: Box<[u8]>, + offset: u32, + length: u32, +} + +/// TODO: Change this `CStr::from_bytes_until_nul` once MSRV was bumped above 1.72.0 +fn from_bytes_until_nul(bytes: &[u8]) -> Option<&CStr> { + let nul_pos = bytes.iter().position(|&b| b == 0)?; + // SAFETY: + // 1. nul_pos + 1 <= bytes.len() + // 2. We know there is a nul byte at nul_pos, so this slice (ending at the nul byte) is a well-formed C string. + Some(unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..=nul_pos]) }) +} + +/// Ohos tzdata index entry size: `name + offset + length` +#[cfg(any(test, target_env = "ohos"))] +const OHOS_ENTRY_LEN: usize = TZ_NAME_LEN + 2 * size_of::(); +/// Android tzdata index entry size: `name + offset + length + raw_utc_offset(legacy)`: +/// [reference](https://android.googlesource.com/platform/prebuilts/fullsdk/sources/+/refs/heads/androidx-appcompat-release/android-34/com/android/i18n/timezone/ZoneInfoDb.java#271) +#[cfg(any(test, target_os = "android"))] +const ANDROID_ENTRY_LEN: usize = TZ_NAME_LEN + 3 * size_of::(); +/// The database reserves 40 bytes for each id. +const TZ_NAME_LEN: usize = 40; +/// Size of the version string in the header of `tzdata` file. +/// e.g. `tzdata2024b\0` +const TZDATA_VERSION_LEN: usize = 12; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ohos_tzdata_header_and_index() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + assert_eq!(header.version, *b"2024a"); + assert_eq!(header.index_offset, 24); + assert_eq!(header.data_offset, 21240); + assert_eq!(header.zonetab_offset, 272428); + + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + assert_eq!(iter.indexes.len(), 442); + assert!(iter.find_timezone(b"Asia/Shanghai").is_some()); + assert!(iter.find_timezone(b"Pacific/Noumea").is_some()); + } + + #[test] + fn test_ohos_tzdata_loading() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + let timezone = iter.find_timezone(b"Asia/Shanghai").unwrap(); + let tzdata = iter.find_tzdata(&file, &header, timezone).unwrap(); + assert_eq!(tzdata.len(), 393); + } + + #[test] + fn test_invalid_tzdata_header() { + TzDataHeader::new(&b"tzdaaa2024aaaaaaaaaaaaaaa\0"[..]).unwrap_err(); + } + + #[test] + fn test_android_tzdata_header_and_index() { + let file = File::open("./tests/android/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + assert_eq!(header.version, *b"2021a"); + assert_eq!(header.index_offset, 24); + assert_eq!(header.data_offset, 30860); + assert_eq!(header.zonetab_offset, 491837); + + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + assert_eq!(iter.indexes.len(), 593); + assert!(iter.find_timezone(b"Asia/Shanghai").is_some()); + assert!(iter.find_timezone(b"Pacific/Noumea").is_some()); + } + + #[test] + fn test_android_tzdata_loading() { + let file = File::open("./tests/android/tzdata").unwrap(); + let header = TzDataHeader::new(&file).unwrap(); + let iter = TzDataIndexes::new::(&file, &header).unwrap(); + let timezone = iter.find_timezone(b"Asia/Shanghai").unwrap(); + let tzdata = iter.find_tzdata(&file, &header, timezone).unwrap(); + assert_eq!(tzdata.len(), 573); + } + + #[test] + fn test_ohos_tzdata_find() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + let tzdata = find_tz_data::(file, b"Asia/Shanghai").unwrap().unwrap(); + assert_eq!(tzdata.len(), 393); + } + + #[test] + fn test_ohos_tzdata_find_missing() { + let file = File::open("./tests/ohos/tzdata").unwrap(); + assert!(find_tz_data::(file, b"Asia/Sjasdfai").unwrap().is_none()); + } + + #[test] + fn test_android_tzdata_find() { + let file = File::open("./tests/android/tzdata").unwrap(); + let tzdata = find_tz_data::(file, b"Asia/Shanghai").unwrap().unwrap(); + assert_eq!(tzdata.len(), 573); + } + + #[test] + fn test_android_tzdata_find_missing() { + let file = File::open("./tests/android/tzdata").unwrap(); + assert!(find_tz_data::(file, b"Asia/S000000i").unwrap().is_none()); + } + + #[cfg(target_env = "ohos")] + #[test] + fn test_ohos_machine_tz_data_loading() { + let tzdata = for_zone(b"Asia/Shanghai").unwrap().unwrap(); + assert!(!tzdata.is_empty()); + } + + #[cfg(target_os = "android")] + #[test] + fn test_android_machine_tz_data_loading() { + let tzdata = for_zone(b"Asia/Shanghai").unwrap().unwrap(); + assert!(!tzdata.is_empty()); + } +} diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 9cd37f4ff1..6fa4ce1f21 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -8,8 +8,6 @@ use std::{cmp::Ordering, fmt, str}; use super::rule::{AlternateTime, TransitionRule}; use super::{DAYS_PER_WEEK, Error, SECONDS_PER_DAY, parser}; use crate::NaiveDateTime; -#[cfg(target_env = "ohos")] -use crate::offset::local::tz_info::parser::Cursor; /// Time zone #[derive(Debug, Clone, Eq, PartialEq)] @@ -47,19 +45,13 @@ impl TimeZone { } // attributes are not allowed on if blocks in Rust 1.38 - #[cfg(target_os = "android")] + #[cfg(any(target_os = "android", target_env = "ohos"))] { - if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) { + if let Ok(Some(bytes)) = crate::offset::local::tz_data::for_zone(tz_string) { return Self::from_tz_data(&bytes); } } - // ohos merge all file into tzdata since ver35 - #[cfg(target_env = "ohos")] - { - return Self::from_tz_data(&find_ohos_tz_data(tz_string)?); - } - let mut chars = tz_string.chars(); if chars.next() == Some(':') { return Self::from_file(&mut find_tz_file(chars.as_str())?); @@ -636,58 +628,6 @@ fn find_tz_file(path: impl AsRef) -> Result { } } -#[cfg(target_env = "ohos")] -fn from_tzdata_bytes(bytes: &mut Vec, tz_string: &str) -> Result, Error> { - const VERSION_SIZE: usize = 12; - const OFFSET_SIZE: usize = 4; - const INDEX_CHUNK_SIZE: usize = 48; - const ZONENAME_SIZE: usize = 40; - - let mut cursor = Cursor::new(&bytes); - // version head - let _ = cursor.read_exact(VERSION_SIZE)?; - let index_offset_offset = cursor.read_be_u32()?; - let data_offset_offset = cursor.read_be_u32()?; - // final offset - let _ = cursor.read_be_u32()?; - - cursor.seek_after(index_offset_offset as usize)?; - let mut idx = index_offset_offset; - while idx < data_offset_offset { - let index_buf = cursor.read_exact(ZONENAME_SIZE)?; - let offset = cursor.read_be_u32()?; - let length = cursor.read_be_u32()?; - let zone_name = str::from_utf8(index_buf)?.trim_end_matches('\0'); - if zone_name != tz_string { - idx += INDEX_CHUNK_SIZE as u32; - continue; - } - cursor.seek_after((data_offset_offset + offset) as usize)?; - return match cursor.read_exact(length as usize) { - Ok(result) => Ok(result.to_vec()), - Err(_err) => Err(Error::InvalidTzFile("invalid ohos tzdata chunk")), - }; - } - - Err(Error::InvalidTzString("cannot find tz string within tzdata")) -} - -#[cfg(target_env = "ohos")] -fn from_tzdata_file(file: &mut File, tz_string: &str) -> Result, Error> { - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - from_tzdata_bytes(&mut bytes, tz_string) -} - -#[cfg(target_env = "ohos")] -fn find_ohos_tz_data(tz_string: &str) -> Result, Error> { - const TZDATA_PATH: &str = "/system/etc/zoneinfo/tzdata"; - match File::open(TZDATA_PATH) { - Ok(mut file) => from_tzdata_file(&mut file, tz_string), - Err(err) => Err(err.into()), - } -} - // Possible system timezone directories #[cfg(unix)] const ZONE_INFO_DIRECTORIES: [&str; 4] = diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index bddc9878d2..890d550456 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -74,15 +74,15 @@ struct Cache { #[cfg(target_os = "aix")] const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo"; -#[cfg(not(any(target_os = "android", target_os = "aix")))] +#[cfg(not(any(target_os = "android", target_os = "aix", target_env = "ohos")))] const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; fn fallback_timezone() -> Option { let tz_name = iana_time_zone::get_timezone().ok()?; - #[cfg(not(target_os = "android"))] + #[cfg(not(any(target_os = "android", target_env = "ohos")))] let bytes = fs::read(format!("{TZDB_LOCATION}/{tz_name}")).ok()?; - #[cfg(target_os = "android")] - let bytes = android_tzdata::find_tz_data(&tz_name).ok()?; + #[cfg(any(target_os = "android", target_env = "ohos"))] + let bytes = crate::offset::local::tz_data::for_zone(&tz_name).ok()??; TimeZone::from_tz_data(&bytes).ok() } diff --git a/tests/android/tzdata b/tests/android/tzdata new file mode 100644 index 0000000000000000000000000000000000000000..ed1b3b061a2e9f425f9b2637c506a37699789687 GIT binary patch literal 491837 zcmeF431Ab|`nRWTT6Tzv2nZSxME0->iV`S06bda+P-#OO+CXVS+9DuA-1T0s2KBlk z2KNn&8-lpR6&05XE(nUj4R_;?i15A7Ntz~YW(wEs{_p2N^Gqi5=A1d_eShc7nKP4$ z#YOfCdmn3`-gZrv)|$5P60O16Cl8(NaTeNp4V~pInqx2HBg16{pH|xx7JAf|8Cb~v zror(xMMci?{Gqe#vve){uF2O_PH*jtVKSHFSlX#vf@gop-t}r^6@Y15-n@7nNwM6Xrn$%NXJ=TZm%@7X|Pc~ zeu`bH(p{opdtoPES2W z-SA>fYjAg5-ZJgw_E~aFg}Uyhnfqy4qbuTV9F-ONyR)O#M#`;WYP^lB%s$t%NPTGZ z5??>V?Q`uOqn)FlCE9rM9p(8s_ENijUUJ(yem#7SbC#>J!pP(5+PEquiR1}Hx7MlC z5vBPp5<#CniI-3~O@tx7428Ldp z#Ad9k#9mhBC>J*^R+*^#()AJ1stmSAk7sP9zOY5jy9Gt7HpJPCv(IzaQ`mLtXIzQh zQ{k%AaRHT5&ogVM<1Dt98hJsIG8pG9D=(24isn5T>iEHUd$G$I+|p8tDGfTWiKz$FS|PaHomf4qC1v_WGi)Svcw5y84FyU za?7&2D=Um`lB84a@EErXb^JPu7TYl7w7NE~V!O2VB?d{=@#`#Cna8!jX;gB3UXjua zQKHe9n?SoTOTTht&pPQ$klxNbN10JN{Wgi=miTm>Vqz@ub?Rq==;++S5?4h9U!=dp z`{=|lAee=z>RF*mG(U3vXM@)Sz&Ssjktf<80n9C-e<)m%WA5G!I zq0-k=9p56mk%8m71QzkX)u@~#B&zKfslbL8EeZEqICN*_&57|Fbr`35Y zt8f-q8u(~38_8Ft-D%+2lP7Chnl~YDo??e2Rc7xstkO~DlK#2V<47p|DVwF-#wWMQ zl=BMdY}e^w%{a=y#x>7XG}*bpUL^g-xC1n{@f5j|w5~Jf=%+JQx|zu$cGg<4u_iyB z@%H@5PS>2cT(O^~&Hh+yX0R6XomjbLIm(J$i|d$Zb8Y&0lSJ}I*`?E5tF?4pqHiPp zzNB5#aykFeJDwbSS^ijQ_Q-ksvaq54@nAE)vTg(}nI$$--pR@!x3aKuUfpCg?@MA+ zlH6vBv<~)o2Z(0ye2KT({#IYl>Vxyd__>61$}7tXovu1Z9!*4Wu~+(|EfZ`ioO4}s zlN?6OX!(`W=gUsCsi&z=q9McVB}tAumWZre{-KmX)A)3hO}?B87Rh;PLVQ-+T@}`} zjN=k*JhP-RNxFB$>V{TwJYy4V9HkDUo~5R(ED-HY(1T%)Qs-iQL6ooe-y?aOkdO{1 zW<7G`1Wm7wZrdYy>zZKWvgc2AmM4t@_S3XGI!fN~g;<;pa}~P`3}nx{?uhl{quM*n z4j(MISa(fJ4uN}*D|zcyV_vtN%XCQ`NMlu} z>5Um}e@5yjAx<-;Wv|c+H)Z#(SrB5ix}D3cbQ+A;vFE#@JL#f}@izF5QCZj3>*4k? z=}63#6V3%Xn*E{ZRO%z~=?u4@QEqrR+AdRV-HF)bOHt@`7QdV<+S@iE9Y@&$NAk*K zH(9P{Wk!{nl7?2b zi%(~S(^FaIaO($MO4ApmtZzxMDP`bEI&ChUf=FpWy?v2jQ&ujgSq7)vsh<@|-V)lZ zk#aJVKfzI|Td^r-(cOl@_;i%bH2Wf58A-_%9nCnWwvD~ik)+?oUb3XL5g$mEzEw^A zj4HJk>T5MNM}8}L^Cj50iYiL%(sj~XQcwL{#x18N+PKP%)9EP3()1q*Hna%&xs@Kh zGn$m%O(mUSi8jX7lg!^nRoaUjrLIa{?@8jLVpHLmXE4l_GH`+vUdM#IRoWNX=gVmm z85P6|{CY`(jl(m~RW4_Nda{@@Ia@NgF5YHLS&>r`>J=p?(usY!Bz|3OGp@{){Gd{1 zh~FgUt=c9_hDpnkwUH@B84^DU+FNat?WmCJGf9R?Q|1K4-`vTLC)ZJ0nxE;csMF_3 z@mM9DgtnwQovDrm$@@qt3!jnp=YnMEOm&tO(z!@1G}$v{sk9{-jBmv1v^pIbNRTty zLOx1dQqGh9L59S49hh(l%J+(o^{ zyW~7G$;fZYZ8GTISYjxNjf@gjI19ytY6ViteJ4pe;y-aV5(D{TL|XKgB(X3~x^Hqq z@0_DsM|JxyGHQ@*S2d=SP|72G+ipIsuG8_(dHUT-c1+nIPMC6Af(^qnWyNxaR>z~+ zO?H2*k)}0i$!aVeog!u*wInI)@vbu*p8RYN;gdY2x@C|hO{23`l~gAYN#}XS?7nBc zI&WDbZiZuADVq+FgL-sbsX2m)??eN)l~6PB{yVe4gwzNt-2FD5GM|`IUOT zq?A{O$X6xQX%?ela(PNOdnr{Xh*l-Uc9wmyy+Y1~lVwY7OL^ly(Vgb3$I4A-#Hr>x z>W)3hC>6a+hu-06DchfrzRUZG>B!lsk(Z`?xJr)arbHV#nzPp_Qa+Qh(e%p_ zZ5BEUT}fMnh-u2#o23jyuj0yps?i-gkR4Ngl=H@n%M)x|WrapvWi4gz9n!CwT+2op zIFJ6=e`>=$b?Q_kN-k&>IrQQowYfNBtvVGOkHfCp-O*CNlXkmVf*v4+ayiFi*K_S8 zem%in&%}V#meRLulp9|L6C7vcPj~6|EG699zbSdEO0aRdCFbUb{Go-q6ADsqmcmKBh%c!2oruj;M`?*pA*3#TL2MHIExXboSM?a+ z(X%;A%1!$*A)U%H850^Rop_yryVRvkq@Sn`wK{K;<U6TJT)bMRZpe^Ucg#+%tVsQgky%NO!Kdd+a6k)~}@?=J39{PO)(s4R)s9FJiTh8Km`ms% za1lA%URk>*9?P1R`k;(EC5}bs6uM*-a@HcbJX2J&W87Nm`j=|gPfm%`?QsRu0)bjDW}8eBF?p10f**E}6D&^?~WX>;<(PF@CQIA>SnXI4r#sVu2k>Q?Dj zr6!!4jzVjsY+e77CB6ja&D5)mP~be zL`ccF&XOdZX(i4Ihs<=)y%3hxRP;UZnx2fElsVlFY5#PVv_qw=n+)UAYOR`XpDQyi zlX^|$OVW;NDsh^UV8hw}8FR~Y8xPnPoaz$*n7!*Q2lPamey0|<)wIQ z*$kIC3i@%HW|c9AlrFVxDjAX=KE;z{WJBtwH@YX~AZ1XKH#vQtUnvbr@^&k&Pfzht z>HF5QF}nPbc9P7&(6-dJDa@*ri%o1zvSd6Skg~plPvY{HZ7-Dp)+D>DO=%~|oX+%r z)UuiE5*gK<;hQ$}OxgdQS~fW{QMDvL+m$@VljkjGk?RcoKusIgS<+ZtE1g`AtF)AD zNtd)?(w{KZKAx#^xy_~90HuwZDc==IM>(5}Y?T!+a>!Lcxw@G&87=)6shfsR@!`;N zr_91H(%ZAf-qiY9c@s%T*_6(+*BwQz*`Kwv(bDHj`CCS%92ZnJd1+$!dAyBlR`L*FAGM^N`jw<3<6PD0l$R9C z6-omIktFhe5-~k1-e#^`l(8r8fv9Ba+Q`)S`VJ^$B0+)6U8f!?@$&nx-@Q zp$^i{mOiW|W!g&%D=UnrhorqbT{ON|jg2&n`ijGDlC5)O+|sn2RaFL=B`)VI zduh4BFrSuo!53oqTD(nJ@qFoxvqfhybr74>lle57j+~myso6oTt`d0`fl01Mtg@>%3OsqW3@`VM#d;o^JD3Zlrku; zG@8I9dAsgm(O#+7Xx>K205NZjmb9C0mU4@Xb)pAMKQZj^$(Pmkozae2(t|ZhMe=q} ze@SNzJE=MyErYft&ipKAS&6P;+C!pkDQCpllojWXljc#^LehD7hGg)`IGY7VQmwuz z?a?E}@Sic8F&^nyme-?iPl%41zN@j3swtagEO6mFS!H zPMpoW!VJV8=@wnQ#AmY2B} z**yk+nzrL2DeISO(&6ST84H*v4#dV}OWFrhq^xCZxjJu^QagqbC&%;Q_hR!)oXr__ zqifFWSi3(eXEBD;zOYIASadh8e}IiqeI~b&nelI`u|az%JM?v-l*iQR$DSbMtMcI`3~JB?YowuJDd4j z<&FD2=2k8)a~OzR+MbW43~KxUn{wwYr=h>?KR=;np?J#IcwA%gAY&p+nhK!NQo0@f(`l;#rXD{N^Ogg^?88kIV z-+zteZIT4Gq2tJ&wfp3gn4npfe9lUB(kAmFF>F+m&Z5PON@X&rktdkAK4&^2W+Q{~ z#jbi9ZqxKmQU;b98|NZ>5i)78)Q;^OyYclrzN&UxpJSKPtWx8ih^fs;Ii6=QSALMg zF47Mq({X6kD}19WgIv1|qd9dFh;BA@yGjbYS)2{$Ov%Sz ztG~(AQ~HUi^J;8nNoTp-I3ZJSv*fMj{3q8T=co1b6HWbx$o!o?HR(9y_TU9hb%Ix& zm)fk|cgZc`cC*@hCB8QenJ*h^&gZak8{Km#d)KjX&0XY5{MtJ2Ot#Tdr~TO;sUMeq zT~-^LhKlCIwR2PE$hnzZE|FVMb+sq0R{JwW?({C3Wj9nG)5xjf2Q~d(u_=?$?h3tm zy5z5pji++nd}BAzYWKyalvnhapg+W?iorDYQb}wt>#E$QI>kXnu#MuUw#H8_l^aQ= z*)kY@(@Ya(ye=+&)Sz-b&6X)w&duW5tm)`zeSTd_N2fca%P02|$~lAXZQ`aGBP5-g zbByT@x!Jj%05atd6q|chxiL9NhOc`KUCf0uRjf&0Ooh8-4#tWEn_)8Pw9=q8G0i_$ z&KKMXHks0B89mIvv{;U%fpV6i)p^UbyB+yc9Ui?~QcUNZCh^leA)S$ShsR~$2&QxA zNIH>rt~#BOc0&^_nax-Uhb(R~Nm^>sg^wGDtxhLP<~TbIPR~sLI9tj(axPPCGug3d z?i{&Vpw|~MT_gQBlW4ZYLbXj~#Cw#(CD&(?wfUwMcggPeB-&KukCqFRMwh2#|3p8? zk)BAY!L<+54Wc2YZ7LlZo{-)f1Jar5M3Rlk7SkJF$(Wt=m*seBY-Db@T_!#yGmvej zo2+83p7OBaS_b$x_J-WLwhX z;hLt-H5tCfwC-7n9~rBNFN5KB&lv_?9@9gj)hQDaZAMfU8l7jD*1jz^UnJU$mK$O{ z6>LkoB+c9S(PNVC9NlPoMy?q(5M7Kvp7Bn(fU1AZ!1UD7lDGY}Y@`#LKU$`dM!&cg zlBSd8DCM3hy~e?`NxBcU{cS?dv|3-C@HT0ks+n8~yCu9yT0hw`8ONZ%YwK zPfaXLs&qKY%0<1C_hgg$wGxDc>?FKN8b3LUq<<;r5jsoKGRU>dGm^@ZY7SqM)OV&j zW%8s)-zHi8Op_bVbn>OrNmg#s5Er^6f^-xvF1BUP&SGb2FN4Wg(X`{{??i2eJL;P_ z7&mV)YBR!acrcPy+JbLXfDaXx?h}vYin9F7$ zqgvd3ZBZMUVL4mOigZIRelAYbCfhF4(wv3K4_|GKn@Is@;g$)R$0t%13o<)LA|$>n!Q1Gv$+nbm}wF(o;F7PdJ{w zI$s~#oSrtZwoSj-hdL|iX&YCRgyT8EXn#ISPiM>} z!De9nHn-Qd8C0)LhVxjSA_Uj(?xsH~eakZoq?*~2V_t6Gsv02O^M18bJ z@A?u-Zy;jwt!wI#LpDtGwn#0)0>?rE3vj@N^Yj%p{NgQ+mtcQUoIjW zS=O?dQCV8gAgp{@yiKLY<#zNMI*&eIk=}DEl}$kAf-WH|Bce9)93}npb9PK`(_K0+ zkv2GL!)S$EWYF(jrXRgWIu z_3?;Ju}4Od*pz%p@8Oa@k{YXtmVu1e)pNZhy?+xKb7;jER@=x8U{2}sEzld<&@#-O z#imVyO=WqxJY^wyzSx?6$}v(u%j0e2Dze-mZg3KmKIVQYH#H_yoi`bNbQ_sy>61kd z8p-%z&GA&0%XJpr0Xx|-WBDUeZptsC=}7lijEpWMr=Kc$Yjj>*I+^0Aa_g8uO6iUP zay-RxHZoJFzWRylCq{g9lsw;(XLq6F$(Qsq=So?t@tx@LjFQ`bu3wL-ca}`t9GPf3GJD1FXvE*@9M3o| z9f~JN4C&?VDmj+)@Ax#D&N!!IfzHm+&FL%hrB07eu#v}38XBeFa-nF**>N`GWz5p} zgf9KghH}2}uXvj|;ym)8ZQU@{>gs{aBQB~ZojT<U{xrEfE^pbcqGB1aGnlDa*Wa>vgrq@>CF#^@OD4;0Jdu3s zy=JtrNz94<#^Q8xeYg5*8OMApzH?@rjhY!?m{LY}xwdV3BEg2MKXQdm8rtMbvUEK5 zISzxTsV0fj9C^lcg`tXM^p?I$LO&$OId8UH1Cz&xB+r?Yb^lG`Z!hq1)DLpzy4-UN z>!)s==D23-j@m|2&N%5xsULM|pgNrjx!Ks&wB9b=zOQqOOl}CEcJQ z!Nw{5y@n5BUY$2*X_?b-0(Qpym&NANcpFz~vCE(ZmUI@MD&;0)W3hCm$$Un;p^j!O zm9lPpQJl>Td$Gqciw$~9UB8~eR2N1#7DS~KtZWdQRqlW2a zrq=ejzRRd#Jq~P0G2Z4t#u7;~?z~aPAL}1WtZP&M*iK!W`pz%w^DBwQY!=D$*$uN-rwk@Lagn-Dt)vs}L=IVXkSG7&tbHB#g-G>OJBl=x6&t}M;d;nvXSjEk|&Cbak zn0-L?bKjKe`iWlD?KRY0U%&T}agvy-znG0Yn9i_)h%2{hn_=>M4RWnI>FwUC5Hg;Z z15Z)qR&65UbKvoAg-)yZ0dP9P{wP^B%P!AwrZQm72zb`U%e^rdT`8Uw#;g* z{yZ5ow#ztDYFj>v+3=JFnFgslVwLgkeA#_Zyv<0tCBrq_z=<+;$hep${r{SDMmpy> zd9S-U%5^d(ql3y!h zQ}QL_BRL0*%;&4l8_!dfyHKTDp%dU4A8i$z#Jp7+Igad|@$m&xKPEC!osRUCq%mzEf;c5^P+a*>a~^vJ@iQ zGQPP>=98;y60vmT$EF-c4S2>6BPAVmo>gs=EwLlB-IG5{h?J6)fjnr!XarwuemY4k zAIChFjS$uh-RLQa1~En|=9L8=<-thSL`bBsH3N%TMl(_BVXnyh|u<##J| zE-g0GN%-{(f*ibhK`j*S_SI8Z*d}p#HW8ZmFKVCkHrQ@mRxp_(2 z#%cDsMl~wKTN)agkhf_Jav7azHPG&_Z8P2OCad~OeBR`NKsqx<6Yb;1PuKkt$Rs|pQuKM=F@Xh_M_TT)5Gkojen=H4ky}^CkkNf<$UtJZxV{0GFor_HF zyB>Mia`&khyYIRFGym$rkA~Nrb(H1a_F;Em<|Kb$|FPlwZEyPT-+4#)!48cr4?gvd zdu`gu{EtcXaVTI{KIJV;7%pdAw^c_Y-q`{$P_? z;U}{W_dogNuJF@6$5@_z^+@-#EtdG74NM3>7Y>Rn#vUAtftv~3z$N$0e zFJALeqn?(J?%dZpHRYiGrKOtU{z(Ln>ZTDj(tiisA$iM}Rm3#33L~0dWk7b3hyf;v^79fjA4qVHCw_ zAdaIb&I55EMR6jCBSD-A;!qH$f;bk$xfI30AWjBxG>Ef791h}i5XXZ!AH)F_#R(yf zs3^_|aY)7g2B$RCj)vm#{IPV9Ki1oNhi_Sz;eKv^hI?b)Bi@bM8u?$nyq~#B&e&h` zv@&m=(9!++xCZWzUfH63cUmBvy70+xTITcNX0N`bHD5hE++yJj&0I3weCUEq_hGY# zd0XZ$F&{oLUrRBirp5g;Wu(hLQ-*A9z)Ay?C|xR}`C4jkdcw`kk>_!0&Dh3kJ4?zT zY}Z;vJ|%#&5O{%KM&S&7`&5;+f5mhz$#%1O(Ms-FA8^&KJQ1}Qs5DL+UVLdp?R zmXPvIYIsEYuUEt|0XVsWbRj*IQ$$%pSeYgBAEr=;%R|4k9a z_DxuwSV=hGFg>ijBdxkL{3&{lk@TA>V7{H?4m<_JfTeAUv`?BQgQ;r0nO9bxzv98r z^Lx&$dSUO^p%*uPQnhK*nu3?!>r(adJv&3MJokOotC!6VRo%I$>b3I6;r>~kQM>2oLE&GX`qjI4`{UtXZ#~Dm z@6m7F`!854zsJ}qoKpO$H+9)`ciNctn#sPyo!;#xEhF=gaD%3AcpG*c;coagy+`=32p_R$wARLbbGXg+GreuU9~?gNan0NA4Y#}fjs3Kv*4`XG+FR_kT>X~& z82j~FhsCG4j~#B&I?n3s?$jyAd)(ls-JLTo)Q)feWBB-8k9fOkXNJ4JIKvYqA?4GuZOa#jhMrblHEjNA zq0GTks)kQGB{bsDU$aN{wuDB8=46jPBsDbp;X|s%gg*?8{nz^JaobV~#@ipO8h>$K zX#6dEvU4g2hH~z?xGHDcqXoGg&&fWhF3-Vqkm98`*?Q!=G_Aer%XhWXZE9!H8fhBq z1kMP=HF`r9oo2n*N$S(Y&RLIT-3{?=Me%Qlk3;+%;_DE9hxk0i?;*Yq@qb7RK;i%r z3yKmCkeE=ExPZh4Bt9T90*MnytU%%g5;Ksvfy53ZejqUfi6clXDM~y+VoFis3KCn8 z_=3b3B+j6UHCBi>7Ku4P+(BXw5`QejAS4c1h($;|LSj-;;*y2fRFwFH#3&?AA+ZXH zR~BLx61R}pg~TrlF${@g7GfC^&ybi_l(=RgwiPA5Au$e#b4aX1;+=(ct=m|tq zAi4t47Ddq)h{h<2&Oo#VqBjuDf#?oIdm#D)(IAKpL9_^>M-WYd=n_Pm6h)sP8l@;Y z1<@*qUO_YqqFaihU5cV#5Dill9a9u7ga6m`tg(n&k3M>xjdVF8{#a(pAL~u8n)LIE z=k~YsZp^#Kz47fYy_;9{32*Ozi?;o%&f#A=d2GKt^|ia<-1EE*pPLtM@<~&zseM$q z*$YFw%`g4d-D1lmuetNh?zRu#rXAJV6&^kGWAEq<9o%D&Z7W6phI`yQLGSp}KXPYX zz0NzK-zDLREByZKh8z6Zy|1%m7vJumwB#KBq^+C$lharFCzqD{b52<7&slSfKj)p1 zmfZ1E{8NrN%|B&H$TH<_lYeUW6Z}(uI>kRN=R*Iqv+uFw9iD5+d&}aVZu#CnJuk;U z{f7aT84suWXZ*Cna%!ib|Fl;!{ip3MvCOoYi|NXsxUjLIV^Ga7(Tunao zyH0FmalbvuKi?eid+z<&;@OpFskp%JuXylT%Yxiy{ssRm^)KxDkY!=s+x|tnJ6IO? zI@@yQD=+w$WE}55>(=>}v$p)(zs$44vh1#tEp?j7v^rZ3rg5yb@BaO}Kct%F41~r^ zokz)8M|3?hY|xvb1NC9#Mdu77-fNzbJ(bs>?RXBvdmtX9C|(5dB#1XDibp}bN>MzE zl<_V_@i2&&vEXSCZ-aOo#OolQ2k|}@JP_iAisFe7Z&VbIgm@*yGa=py@lc4DLOfMb zyj4*=7UH#v;<*s-RTK|~c(I~*vZ8o1#G@fzttg(YDBcb6a7FQQh^H%xw?jN$QM?}F z`HJHGiV^{kC{UD0fJ6f%A`~SmAd#Uc(E*7NMTrtfq$o(t^B*NsExiB7jgfp_a>}oM-d{2=lFv(<&Qb3#&5Y#pvJVcm@VT#T zB%jOIuUDTh-xbN{6&D_--e2+f`HKHO{S}KQ%fJ8mt$KfTx0BTSt7k>>d2P!3YWuZ= zBKf>->p#@z*JniXx#HHJ)cY0hM)J9GNyN`rJ`l;LKPTeX{<9g#TRJUM+i!hlqk4bq-bg-gzuu*`-@ZAL&pSP>)aQ5lBl*00$hqqM-3uc5T-~ah z+JALcB%f=(*sMOkw{0Y!fk%d@_knLB`Mlq^OYMLE6HV0i2WLj&=fQtR^0~I(FKYYR z(nvnnHMv>sziv<@pX=W?tM`wXBl+BL@55^U4Z9-weC&dJ_4#8DM)LVY&P(e36aS3l z^U1CeJ$N$j8nykYpF+z1>0Xh1KJ&^&YWuV4k$gUPYcKWrb6X<$eBQG{y?_4BNIqX2 zGhFTeqBoMymyZ5ieg4vfNIqZwGEcpKrDG(YuRf!x{a^jIquT!3@_Fk0YtKjWxuv9~ z+P>w+NIu^<NwazSXpgdjD2tB%fQ~uTuNJ-8PcXZR;1R&$sQ0c`FtiZmakHoy)xk z)lIv^XFht#*=9?r$9&9bh2{?9bIr#N9%1g-eSo=ByUyn0nzlA~PS5mp{-K}m_>VgI zy1dcK*Y(*3zHSfh_I1DIQ(uoOxA}UWx5?M5;!&TqXtl5R>2I6+cpfzOJ$Zw<-{h;z z{oCJQ9?)l@dBCsB%ma_S(l_v3yLr$r3w$R&mT5j|d!BFbZTY^FH=O7@<$|HUA=kI_ z*=8T>8+zU#^RSWWzRaQ{&BHtG@{P#+)jTr&1>dM+-!qT;>SObmv?tADUU}X;_Jfm2YBMnK}ECbA6Mh{lho8)ZxoHq12Z^OOiZ+63ne8ug3zT!`=_LcnV@j0JgYA z%!|Hy+qXDyoB7OFHu#oY`;_mjH8=Q{F1p3H?9ydE`f<_0jkqqIiGk)nQ z4GC^YfJ1^D66la%hXgz%=plg*34VwGD2fO`gg{Zm03rwwQGf^oL>wRj0TBs^P(Z{2 zA{Y?SfCvXfJRkz1C?Wz85=9Xch@e131tKgEae)X7L}VaB0}&gD;6OwNB0LcBfd~*p zgdjqsC}IQ=B#0XVg(T_h-g8C3nE?+0fUGbM936H%piiMD53@t zHi)=E1P&r{5TS#J9sHjYyvW5Kl(2hN-=6(@TDxcUw!&V0^?CrvZMEd)MAyxajt+;K zycY234Y>S0&~o4dEh)*a75uhsGJx){xhxlJC7V`XoRi*P!q|#VI~ZT}>#M7tc8cnk z_Zlvsc#tC^e~*Bq`YAWY0^#2yAb*d5;0J$?fc!lIf*<@DBOrf3v-8^;tpl|_*v9Q( zhdkn2y$q=g-Z%2f%Q+)oEqQ6pCEk};y4lkXh^TO;Vp^Q+| zXPRa=d*YK&^IL~hwYW3Azv9GNJfYxv*8r22ehbGJ=#7q^uz21t~K~ zxk1VfQhty!RFrarlqIA*A!Q0FS4i1H$`?|`kaC8UHKe>DWezEKNZCWmA5sU3QV)>2 zfYb-1P9XIHsT=r5*H2@qvV(VBRXu;T>rUVO;eomCQC>>&&v0GR`P+4)uKP#VkFo1Y z>k&KIu3kU%g1gfO%qM&rK(NsFVpTDG8(EiN7T5XA~Mx} zTAZrsXj*kDd|CDSp=cVZLQ;mL4*&BUG?wh>&omXaRx?b=NJ@0Q;W;Cp{a)T2bqQPB zSt2)#pw}!qN0ju7$hqN{9eBM+*{hOj>Xj9Le(8#|7iW~opt#IZ`;e1vc{R!F<${2OdmX#=%xy#C)sdR~5PP=gU97 z)H`F$Mds7GZjehoY4TZywDdCJvXEV6ku7qJqBg8ot4%~_h+0Bd=P;9|$zjeM{Hrx` zxR-kQ3etzaB@c$)&Jpv+Vv#@A4_Wo?lO5mPaG!a{v8xMqF5GP1xyupysMiqpM|Zvy z`h3E3;Z~OqD>$OzPwpf3bPKiqtgB3&&M#>D(!Jp$AN#PN-Ght6?Qc9Hbkr41+()1D zRe{AlR;Erb4Rx6Os!W|u3w7+58t&BTgo5L24~P3q_@4@K zvhzW2^g3_dSyr-z|I+I_P4bCvMe0C0v!aOVI#=wbjQAgO(5Z%`2T$=+0FXj}6a=I& z6s15Qg`y}011TIbMgc(zNl^+4Qdp1zgA^L1;2?zuDL_aeLJAU6n2-X66e^@(A%zPm zU`QcD3K~+_kOGGkItv94|B%8rp6JLq9)Bzyxfy@>8v}mtP8)U%9EjrfwLz_=8a7hM`sk%{IOlZhrAS?#TTA zw(br$pBO%N@bTe}-P?pawQC$!^Y+s_yN6XY3ug}Ys*765u4Jp5h)n6Rf7O?JYbYlu zszHw2M15O2dGs6`K4$f#S3euYqwSJDQdP;+>j!N6g!Mf zkCx)+Jyigy29|@lzW66pkMS)*Xn#ci<$`Q<{RpqD#E*|0mvMWj$0ZvJdOF*gd!6;E zW*xoH+`Bm4+voU@x$nqb-hPMd_4PmQ1#kavcKA-nxZ8WeYeC<@kN@Qzbl*+B6Q6T= zPg;JSZ}7dBXeZB~?K|bNGHu9=bImqawl?%chk4k zTNCrB-G_QdKi%0k=B@9&V{dNc8~5;=-tp&r?o+dSXT9v3c*b?!>|yu%CKWuaP42MV zmt(tD%Wbm8JjHURHsy1ld75^*Hf@u~oEI9RO}{(WJpHTL(zMG>uLnD^K_u3!N+>cO zBq}W(Bsr6lS6OeN!sIZe5gpw4uDg`2T67dWX@%g%^UfT%5K=KP ze)Nbhq>>;N1*t4Zg|Sd+kcxv;9;5;xl?bUwMX5|kg(^y=LMj$gxsVEmR5GNZS*UDC zg|krUkcwxa@*xhuf)hX-K~bCm;t-1B6cERNI0wW*AWi~t6o|9H*o0eg8s6hL5a)q7 z5DQKOaU>R;3F1(S;#3gFQWWQcI2a2~rug6HXvQZ)((&M?CAIjX>&JLyWz)sOt2Q6| zy?e7g+qP%(l)%1q^RxHgk!#bg$;nQ+Xn-wsVgKy3`ICbt`=qM$N&SKuL;F@W=-n#V z;9v8q8ku(o8?F8{yK#71uu16cs-|!MWNQ}OP}O|>+qM>WZp}7dyTNwI#gAkky66Vm zVa^rVEvGND9X@7hcB?^l+Ywy~vRijJE7+#du#dhqRAFUk+G_iFWyVZK!;a}Q1cYVa#d4Ho| zm&PlsU3PpP?E2YKYq!T=4tC#iU7*LxdxJe6SQ6;9ba~Kv#mqqO;x)ED<+ecIQ9fJ0 zoDPBhojtYzeNBNA8syprwmZf;aMu9apuK6<6F0WDowWTU>)=4Y;K>^{T2Jw{3J$sc zPOHteJ2>>bX9L3~ZVP4>-5wa;>(Std;TH!+eD}3&RFB)Nqqe+e8`JC}>zD@~u#Nq4 zj&(%{PApz_3$peE?HAq` znAPK*U}5nUfud%Q2OXm-1CB5K!Q#%-0>v+%6LdBh6mYIC3eMSeWMJ-9XWL4v23qGW zD72LY+FD&xN7&q#?X}Jy(Anm3?XZ@&YGkXJ7_?UG?-X1x@SVVd_ZkEjwthUY@bOQB zi+1}1i&t(6p1Jj$z>=k_gJ-QP3M`#@S#a4kBLgC|DX}g`P3I$SEu&$BKk=WY-Tgi% zK8|e8=F1CG8miZ(YKwFpWQ&}imKSKP_wPU3ozh;dw?W7!$dK}cY4Vqys|}O&Ve+uU zp*#(DbmXarv0hMQMMu=7t)SIV%kt>@F^Kq(r+!uAFlSvjVh!I4PAff~jCWtse1PUTj5TSyI6-2NgqNOOp1raYr5ip2|DT@hJWI}YPFs*>yOcFhP9$g zWUa9XdT*;JI`ioIsCjg~d-m?B`^ce2V$C0mPySdx-YYMgE*=n!^qGcP_iP^J-?y&N zw*QWiR_&S*wv>yGx27)aY)hLzJYceC2Gb`U7swdeDcGQQ!$5<7Cap#LR^R=t3hg^J*?a)P+Sr2nwYHK-tzV+}iZdhx0dF%-wq2L`FAdfkK$pgsTDt6bH_-Jnx242g>}tmYy51UXktZUF^5^DevR&JL(*3znqr-{+)}g1N!{vKcT@$ z>%ex0SqAPp-a6=)A1o(sY-~Mg`&*X5f#U)vZ&+_R#n&(}YP#pnBrngs%m zQN?nr=H-Fn&SU(=FINPd4Z8WAtEUF$>}uwpdsVr$w5qFR-hwICvOrUdYw8JB_hnyM z<_~CN^|-1m<%j=bt(bVfrDFdPfdvC!@h{l%b6{cXfPdlR+XIVsU+!PL^0B~~TPyra zmfjdRYu!};(wS!mmR&Q@|KHbVdS*BAS6)D$NqR-nXOdUxGsza|Gs*hlylQRt@4w8O za+F#>mOc|>V~8dziY`O68KTb+jfUtnM5`fs4WpX<-_~`CiuS@kRJv=l zJG#~wQ6Gf7A7B?MO-hhImVWZbdef`!s<5tWF($O`SDSzRH~p-S?5MD88@R^0?ejg> zciQX>yt96~<=tNnx9+%phvmI@ud{}7`&z!gdVk=D^qH0)gGJV#njhugJ*m5O_r_-} zdm3*J?73sN|CcW^tb6}?mcRWAms*cn_?G`@fB%4`@2mb}&URWmGUe?`z()0TRgI&%2CqJ{?ddh^bKl@yVb&_R}f0?v2`geQkej*Ry+FV85eGp2K zX7`P%>W6Ukkq2@0rL<(xX)2SJDJ>r53MpGfDPKq#L&_Oa){0WzkTO@4a)*>Xr2HXu z0I3H^T|nvsQYVmlfz*wn)DNVN6s4XZbp@#}j*~ir)ElJkAoT~SLr6VB>QYha6H=#& zQm>G@h14&kjv@67scS{4Z%CahO1(qs9#a1hAAtA)#1|m`0PzWkUqE~V;vaCC9ILJ; z#NCitYo7!1k;a3?Ury1T29Y7j^T%?E{ITBBD=#-)ewlUcnUie~w|yb7?x@Rb>t1(T zA35Tz;3E&+9N4h?KHH;Ly=8su?GJ;GFKirmVnb_NaK>eUCvUjH_S8vFTc2K96?~@M zkAY_kOtxqDb+v9BdO~nx=w9pdXJ2W1F>6)e#jh?1zLK@y`byx-;Fd1^1FvTm1;0PJ zq4oP4e+>SRUTXcZkJW~6=kL$3?b$LQ@XOQvgL@y%vHp6`WZS+K z{jK{i>1S(j^VmScjxPVPS3VTzXui^~;y1j&-}#sp*3MOV{w~@#)-Hh){ar&_tlh4i zW>InbMAyKmGa3a(y|c_Rde2RPF^}3U)B)Fp4(wmt2&O4LO}$H(2Xcvm$zwXJ=qPUt zYSK}Dql%8Q#IRY#u)0?#Q@X}Ito>9R_ptVnvaVTlB06a;Nrv}(R@^ZjQd^K3gVY+N z<`kv&AT_8cwFs$6NNqxD6jG~@nuXLZq=q5245?{IZ9{5YQEDAh^NLdY5D$QOfueW< z#2XaFBOqP@@eGJ}D2j(byaeJYisCI0k5Lq_fp`wYdmtVJ@gj&PLFG-Xh(}QycooF6 zAl?P>Fc?!5@igA!ZHnS?5U*1d&x3d$!~mi*Ug$$lFefak&b zv;=QtKRgoF)|USc@BCX!l50t|hWxgENmNTv6wwmtk^JdevRbvbfA>rN+xjI3rzN$u zy=v1T3oTS5L}1rZ;k7lEHad<`CGZ=|+@ER#At*fW{m+ZPW)FNVmW{_p4oVXPnH$stVcgtO)#lG9%@{FdF6Ymk*4H+aVjc9z56q zl0WFA=J$5fgHV?LthL`%^%Y-`MtgKAhaZQ7VZ^g z{NK3>Y&_b!OhWNs&q~wfcQd7)xcbWo$vuC9g2d_u<&Y7Q*WuqIB>yF+I57qJZDXv< zq)z{8UpxrCh@E!-&*(*cBP5U97#Nynwf&aSRnd-G693GfARn=+LN+`r?t2veEg%0S z^6^_oNW?9r#ny05_hD}VLNe;eVD%g{ zTfCB7vDZIp%jCf5Zx-0bJkT#N_O(3QG6~OvZ3$$QBytkUL^e7mPrg*2vP7n-amR+y z)HL1crb(*|W$~=I_d)o#!u&-FBW@>=qv3jr)x#y?g1oOi2@Q8-dmRN4*JL~H32{+~ zn?hU_E|a?cFLKv~x8TPjI6MlzTv9(Tq=CIY$nL zK8x8aFYAu@!I$1`o2|j#aG>$46~QJ~zZhtG-_OBj3-?)@U(v(XVn*+PxuPI=NKt0s z&?#5i4jcJ=pyj|ew!=H`wYF+AK6pf(SmDgBJHWfn)4jY#nkM1dbh^XY1JSBx|RkC)tiWye!Z;;|W{mA8)co zW~0e9;8!0Dc3a=b+WqdkZ9Uvg13fR<5$rYbtAMraP+RW-CDuOEdItNpxIfVE1aq+8 z?pLh?T74TFu=Df42|vCb9QfqRfkAJs4W78_-oQyuY_Sc#cdhm0`M%&OmtA8WGGn#P z=2~nWI{z!%u)g;MGAC8ph97omU_|fxZ6m&O2S&AhC8*A%_5_0JOlsTZ!EsNXWF3F| z{$SRs_SOj(?hH=!?zd*U_t_>*D78+`4%u>gPO#=$pR!G9ZnaJ|-(s8k)A+!&@CCNK zw|WMqSC!aiJZuh}y8gW2X;*z4IQ`n$!I=wQ59BWz9W2QI#%iD5H8^X?7Hi?4rop14 zAF?`He-(7>zuH=y+Qe4;-Xd$s&M$4wCr-7_dGb};+&j()l&-qZHt(Y8fwHr&u(?W3 z3b^Oo8=OD7eZVtzd9b|m{y;_7@?hm5?W_wLPYEsv@3Wrq*$KggRUzx57uy6E2cEK? z`M?0%65lP>v#w}uTk5{Ry3DgDcBUvp(tq7QliUFnMxW(X)~oM;(&P@P$bC?3Q4a=) zy8}v`H?|x=&oAcnXtMLdLwpeL=SzqHNEARK0TK<6h=4=|Br+h;0f`Vult3Z{5-pI3 zfkX`?av;&8C=mpSB1j}bq6rdFkf?%079_eL5eA7eNTflc4H9vXsDnfvB>Er`s3=hg zYv$@juZv4WvYn`eL?$FUArT6RQb?piq7@Rckf?=3uA)RQB!U$siXo8Htv)h)O_| z0-_cW#ek>=L^&Yp0Z|Z$ia?Y^QPc#YC=gYFC<{bgAPNIf8HmzA)CQtB5Y>Sw4@7++ z3ItIhh!QD^8bK6EQB(<{Ob~T~C=^7cAW8*MD~Mu2R12b95cPs67(~S&N(NCgh@vTq zszHxo;)PK zc@)SPx;XY;E#o&IM!Qx6R0?{WD%x(8qKaoJ)(frs4kvp2N z{HLYsXUziL=JxS-4?bk=G5Z-yPyf}{UL$Y&%}*|vaNX#@#4(p!mPsYly5n5`Ui4(2 z$o=WtKg7j;<1-9mwSZe(b=TZ>@c;K`7{u;47LOnjV-sq{GkA}8Ks*HEB@j=6cnid1 z;9vbr1iX%X;CXPF)bSthef6AgAzb{ioGyQ?x4w1lC+>T{EeoxkJ+Q zek(l5-q}03bbUByXd`d#`0K+{ET4O)c0VgTO?%m!cldSgywJVg=|3!S&j>E}p8Cd2 z_i1;o(N2HJ=AL=6Ps{&z2X}$fquH095}q|CS1X)p2^V!4pgB%S4bMJwvbVVXhvDLH z`*}-#eJ%QP(_xu@e%kTWob(f#G zL8};<<*sadgZ!?qGrVBWGHqdtap8sA?AoHQdxRH1lBu2fYK!oaoAbSAt@$Rr^xUD| zWtVOVN99GHCTs9dZk_&*{_Wp?gEVRK>f^K}(h^?g<+SB)f21YksjzIPJ3}gE%0>2_cRM zaYl$kDvDD=9Fqm-gg7V*P6}~U7MvC0uq-$&#Bm|c3vpnG6GI#s;>-|-Rurd(IJTlV zH^jjqP7ZN&h_kcc@DQhG!SNx^4+#KB5I_O}5)6=lfCL33Fcc*?AOQji5=fvxf`x^E zfdmZ;fddI176J$oL@WdnB$!wTC`eF20!vYX3ldNUwKJYi>tMexuUG5$8aiF#(tFcF@iJTo!c`6ekHnMs-(DiT7=X<)IgDzkTbAJIh0xe9z>)Wa|)8 z=km|Z&wjJZo`N^u9=mmGqjw6nuJ69}?N1*s*miC6t=l(m3%#>2y!GAt9}Vru+r0I? z%U6a%L%+`3>G6i%KjyW(4<^qFeVFn<-ba0ghd$c*@4Q_{+6#6)c}CtRzhoACx@vmf zXWKg!eEyHhtzT?tQ1IoPX_^9$kB@KTn}sizSe{R zj_MBGK@4!j{EO(t#~^+N@imCQL3|G4cM#u$_@AQqAjA(9#TOy|s3<-O@k@wrLi`ir zqYyuZ_$tI-AwCQ7TZr#M{8v$Y7~;o@;>!?!RurFx_%+10A^r{VafqKod>!KN5TA$m zJ;e7R{tt-(NE|3iEI{HxQDOoT7m5-akobVa2qaEmY#>SEh4;jaqQng(b`&LkATb1q zBS@ zkobheC?rlHu?mS-NX#lq+(KekQQ{X8!-^8ekXVMqGbE-VaV?sv5!;Fq-;fwr{67}w zs@JF2Gf9^t;*W)vg!PtQd0Dyc{DK=Fc{h90Mei3VRWA6lN~B8kN<6Xx1(Kee^yj1} z7keI{Y%#0rV9_33^+U4d>mtLdDLQ?-q`9CjW}ZAqPFERJJzv!gIpRIJg0UF*U2=E8 zeYg&CK+!(jve3E~)57b1oe)_6%`n>|JI=9g8~CVg+ZPRO@3i?k_|E!b>$|@kYuj=C z7uNUQy~7sD9cumlnr4xwK?XgxpPF|M?4ER@ji*8GX&egfxicg1%NH$ed;j^*K>HU~ z*p6EGQQ&BQX3*02-M}$tpJD6JTN`%);3xaYc0}B z^p%#P9~C5vPVL0^USmg2>0fo%vJj*M6r~g(B>^c7NQo#)sX$6bQA!6=LXc8|loX`2 zASDK4Pe+uJ<2|JZDM7{P(-A32-cy>85`~m1$4SXTN*7YXkWz+}G^Dg)?0TP+I`1iY zMJau_OuqZ~se$OV61`}RKL;XacyIQ~%QbmZ++EYkv%5Whx{cg$_9egZ`-qP!msb_2 zoOsFAkJyP<+>gS`_>II@4rK?7F~0sIpxBATIw0Un$zaD^_uK$eCgSHy&1M&d=0ESybbO@+SkY& z^fp?3pSf}PCU29_DqqvLpO8o1pX+PBew94x-fuQvd#*g<{v7k6iyZQ3`yzA8>7(S4 z_9M-$26d4~*&lCi-C=~cO{2!8=$uS;W()@8?&zOJ9i4;Mdv zv#M+4Q@j&TbNaHcvUn#A9_O39Ak~}Gu7@vo%7@Jxo)KK{l#_o`{; z(yHe2NcBPHvU|hwDD@-F?#ni7^ZWm5_P8FDN2kAMu9$e0RWn?!tzANLtYJ4}PHBz4~jj|fsmFt~Za=nxBUdDM@BBumEM+1_E9I-#s z)K*C?p~ri|4-$fqFoc96BphL^;g+!EJ)sE+Pe_PD!W61dWrc8Mk&p$1EeoLw313JE zL&6vm%8+nYl#qsmwW5SJB)lOZ4heG>LLCzBEQCBH>{$qXNcgiL0T3BrK?)#pz=9+| zWC0=#5P47(iGauiL@FS10g()dY(S&~A|DV5fyf98QUZ|^79<5CD~cj55P4A)iGj!r z3sM7-8;ImUWCtQW5cz?>`MUhH~T(TgU5ZPowIwA7Of`me3lm#h; z$SFipA+idQR*1YRio`->R#BuDBDabnx&M#7bAgko{Qvm4-#KFCZgN|TWn7lUVuaSZ zj%(|ZhOn)algmc7b^52IU*#yF^xH;B^s9^%E4599bRRZy-6l$Yq7D+dtpE4@jJ+hA z-BQ0U`|NAxJ!fw-=Q-cc=lh)JJkN6=whOUdi2YKqV2BM&h&?Nc zMMG>_QLGwb*NS4<5Zi`WH^jan77np-h?T>Cn4Omvmv1XBpU1mal#v?ZL75>BUN=~B z>?0#*E9zwPVfv{?_ga7YR?dM8x#ai0)xPL|UF{26I(cg86yXfNh)lH1%U21x$!VT` zF)#nD2r)-%Mp`h^nvrK|%}5I-YuuK6NAAZZ-J8|;sZH)-D(5nClhNS!`OR>L!`9~~ zts80HWM0bV(spo&*M-Io@0Rs4muAl;^*}2GO~BlmGiZdLqZ8h!pgHgxFX4-pDd-uZ zX~lwOfkr(PGz;b%)B|U7@Ygg8{;A%V&o%-^eZj14BQCs_J6qk^`j74htruI{S8rdE zqd}z2*WlA_j?imrIYZw#>J78W{NVYII~pIV;cIe>)7#Ydjx&7RR!1|>v(Dz%z2a~f zvz;w!4fm>fI>o0sT1Sp{s(Cv0aA%uO5`NOZ+J0_(Z-@8F`JyJ)^mg3vl(SRc3~%R0 z$~v9R0^TmOi#xBq`kaH&`dv?c?&#iUl&^c>j5oUCd}s99XB<74r?c>0M^EPI7)!jp zUz_6Vz>KZ{`P3^fSU$82g(|MgSxhL4*KtPjv@7`IfuO0$Pruk zj5BscX~)ogd!57XKP)%*yv;Y_=8qjSC9hnzxACVJ4)e1mJ;fi&Ib+WL{a@8noE017 zLE;1c-Fu3ONzsIjxP-)}qQoa8MinJaA+f3`@d}AqNZdkV7ZSga7>2|#B$grZ46|mk zNL;f`Y%6BYWRVzWn>dHL^=uRO{G8Z_#6Ls>5FJ3Y0MP?P6A)d%%$Y3cgKadTC^~^? zMN#wu(F{a45bZ$p1JMvfM-VMR^aRlqL{|`PLG%UDn4;(mqBTX)8$@%8qC1H8Ao_!7 z5TZjkQ)2pmt;Ne+GvGP_4@zTs@VfG|Yj(M-+_%l$A}-EqIq9^!RdkHidcYocL}(K$ zVuQ`yrsaov+Y&Wp2<1U5GV?s@L{X!|nzyZ}m1m8P>mRl{%}qBtFV-#RM4!=R)9 z*LGnP*)NTb4gO3ncD%Gac>3_uiyi8eL0y$pX8>|Wz*#`%SO_X5*G|t%)*LQI?eTZi zAfy%{H3_LrNR2{jRZ(gdQoE2Ercld@7q@by#`!+A4yk!a?L&?LCxIv zqkdR)xJUHxNA&9(tk6-rS_np>?JXzK_EtBd&6h&g`@23{c{1gN#fDlra$H%>HO4Oa z-o#k^?CMU)XMlVT`A9ZC3*_^_neww=`Yao%ekcle^6Y-&Yz7A6ry} z?7UROwyfKE25$ox{M%ekt~3ATPyW5^&3WDe@;3Z%ro1jxI8SfI9{s{yAxp@A<3Twr z4_;R_+ zeRE)+?eO*u){#vcY~Q~5h5p^jfMGrLj(&9R+s3h(&+5k~E;YWtakhS<-+jgpo#XYB z&2Kj75Bae|PvdmSIP3JOINOF0vm$Ph(%E@J&@2tXid?%N4 z%IZZ&l`)FBzSWC6j~FE!e!av)p++g~Wxdpk&&cJP`Ffeaqej`CUaOpMs!@L3BUXhK zkH}@2DOSZfQ{-~Y5b38JB9~zzt&o^Vx%^Vyx;m`7Ty|-zS1Wl|F1J+Es~@Xs)cE3z zUUSQ7qt;t{_1dfU742ghc7j5iW{zvDR-YSHoBue_Q=gf zTxcCV?xUVYe2GGO{EJPE@dx(nw=9UUP59)zb!$QsxeV}yHE~Ee>HdGmx;^4M>Gprt znjEr4y8CBaQ!v?K#70K~iMT_Ow$j_gOz!m&?0ZzK8{nERx}Y_#@1m)QV5CjaNea65^Q<--LK4#6KY(3h_}z z@luGNDvGBwWo(=JBh<8K$8{***ABT83#LpGQ(;>dDDBcb)HF=ot23 z_aWX7@qb7TP?S6X$pw&n0LclEya34!ko*A25s*9q$rX@%0m&JVyaCA_ko*D3A&@)* z$t8-CParu3l2;(P1(IJNIR=tvAh`yTZy-4bl6N4v2a*LGltL zH$n0fR5^+l#o_fp8ekQ_`Q z4?}V>CC_JMlA|f)X?Ug2$jIpw^17nrb_)4jQF1(mJP*nBkbDox`H;L1$^DT04`~BH z+5wQZ0Hi&jC~X2ryFgLe1{B%{iqb})&`yA~6)3b9AZ-Q;?FLBOfkOKM(uRPvBOq-F zNP7a(rhv376s2teXZM2!(bCq%A_BJpyTy zP-vGZO4|g|K2el53Z$K)C~Xx;dj-;FfwWs7Z5K%U1=5Cr1??ExF4-4%rG1mHc1-q6 z##YT*s+o)}n9-QqxLMBXc~FMQgV)jrcIo-jqn(M&> zGR-%_957+COz*wg9Mt=Hnb!NGIi%@>GM#s~85^?Phz-194n6UJG0gXrIegm$W5kNr zwHsdTYmA(etljvS!*ItN+Nh*HGCg;+cGJk_GA*~8HnvkmnU3o=<7yqZ#T~oWy!qTV znSNW}9KUz1?Ut8|n-eyr8Mn?mXx_H$Rb%4q@0(dv|SUm>EA`kwAudJj8AIIblF?YyH+wywx2ok;eEDXE1G4S zFW-%qzu!22{>cYvKgh-Z9IfM@41g?3k4)y&cG}08LmmkQN-mufmy8$^e|{#l^ZzfO zfqH2C-d}(6dR=xyMKL6jDMT|QqA5f*B(f<)HzdL-L^&kVDMULY;uR(8A(0P>eux4f zDu5^fq6UZ}AgX{UgMvCB3ZbA9h*Bu11)>;5Q4K^n6x0Jz5Cs)Mlmt-|L{Sh`L6iki z7erwYl|hsSQ5!^YilRD*@)SjV5Cu|DAw-E3)Cf@|1y#a7zcRD0OaJQG>J<&y(`OD= z=~J;yjJx96+gz2ZG;vp2=yHWF47eNJw#FZJ$J_3(w|Dw;&Q@R53Qv(|@f68RrPO(M zIecVbPr7$6oMcmplw1rXXUA@7`8BpHVj%Ie8~1^HEdHL4rYIi|@(~r~V?sVE+od{`?~wvdwp{8hwrtXBsYJ%`;0L>L}}N zsGE@;_17V92Yww)-K-v0 zJ`2e^ym7ce-%XQZ5)noFY?yUcfs`>vgKkxC2vxIY=H_#wvTU%_z z*edl&bX9J3+6<}pjajAA9`ouVADLGlPc^IVUujlL^O)7&nP=8`{SB?=3omK4o_tEH z{h+DUNxWOLPne`#GvX$#Ztp=_y{PV5{ibcS1~o&qhGna2p~bqIp(k3KjlODNhHa~2 zHr`mmZ1Tz}v+2_Rn&IU^bufy6G76qUrd$ucJ^=ISKovFIhbETO;V8)bchz z&$7y)Ty@}C_C@k!wx_nVMT@^m?`I)?z;iD3f5}>*-l-W1wF9XkNG(BX3Q}8&Qe%)> zgVY?P_8>I~sYOUlLTVFIqmWvK)GVZSAvFxCWk^j!Y8z7Hic;&4nupXrsrH* zE%wmH$y#W}5l>iYLkmm)#MAiDXst==Do@k2Br`nuQBSkgZnJsPRFC7)Yt0sMkJwvI zu5Y%Ao?>r3u(%l!I>g?lQ*VXOq&U~%4-*ndAVfTAll((v<( zRHG=$fv5+fAc%?}N`j~fq9}-}Aj*QM3!*TH${o+_h_d0|OWnVC zhMhCxbBT>@bty49q>H2V(QhuY4NSM56kFKN3n zws>~?Ol?p4YR}#kFPQ16k9j_y^RO96zQglHyl(DG8tT~}Gr;_EWCzbzVXe&r(XsZg zuj;QI3~g^ec&wFns6HwQB8hgY4{j%*6pzn!;J`)=h~k9GTc?daTe&#{?r zna3ykJm25=jCrEp)1DtX-)o+1KFfoL{8(X}=XA+g+UZmG*v}jsr=8s~)_!ht4=p1l z+J1g{W6icG%wFjJvRdJ3W$Z<6Z)6r773wKg$TG#j^R=#5);JSsf6eWIJ?EU#FR#U( z`{WsTf9+x~SIOfQXQz9Wp2WC#==P0qA?X(T$Q!vip zsB&D68rvK>$kBrwK}9)=kRu5>nvf$3IjWE&3pu)wBMdppkRuH_+K?j-IqHxj4>|gf z2vC$LfJB0#L<1xuAW;E{3`lf9A_Nj8kVt_<3nXG7Q3Ht_Nc2D=2ogn*NP1MIsFlZIFnAL>(mZ;GZM&gZj%i5bytuJAC z?yG4odE&pu(_=HtXJ!?*E$tpKm(E>w(Hvyjo%3x!R){rEmrVHeE4w@tQLwU$EL;4K zUfBiv{dInQ1-nqPazKv%DSkhnY!I9hEe&yLtjp^Q3%t-M=W3=}$mPPcXOU4b2jpd? zwJh~vG$V@!+t{d(ZIlR6BSeu9RYH^rQKzCP6y`o7IpogOV`i7W}feHQ4h(%RcC4U zzEOAh<5BZ%AKY*0>I}_&Ch%Fra%+ceN#IN?^T9I*I|R<|Xm6d{Tq{uA`MF-g@wU6f zL)G+B+ERCE+cSD;>ppjxz`c6eolghK`Qr5Q>t+QithmS09v>H|IA^SN)$|^LO7YQF z<)MuOAu(ZAm3CzVSBI6cs@4s2S1WnMs$RH^yZW)xdW|oRxNB}Xtk-($6L;-ZAM16N zta95EY}Pe*KkBYKc%M~o{K`Q6hz(YQp7R0?LzY>gjVA{}Gv3j|N)HZ%r9Z1TKGZhQ zBz3mlG;NSOJUL!(wz`eGc~URk@n}`|rRF`hX_@M7TcVs5Sz}iq^1ydiyECr`+Hcxo zb=aL8h+6rr-f_JV=rq@_cV0X?;GFof-euNI_qF}z>s`l;a(8QIHf|IY;lk-RbU=c#Gb5O-#BMlh4b4~{7`8Oc8lL_@V8pCfts7D&1V)Zo zXx*6HH{kB`iau)gE$-3b3-p`f`nboGo2HMAZtfm?WSAZoTG1W%QIsBE{J1;*#oGG# z1KZrUENE{{_~dBd)`XhYZ7b6P69;El`rKCow?_o5NfQ?aCWpLjP3bp1FeT%2Jt1;| zJ0bloeOlFN?rEvd=+jRPbKjYKuRdc(lshpgPQNRqwmWHLj6QRb&8-?xzwRz2SN^YL z8bE_!vne=)BRDyaX4AVqPNL;Gcn2`)v}7`9cWFFzmy_u3(wL&;<0LvuG&Erl4n#Z< z0l}=3XAu(HhzTMnh^Q1rSP*eR1jgqxJz93pav&>>=n z2wqV{4-vkih#z7A5EFnH0mKX-h5#`Ih%vytPUpoi_!MxD>^_AO@x=CI&Gwh?zkQ4Pt5#V}qC*#NZ$%2QfN`*+C2sVtNqc zQxx-q7@(q}_y-^|XLKTR*u`SXu?e#f4nELG7AL0J%W!K|JkQ5oAP4Wc%f z)iWfjV;ki`)CW-@M1_i?M3`I85z6G}sFQNJrF~H>zn9swgL2s}SlV|I)u2`nhXqUf zj-(pYD(0|YY2OM4^}uTC@GrZx@88O;^Vy@5vu(uqL$+mP`~5~rg>kl&b0d7~j=Eg0 zrJK%Ok?*^99j@ZqQ|+98&$_A3y=Pju(ifk0e!h2&D=?^^^ZP|L{3nXt>HNWGxK5UD z?>p7MhwIe4UpP;f{?>nbMP=Wa!*yL}|Mj@9)_c#pYEL`vtK;eKx3~Jncg@}Ry6Tqs z(%00t#T7m-;Oya^=^8MzsBfk?c#caOna3z6i?GH^szF|pQ*;@gw9XqmbDDk@NGuxKg?N96pTr*;^tL~ktfqGGQxzwkVMIhXSMD`LGguzc| z;p7qmmwY3tlsA(%weg0KH-)@0%-5U$;!GNPRC!S5%7fScjBzmiOPj^Jl=3hJucZAO zPbuwVv$X9aPif0*mID}R?auiQ-0k_t-^|z?Aqh=w2E^~`&H8?YXu`ob00}6 zn)uV4g%m0*1C}2A5q16n1D3MngMvqPOqdM|1}y!>2P}zA;Eh-X%p9_dWw4EHK&(Si z>;qyU5F3G53B*nymIARAh_yiM1r`hmE*KI_9OP$6@ZZ#We{r;4&L&hNsV~)5Tk!lX zRsZNaSJ#Wxy!H0!_6BW_c^iECuD$IKo4k=J_uAWizTeUASWkP0&F8&Q6U%#MN>aE? zt2XnTL(VpKA0&Cn+$K5O*p{_;WlJ?@l<>%PM$;whg@i=iz`y&wt~nmm561*@TomQl zK#mXO7(tGcq8uy8@q!#PI8*BEN*=e%tOT0#?C?Q(d$ubZN0+z0)O}9_$(`L_#7DY{ zy1#Q5b8U4McW!Z)aJ=FwXyK}z3oNuVR{JI2xg%wi+ z+T*eQigT_HTs6JDzfyd=K;@w|{UI?m0#zb2{8u+R7pPjdhO1i1&jZy9opV(`de&Xz zi_cv(x1_skz4ex>_9~yd&XQ+b_Nm7L>b!dJr-6Fom-y>PtO+#ed5^zg$f7`Kj3u44wex;4Mq-Mw3ctNZaM0@309{n2TOftYfw{4uLX26`R| z@%MUihP(InmaaaDH@N$*sqE@EqO;re*blD$QP;Q!Ox@ucShI+G(BRixgU)>A9uo1S zYsgoH0R z@`2k{ZuCzaYz6eW&-rhU*czBLagKj-$SZ*<{ciD3$=Kpfh`isGkiObIt?GE!wA9Dk z(@*tw-I;ucd&Z7tuEeCF?z>VnSJKE1?wJdZaen%zX^Tsr$%lAZ_S|>%S_VB_k-PF{ zX#{UWConH}<>mL#4@5&~9UVcm1kn>jQxIK2v<1VqC1H8Ao_!7 z5TZkf79o0sXcD4Jh&CbmglJSzbPCZbM6VFdLUaq!E=0dDtA|E(%r;tv=ozAEh^`^p zRup|hG_EK*hiDz5cZlX8x`${VqJM}7Kzsn=1rR@gcml*1Al?A+2Z%=~icdhi0^%1C z&w%&_#5*AV0r3!sk3hTx;wKPKf%poEA7Xdr_sxcZP0RzTu7&ksI5sUcPBm#guoeJ+^$a^LWa8g|^d)QY!KI>6($Bt6(_ShMEXvfa~ zuIRD5Vu{$@TQ_drlRhPO?}}S{q^Ayv{d`Wp9)aY@*e~K+^w^hFJ$8Rgr5<12a5na< zMkjh4h_1Th>#HhmKG^W|j)O;!Z$4Cf&yGV|wr&3AK-|9Tvscz5jgH;(CXqTfxiKXksX$I0g1V^1|2z4_FxTVj8#(9C_h zWH0~eQ#}J`4u<>B?r0h~x4D8pBc*)c{BuYBwuM%p(CjpS;XAelis;t=usyc@>@|1C zPpCUMw2H3`sm2+BoKLXvX7-VWpcK4ojbr*!WPn!*WCH#HMMD zCxj<=PHeWi?1bh?*CaX~`F28!xQ=IAPV!G^6j5uMhzKoowoS|V6WSKve>Sp) z_i*Hajc41PdE{{WP0yX}uzSkksFfQMJFXvcxYOKaiJccm9(GQ=Ke5ZK!4t0SH$JiJ zn6?wTHSe9+y<4>j-H#`ojSi1E9G&Jq8&mG;!!fI`J=^o}kB56bd1GSl?Nui9N$isN zcWjTziJjcNF#D^x+_O24UnNWAk*5LSfvwFRQ z*L!v_pjDnV)xwiW`|tW}a^@-LvNeO>;W9~I8Q$7ghPC#U;jMjTn5!6)S}mmullN6A z6a>$~m$_U^xuikSs2kZasy@W1AZ7(IEQo1Aj0<935Ceml7{tgRW(F}dh^ax04PtJJ zVsH?XgW02O4{T-|!-JR}#P}fQ2Qfg12||oeQOpoxh>Bv05MzXxBg7yfCJ8Z0h*?4m z6JnYWj1^+85QBx7EW~IbW(zS~i0ML%7h=8;1BRF| z#E2ngtSE*IF=dD`D~dTo3>sq65Tl05ta*W9Q^d3Z#tkuVh=Id@-pRoBv+Oqs1d>D` zi3E~NAPEJMR3M2(QIZQJ!6-_Sfg~D8vVkNVNYa5M9!TEBe#T+_&V$DU$2uqDWyHu$jZUf-|yg(tzbVG*^4cL(Ry# z&`g!6%PP6FOD`F&G(X~J*LFa{mVZ((%c+-6JrvAxs_Uj6e(fx$g4s*IY4*}z(R%VZ zKPl(6;ZhQ5Y?poN--q^;82o~}#77JK)tXLoSNr5kSLmT{1C2J1@OK+Dz}>x0ui$t` zu1?D`ANBFe-#F$m>&@{&J^*pghftIc0{JkC@_`^93i81q9}edG1ODQ;$fEKec~H3a z%4^M#r!`Bjt=go@S$~t;4>)Ja$7W@Ax!vZgGWbAl-*q$jLk0(zR5?Q}McQ9-M!19f zO-9I{2TLxw;*xJl-NesssLpkHbN&M#Kv6yf{M8@iG9zJgexS_PS1sB({r=K-y%H5V z^Nd5iv8sUgrcnVOsQZn|NQKg>zen9~RMxj{9ic`nPz{+S4^@!$j#L5f%HQ(Nkavf_ z{u5kgyp23*&1~m%LaOU z%gzq)E{}S{xqMG+@AEaEaz6i2d&hsy-tBzh&3TR&zun<{>A?)g%GbL)y)y?pR=xD3 z^W|Q;clEmR-Ai+PvSY)ox^tt$aBP|s<$PBg?bzJrHs9tS zlf3U&arxfg>Gpnb|2}uH*Zqh;ZR+;Gr_N9O+Xkluw%b?vKZ|%Gu*3GKe`iQyU}s>e ze^2@h_uds#T zvd90;fsKK~t5W?(=Pq*}n>ojIeB%A??{B=tb)w&R_Ya->xK1|j?LO5g-v8I>Q7;(b z@FyMNkU3f^Z~W7YhsfK<4JO+SZoKt(8aa{WZMZD{2JtwE&%ujFQs8-fAK!y`AH@G4 z9;hfj2=PKi@k59wDvB>cyb8d@hIq1~_%g(s6~&(+9u2cE z8XWPnjbB4N8{*p#?}qp{#KR#zt|(p(@pFi$Lwp_L?J(y^7`E|wh}T2>9^&~B-&Yjx zhxosufaC&5K7ix|NM3;C21tH@=#DAZLCV0?|OO44bIKO$N4v>^>@DCp@;wd*KhTG zSbwbR!zcUswrnWl+S=iSFYTElu1{-x<~!*w=0AD*a5GeU!gdbhy8@bEqq#);1TTBHNr> z>k5vTPV)yxOuy<6j+ox+3XYh5#id3}j~ng}j*x!PA6@@ySM;70u9zY}x?)zl{5|*Y zcJ+GlR#)#2Px|}ZJICMm&A$GAcLe;w5z>`hYJ_x+6RzM0>Caqhg!Grk{lO8^+x)>1 z(rf*}5z>$QgXa%3{J{~@i(J7G(s#OoBcw;Tf+M6mxq>64?XKVm>B6qy2>DjK}22f0f-ks`~cz!5MO|J1H>O7 z9s%(Qh*v=T0^%7E-+*`r#6KV&q9{HB@e+uiKs*KFD-dsi_zT2iAU*@}8i?OOJO|=C z5buHbkD_=G#D^eW1o0z?CqaA(;!O~Lf_N0fryyPh@hgaDL3|71T`+T0Hy*}z_NeaQ z0G;en-LI;@&mPsiRsDYUsP5os{j5>l;&uGL>`~hfs%?Ca@8f+C|5FqXg!mxD3n6|8 z@kEF(Lc9@XkJ_%}VmoWpwsuP}|v~wimhh`|MHMcdG5|QQITbcJ`?4PHH=Q)V5u1XOG%0thTd9ZP)hm|FcJJ z+thaUsO_L0vqx`~i6J!X&E4(c&`)OJvh*`v0DddwcR9n@p?sO@m| zdH?P{&vlFlNdOmCr)%zL+NXuO+j#R9x!qqhXAYGLzq1)X%=DlLc_0jG?J(BQqtFEosc6bjxT`~3NZr$}A@&T-@GxdyjWm*wHL;P**g znK!=XHch|c#@AUNoh*=_8(;JL>=q2}8iA}EJhO|C-GuBaWOpIE47nlp@37myINP;^ zye|*RP4eJ%O+%iNmo+t>dhJKub6KbY%8suA&=X32Ig72GF~-y`zBj^(ia+gSD1Qw>U87tIg|82 z@^Ise_(A%je^m^S*CBkLFkk5#b_$-8Hy)U?h&cRpZSm|+!~{YBuPzRvK48KRdw z^OJ>yi(i2+=?ra;wz99is@I||`HpBy#;T~n)`hB~u*`;GR~cO0l{JJ#X+K>@m|@kq zT#UP9xS^SmMKU~)u!DpiB>W&D2nj<)2}MXa!mMsh2}`yKO-Oh`LKG6FkWht$DQIF{F9>;xggqeiVZq9`3v}w? zpSbevUsU?}96OTJ5}uGF&308oo-WQnIWy0d$T(kJA7vegj}OnHrT#>^9%jEl>x(g>K*;)+_#PIZ=9{4J{4y>b1=p_yCcSSZgUeW zBc+M${PJ>^ZBaRq?srzPHW_tW$K=Q?mllDqh?RnM~5#pZi*YO zk12PbF*drRKK97XMqFqeJ?^8PMtq4vdi;w`jqwNe>$fb3k+XvH)~yLmWDe&S*2E#@ zWbWoW*6k7B$(+q+t;r!vtwUYRuTt zQ%_8qYTT95R8Jaty)ko9d0k2DlDRc!$850+O3eAa%e*djnV(Z<$KVC7K@sPm;2sbMq2MAACxN&L#8DuwqA1P+aTi5# z7z!=}aTWEwyS_uq`$3bqnXb_$8Tq%M$IS`4)FsD(DT6hQlS=D>-@r3gvkHYkLQzM?lp3hxZ6%g&=wH zw>Cg7K4Z`AjGWNr{jwSe62fffxq%>|&F^P55G2Ie&TJrD?hK#M7sbg%r3^Pj0uUL% z-0y{ynds-MzegVYes-eksdwC6to%6WJA{~+~c#W1Pkx28Vgod5HCr?!jFebET-i%wwXxiETR z8_hs;1JRD+U+i2tJH_U9F8P2;xdpeQtEf^g_v9qy{vB>d_jmE6eCFFya`<>qM$3cO zmpxOwlKJ*$z3o4{MqJQ~{qsI&vLre1=Re8ccO)yW$Xiy-dD582%f%PEsdKqxe96co zYy4fawPTfWt6lby^O!< zL@MX0mrS#C<}H2XF=ga!gi%&dT#0m%*?;(3`}49-9=f_EXyrmwLH``}@K3mxb>_*# zpITV?JTt&`c~Hj6BTp`zIdb%8BSG`iLzg)&=k{catM3>0&|U2E1-)rOSI@2GuYyv3 z_?v=LCfXnUA2@wh;9Uu)zj*BEWyf~@UfpdM4>gfMXa6#Xnq02C?Q)qaI}_!0!cfqS zg{%FOt`udO!=Kg+enWP;CBNC}4;f{8nUN7c&004GqSFel0c8ehHZO#0&U23Zzm`s} zPyPqFD=RF2kKFaQ8HV{AM_J^a>uwC-@yH4mT(bFRU9$NLSV%rIUNMPc9+YYF;B`o# zcwd97uBRH!S2u>O6hB6*JTzKH@m{A@X%{A=cN=L{>vlJ)l`5@OFC1!AKi1H!v9FX- zb8AVn)?42gwO1W7>n!=muqV{guDN@qQFript={+#WfblgT7#Z1$>`g6w9v+;Ei~hE zGpzI^nc4l8+4#^PnbG}>*)&Zz!jtbco2?#TG*5~%9gnuob=GIw5{wl|t*uCBso!bO8>CgdGSnHLjPm4%dEx5wf($i*D-e)-I_mQcJFqh z(f#;FEjm0=;%$X89Z*xLJTu^Y>4Lo+tY=vzx0p8lMSy4|YXkUB?3 z+rFaRn0$+jvh|yzX3sW8hrevz6c=xdDL3C78{NwodnCb(3k^5oK8iKtOH?r8Uup+^sS?_MA3p;wv&L_H%7YzXi4_8E4Fd$d`?T z^u6Y^s`HI$sc)LoPbC<4CND8(?1(iIlkPF^N@;H-jT~#vTvXFgXLfnK@k?HB5R;eR zBgjaZG6`3fDV=NBN~x*1<w&aw9B=8}@4-o)F1P~!W!~hWlL=+HVP!I=1AQVIb z5sIRS1tJ(l5e-B*6vP7&5JW@}Awk3h5fseNJY#+zu|WiE6 zI26S^AO@l+CIT@Mh?zhPg@UO-jD>=^Kn#Y0$v}*Tg4sX}M^Q`%VmyjsJ`e*^6cd6N z5yXrjh6FJsh%rIT3I1gbO3j>?RRk`qgK)Xg^rdBAc~GXygV)!5IAKX}zP$TP;AHbo zF4i5a6d!F>9@;n%5))=sX;(IIbyyjzYTYn*wUS4y>V?a=s~;<^*ZAUyyXKa|dabuU zao1k;v0i7%Dz`nsW?gglqwczc_gVGEuME_W*kCp2IWN#KWSJG(cyb^#;~hP$^x!~P z`m=iDLu~_1QfKQ;(+0W2ljHSftJ}DnC-u@<48KKOoYivDX?LsW7_0SwJ?@CmCRUr4 zsqVHV%2|;$b_F62WQYm8Z?)gF#p6cy?FY!x1&F9aEG}5oE4=(9nHfe;l!lnAffttm42cS^Mo!DE?WL`+%2DH+C@beJSbJt+QithmS09v>IT>;0ujF+cxOHyDxxaPe)17)Fuv)WuqA+`*`^##Dru%LG<>%I^F~E9aZ%DZg%|R$;|U zcJ1+b8mG5cO`oh)il1b!JoF}eNX#I6m3D2ltHavZtJWQ8Rx4H2UcGRHS-nYRt@^QR zy*2hV@YLK|-&^bL5}w*G7x&g#@?VcVp`GKJyFc*M9a6(lZ~Q@f{fKjp20h=mHw^jQ z5!(0#duYZPZ&>Mv?P2MAy^Rm)_9m%sdYh&_=m}3=;%&D2Hc#`Ud%TWEU7i+k9!JZ` zEj_KG|Ig8SU}aB4=om+vmOpsfmblIlS+kNo^6N&9c4to5+rL}d(P8&z_9$;dZ^!lP z?48V#-p-2`+no~+dArP7>$$eyhu*GZ9`|%>{*t$Qw;7)9$G>nyhcB{6r@iBdDR-wm zX7#gAsy=IJ-$UaxCGl&61GoOeJ%ZO_1(G2TH#Y@R`9 zn|Oyr?DGuys)r-C_IZ2k#>S4J8DH3kJzLf>JpCQ}h*{q{Zb*IBK5~rTabxmqySvX} z@2J_&ct(eR?7b=OUeB0vUhmlGIM3K4k9gxkV?1#mP4UK;XyS=~afo;P*X2C7EST(= z;BRcdHDR#hHg8$`#35}R`rL2rx3{V0m^9IEpB!?=F{R(j_9^GD_9jGr>`6%f(L1fG z*E213w|DxfM?7~Xzu}#+V~Qs+=_&7BDMLI-Bk%UkTomb%n7y#TS#X)N?B`;^?-&0A zZoHd)6JDDwO=i8TjvQ%E{@diPGx99$NqN@zej!;2>TaFe?#gSq?XJAO2D71u$0dzN zm$WEd(s&%oL%!Fgm%QlGOI}IIYDNZMv;zM_a9VgL{mfEWS93?PO8F$IV*K+FMR z5D=4q7zM;EAcmnRrU5YyMKKSEfj~?IVk8hVffx$JR3OFzF&BuzKuiW=G!V0a7!JgA zAjYF8<^wSxhzUWA2x3MMLxPwR#F!xF1TiRxNkNPXVpb5tQWVpI7?+}$7sS96#l#>+ z1~D^;p+QUyVr&p|gBTpd%obv} z5YvSiucDYQ#DEpWgds+(C}s>XWQZw4j2U9i5QB!8G`wKcG9LDlU&VvpEm5lEANZB& z)OVlx9sN4nurItSjgz;-)AQIRRzj+e2jyjX@VcfUPZuvRv2^lD+p|?F>hG89dx>Qx zKdYIxw3MEo%Ujq)bf&te%N>E#l32q`q>@rt#g}e1uh;qSXgH0=Vft< z-`d3g-*@|cXSF-?dZ3`&?=5{=)oJc&sn6)sPYrY5nS8H4V@H%bF)2>JE2XwOX=IE( zbCJ!hM)Pt3`4S6qWL;PO$!N;oHnuYVBUt87g7Wv9KMN7T@y;u6oQa+8cyiVJ~ z5%_&v0pbjbJ1)2K=V#I`l@&n6G2rjDlIPDRS6{TS7>>jHXRh>#^RS)gg~h~?_VVlox-<)mQ`SKrfZPVslu=cOMP;6NGYNPR??pBl3 z$Bd?FqxJCQJB((lyXno7h8m7X8|p3M66CzSq~0nzR@SvYq(_9dm-F-w^|mEy%J`Cl zR>39Z&-H}J1@46Oe6%fpsa=`X_{?f)Uiv#1Hvxacw#?$3@=L8px#ldrgK};8((A-a zM<8p(Ul82|QAie_e7Kpz< zJO<)36ubuFHxxVv;yV<)2jV{zJV;T7won+}0t;GG1+6LKfb)GtIp>GJx;6C=@a%lH zmvUZQ&U~#IQpYK^7kruW?Y2*6imyv2N)fpUvWQhZ%hjikn{xH(U7xM2S`$F7F->1qZ`Vy< zgXI1Gh9QZ*@CHR(;ftSis?U1SH~^`*wHv6owJ-3TJs6CNqr$=+5%RXdl7~vm`mj`z zi~x8Y@}VIg9P;5II{?`U$c`w=&hU=xkfQ7qWXB*o2iZZ$PC|B6QFa#2lz(>dJr(n{ z!yKiEP7%2srDF0PJSeZpgV&V}c}iZ^!}HYo5YuyCW6zQ&PMSF{@o&$VGGd4EqPZ*M z9dozO)b^x5Ywlg~f|;H=+x&da!)72k-uxn7H}@s=GWW*}FuxobZhjTk+B^{5Q~Ua= z{@TINrrN<{t+YcW%4>(VhG^d$u(ZRgPHIOsZPmV=w^RFW<+rAF`+Du@T)%m2=3D0R zi7%Vq-}sDqqThV;htBt!Cz~gjry4!2{a7K^JY8~@cKXyk_A>{^X=itgwV&JE<98l{ zFj&iV2*R<}o*MhEHfwIJ;;Hr4k7n&vr#y9*>^AKQP3+g){f1e0NI84G@w>G85#QMx z^n6`w7_!A4+BjJY%{bx-D{W|D>7RHS9~!MSNnPb>nwDgSCqL?Gw%TnrPnzm+JbJC! zBJL4;%gOc4R?$=Jtp^r2BSMGR+q67rwk;89kE~fti~PE}z1^9wwf39N+B@uiPmA(a z^>keSAFb2e)1JFV)j^%)iWT%{`v(-EK9zA5XJKhd-c2r@d;A zDK|ljS-sHS^GIK<*ORL~y|<4y`y@W*>AR-4*>A)h9@m0qX8)+6o&gD(Ik092&!EA_ z%t2>sd4@!MY7Y6Ty*;+}Q7v|3P5aP{G;P?@4Eyl(SG5td0`?nH7iuHNyluZRdAjEA zlkOQc`!RELxX*J_+#Tkaa!-54Mh`W|9+~Bd3+-UWeKgJ!U!s;7|6&i%_ygz7TNaG9 zPw>~&ZcT``-?lPCn>ZxQuFnll5wJGOIc@iRhW5C?&{2*gPsZUS)>h^s)H1>!Cchk>{Z#AzUIqbQC8aUDf*9*Fxu z90=k<5GR7T5yX)ot^{!=h&w?X3gS`_r-HZ@#IYc*1#vD#aW9C2L0k;tWbm&$o@1~^ z)a83Thqx$zhm%6w6ym55SA{q$#9bi{3vpS9(?Z-9;8RE;cIjijqYjnM6^t2_&OHvI-=#K(Y%Y!$7hOB-22$4J6}0vJNEkK(Y@c11U-t zf@C5^$wrWjq$pVll9?dc36h~8SqhS=AlV9%u^?FslDQz+3zESUC5u5anWAJfNJdkX ztOm(!kn9G@aF8qq$#jrx2miSlue2n>NEw!w$59(aq_TKW8q0&%Wfweub7D$`m_W)o zr)S;K){fWGr`UHzzU0{T&1uJ;YF~KwtQ%_IdnUw@zBt|f`Q9fSfkCb8-!DAxJyC3= z{RiI|$I0@wJ*WCNb)0&4qy2R0t=`kiPkGK9F77z{ue&_8-h0?ld)jB7I-Urxz11h4 zYwnJB)Gf2wQ!iHY*4wAs8?-&Jf!y7 zPmTcOC_s(`j+|C1w%bW6`X!787Wka6c2=8&Qc-JZJQcF9zD)v7WsQBqbSEUxa0+n9r z?+P0=&))d0Ualrx_Xb+N_nqt7SYM!P=Xg=tZASMxL*3G=pk{P&!d_IqWc-Ed;aFMK zCK2}^xp|xFFV7l(!FOo8_K^qEu;?#WQ?cZ8WR_ea@{*sQBOE`w=@`g<@ITlW$o@d~ zNm2F-vTusAe~^8I>?dSjA^Qv2XUKj-_8tCS`>z%W&Fv`W)TLcM#0BS?*)65Q^;XKc zzTS05uXVnb{(p{Lk*_;<9WLqIQ|&w7o^^4Ky=Uq<(-$9he7<+NGcc%&_Rdr9ra4ZR4){*5DDFLTxU%!?zy9T|_1=@t+S9)B*70=p*<0=P zUUT;y&bno`dz<>+afXlE>X<1tf0;OzsCf5vd}x~=ACev~6$5xsc5hcMNBxrf%h6u& z{LF5U{Z^EHhwMM34j}cQD0Km;4@IdHNWDPn22wweI)c;_q^=JUM-q;NrQGIQSx(l>UpcaQYWie_01-$S@OKyA zsquF^X`}Em02lbW5ZVRj#H^jZR>sEJ4 z**5N!bN5*v9J<}TW8N#4oO2tj3e$3O6{A)3IIZ=7Q$|E+53NnhUB=IrS~_u+*5!W1*QeitM9)I2SwXgwol$7HSNm&J@;DTB4%AJw(>S=3hR`(jaBziHJp z*MkRa{YOQZ114;?4eWBYIjHyZw!!uv%^^)6v|Vr8ZN`QyH(~>Cm_tuIU<~s;We(pq z!5FdPb?t^%`x+zXBx^T5<}lpxhBhjxk8O0!Xziwv&23}Cx@luORkV$}%5BEgI&O%4>JZOdLYCf@$Oso%HIxP92y+N5#Qjmhob z)23WE+?ZPZKUzZbX|{yZ4{6gX46{w!HBp=XU6k$4*ZONSKB;X>eCAg3u9eb@a8EyT z=EM7J7eXetd3)J2_}`l8WPlyz=i}jg#d>HIfX!n1UrR*hXg%^z=s4s1pz=rKtTu)F(`^4AfkW> z10oKHKp-N42n8Y*h+rV1p&%THcqj-6A|eVxf`~~`1O*Wl1z|zNML}Q?kwJt85gSBs z5Ya(|2N54cfDjQvga{F%q6iWqN<|SSM4S`^3K1yPvhFUjk(aa%B z4LO=cG;`#3o||)g++5j5wyPTQbn(2ZoMTWk7hV6cLQi$uLT*7GywR>-FCn)N=hoq+ z!Y zF=pK6n_GNydv9*-ZI|16b4%~c^963>%`LpSeK)u64wc(>bIa~4H{LWL*9~6C^LYlh z1?QIF(Q-R*ZUr7Aw*luC;D7h?4ApMAa3%OR-!1C5x2j|r`Mu;pIWv*-lgy4XCZfxC zBoD6h+GMC{W_KA~Wa`|7)r&4HX(U<*UhY-MB`01|M&!0pcf{NTkrRCCwI5ZE^xRio zZ7+FZx7yBanCZn!$Rj!aPv#vDksc-c=DvQNZDa_MqN2!=f+Qibgh&%2Pl!YzGNm9@ zh+HX179v{;(uK&Ef`lP5rXXdAoGD0JQDhAZhL66l>Z|>x0M(%^243lNQS69*c{~jV`7t_ib}m?cK}uhxat5L`&1Z zlGSHXn62*%C#-(c%1BfHGpqloP-DP^b=JTxrHnzn7h8kv-xx!h-ep~H`^bn5c|wm3 ztTcw6NYsb<<{87cjnqf1cuAVC&QCUh@r+OAR3bPl)fe66cAnJZX{&)jI-_0mx* z>7Fje%!kt~N!mqzhcC&}l0sCjRy~cuOI~Dh`W`u#z4V2yTr&10!-CA3+51R%NT|ph zl;Gv6zkTncge8U0goG!B5QT&(g;0frDKCyAy*8O5}AV37c(uL+n>N5ygIgzM|LN zIww-HmRB{bIOlj-R`F5`cc~@30{I6(gdJ-zVZA zQKu-82Z=sKi9kpcLL!k*S|rETgjH<-xW#Z%yR#O1ItQ zvrqlfuZA}c-sG=0{!w53h~@qUJ*WB_hCJX8ZG62iG~+o}Sm}1Yu=F{u#)oS7nxx+1 zYMK`53{URkYPPz%vw2c;m*bJM&K7aK{4FP?J6lDE`&$q2IU_&N&y&3(nydGU2V=fnlBF0*cOUfXY)tLvEV z&Th?zxw>}?b#_1gpg%git1mk3Hh)aH2ELfpE`QG>C49Y}yw%lvdwpl0#D1>6Yl=Jj zjcDO=J$BI9KdO>zz|{Ah18bac4I2D{bI_U3Ttgxrb`JUSxIeb`^S;;(+x$Z_9`p@c zy4F8D{Wjl-S&#c~NOk!}j+x=VF}bDB-Di<&)a-uF(cyQxZi;K+98+$DYix8S=h!2i zTyYIgIO9IDyW)#~=8S)_uxtE*byKWSj5S1e^c1m1}(y2k-Oib07EJ9z1*R-l5oYPWgyQZJ&u zVQ13FaM#R*Upi-sQwDR(Z_gr^`wudn@q<{>%)36$C$`C$c{P4s&CzBkV{p`JO9Duh zTvE#=0}ab3*!V*N4ia=QYuu*+(7ZSjbAch1oB$y!q4GC&UU@J;+LjoKUf2$b;8a4S6z-u5l%&-0ObowY{$OQIY=juhetCQM0=L zjrql#sb|jm-<=d#i|=wByAxOPpfsecn)#kG4-5q(d+ zv#!07HLP#1tK{DoTFCnDhTZ=C#lNxkpIGPre(zfAz}AKSgXkS_s?a~gUopC>bwfms|HgXf ztV*@2x+<60X;mq7&Q;~eS-tAlJ6$0gv-N7LSG%e&_31U{z2vfwJ8IRO{IIK5zt64O z!{_;L3SVK>>GqH;X7QXA+F+PJG-tV9KRD7~KYNzmV1Ipo!;FEcjW#zJ8J5-|wehmj zBb!XDnd*4@(8#8V?awwF;~&{Pvc}mKz26ua9$M&Z%Vsl1wkrP3*@&v%0}*@Io^5^R z=>u)nEjZiutK5#Qd)hDEg-nZY|uD)mX=yBn5Tyc8}S@G4={qbwQvHIt{;J8LkhDtmztdu!o))TEvyOp|PG+ ze2*(>v8E5-`=M*Z%tqG8&%W{Bol@4iXUSUssD1&%O#m_4~6LyHY3i*B?mNToVVi)hEq5>iYRw z8o8&RMT`Af=Kquay(~!5g?dCLgs6HXZ_OBWmxZR@D{%75omAXrp>DU3li!XqO+>Xq zx$pVZDVMb$>Xbp%52|HRup#!bB8VM9EJ;yp31Ur(Vowl@g4h(qsvvd+u`GyfL97d6 zUl0p}*cimh6vfUUmZm7S2C+7H;aLe{aqMGr5UYdO9mMh=wg<63i2W&w1ww2PVucVp zgjgcP79rLMu}6qSLTnOZl@PmxSSG|aA=U}8Pl$y=Y*bOK6k?|kONH1f#9ATt3b9y- z%|fgeVz&^>h1f2{dLi};v0#V|L#$X)>==GgS7>1{O^dn+8}l#I7Nh4Y6&A zbwlhMV&M=QR}?FU*g3?~A+`>&c8I-0EFNO>5UYpSJ;d@Mwhys>i2XxS03;1SQh}nR z14v4MqywfTRyd3Q?3a0!bx`l1?Bg1(H@EsRfc= zASnitW+15sl5QX=2aBsD?O6C_1J(i9|BLDCf@ zWkJ#wBy~a37bJx#N*aTtGDS&ekd&qk)C}|mzniVBI zLsB#(O+!*OBwa&NHY9Dsg4CUF>Q*-_N^ZZxGm0Y8o4jbZ%8TvN|J7R<|Naw;)YP`$ zKP&j(zJsj0bezhMX+&yTaKSB%zx@`*f=NUkv4OwpB%=S8ysq-}meu(^Ho%JpYw*(UEfAS9kS~_}8Y+j@LeTB7O0)6z{6z%hTU^Z@Kr~&u67)%$?|#D?nY92=g!v)+MH4|@+B4X<~wYd7z~hqu~KUO&cr@^n@E*_@jl=T4NbXPY_B zVaw`mFM8`^-lFT;m_akEdV_X;`CN&0bL*A7t*4{pS7YsEs$AzNGij<>_S;F0a)Xmh zZQE8y`3~L9^3%e+*EfC9tocD-N3GED_S*AydvDTan02}(dh2}K&K_E`oFmj9Z`KcP z=&kQYlhI~p1xX2%{U9Gz}y;_DpslE2IKb9`NEba!<tk9~6du*-is(zmg83zr&=AOC{yhuHD%6CG~X zPd2&DeX8EQ){o`dxlfnqZ=F6BA2_qGt#!4JZYyD5t)9*tY{g^di%JRU#jFHx$gjs>XX+14H=1upIsU!5E zVKd!#B*y3oWyZUChWfCBx49ET%j=07+PRa8AJdZ-S9cHJyG0){vrS;+XGg5NQ$hmw zEZJ<0>X#F6PhW104z~hhM$NLuR(wB@96P~E&bj)>YveA;#j;#~rayAueG19-;ws*1 zTav4MMN>oBGJMT&c319nlp$?tY7o0nes}&~R%xUJ)CN%;M0F75!3%@MqCoahAw-D~ zH9`~#Q6)r~ilR=4Lg63%C^J+|cjP^t9Cfq*XLvd}zemBN%nk?Whk{3$t+VI{+!7W% z%51z&Kk&d!_*);o@%MA|t9;bPrA~i9I#ShC9@$8HrMi3W+u!TvBR2QE7dGfG4%_8^ z>7nne`NLm#zZ||mf2GgIfdv((=?j~`7Fd|`vi0iq{|LOAJ=OZxF?Zm#j3jIE=H7wV z)4E$rmbD0YCx%%|=k#*Fk=RXNmfGC?W@ICM`GAV<6`^JIx7wa`rxy?CE30mEuiU#y zUsd#nz^Zj`>hJ9SBJl1Kzm>6OW#GN(Z&<$hvjeL~&9L5|@<8B&*c59`!r;K#Ch^v~ zm{j+N<=R-8VT0V6r*73hF5`56oYh+YfzO>gtS$Z41-9DXv%Uyl z7|62Cv$j=yEU?Xb$oev8f%_}pFl&4EH202059-+&BiuWuCFoXKjQi`PTl8HMo4CJ; zuBY!FRNlSk*2dP}$gse-tu$+2Xt}_5HypM07e5l%f8ulN`@Ne32ez)T4z61sIP~6T z{qT}a?!esT`jP2xx{pqpr5_tL(|vsK1pQjqra@0uR<&u##WMm0wdr6ha`XRQZF*HB zdVk3WIOLAy%R;1Szf$c=g{!Jvs@|U_ZRD1_`3wkM)&r?N2Nekz5HE!IA;c3Qz6kL~ zh(AI+65^8(uY~v|#4{DeHzD4sDE7F0ycFW65Ko2pD#TkM{tEF}h|fa27UH)M z&xQCd#CsLRe<2>MC_W7FVu&9@JQ?E45O0R~GsL4IJ`M3|h+jiI8{*p#?^YE5hIqK5 z_&CJN;f1pc{fzp5_&Q(5+adlA@py>OL%bg1_ln~A5Z{M*Kg9nbIRKIeAh`gN4B*(x% z``H(HOG=FkXJ6zm_6yFw`dRct{p^=j|1{X&O6@nyn5y<0ZSJe{_hD&CYQOQamTJGr z#O`X}@k}MP-!w5Xz`tuY=Cs;x9vQ9nTlC(p_QOLPs{NMDGSq&npfYMdqUx8HO zg0nCBfpIFug0n9-{V@JoJ^Jw9F3t#d zg2CcfRCy5EV5~)dgywvp(n0;;f^voaVAuh2SxiwbjX^GtK`xO&E|Wnnl|e3-K`xm= zE}KCvok1?2K`x;|E~6>Rr8LOpG)1|j1`EoSnW{fhvU4x)T&r^BFK&}pSxJ{t&3lctPXKGPjBky%}F2aI=`^5)An2+SD{B+Itz~vcNH1k+gEf*FMqLK zEquj0HuncLy}=js4R^ZKMYt_uApIV(m_bluQ;i1Wt!x~o#H2Yi)Fc5+oIJlI#IVFj(q(N5l~ zyXu%Bn{M(}dq2pm{zh?cjd|ah_LSC+nv*{UiepC24N1bjT+v`@`=?HD` znmshKmhbA~$HalUq3cAG+5pumf;@f(T)@OdOxA`#G(e|q^?CrdDz3o@6wBKx$ z@OGFz+wL5--`jD@TV|)&_1?}2&zoJEyzcGV<$km4v9BGGVb9qkH-F%WE_0tfdfCg4 zZU+b0yU$te?Xh*d8I$^wx95u6%-8`BdtEcznZ4R2dV8l-H~WM{d;7-O%)VzEdVg`j zswsyY12bN>4@&Sm2B%H6>oEtsL#DoD4h{R%dq?8KW?02X1bKK)CZ66ia%Hf`V$UeGdWyhFNe*4&pXB^3~ zZ`hO1-{?(=_|#0v{?R+WlGhxc@s)SNsi)2RWSO1&vy#o!iF3UVq{o>P2Tk@)dM?70 zyUq(0E%r-)ikArb5B{;)R@_6`G0er3;ITS4gUP#GJD0C$YFie&n(Ve!QcGUR#5CEi z$u;$!ay`ALj4<|;5k?*}-bqFs`*X`!ciB*@V2M=Ju%qy&jeN!*FP)I~l>B2g%aKCl z;v!hFn^a*U%7Yii!9;=Vqe7^Z$OdYpi6Q~2geViDPKZJwDupN&qE5d7lAkl#7!WM0&x|Hvq0Ph;xG`GfjAAs zZ6J;VaUF>BK->r7K#Jl*5GR7T5yX)ot^{!=h&w?X3gS`_r-HZ@#IYc*1#vD#aW9C2 zDT<3hoJ>*N4B}`ISA#ek#N8kc2XQ%w(?Q%0;&>3(gE*g}xF7t@i=l}-^8ay2h)Y78 z65^H+$Aq{h#5p1E32{(GaZ!kqDvFyz992QAcZE1C#AP8)3vpYB<3d~);=B;| zg*dRHxG=>y z&>v{r7oGd3t*c&Kt%&p=FB-F9*_J1w4ogTc*VdnYu9>m&NL~Be*>`GRMws?5zdvB# zUU{>*ePwrT$C)5|_H4hlbH@{Qt8X3c_^c!555-*C316K3WLeEP)vLPw)Q4|rr-Rp+ zrx)%w&U}B$e)jQEMzxPT_Uhw5GHRHiroH)Uqvqt>?X^n1ZPbo0Y}Vd2RIAf!w^`@& zm0GJ4>1IUwy;|#?TkNgBD`d3IJYu#RWiuv8-uRUvxV+&yS<~g~IrRRK0E_L*{dzw= zf3T^{#%{(ZmX=SlyKKLZv*;U0|0qfyLHY^OSCIZvls<#>o1*j`r2im&232xqL;4?b3?Rn=ax5UnLs5P z$T5N(Cq+3{kmCh8W^j`HF4yw7F$uTX&CRalIU+OV#|OzP#3OHMue34dt_t#)kAxWW zUii-Q$+Xf&?rB0L+v!u|v@`qSZD+G?)y`$MvE`(<*3K^skpiWvR%mLD+{1iMD>Ax@ zr|6K%Mlsh}PjTlNBgm2M33@WzD5?28B^NK%f&(vlN?G%a(%asXLgOK$?8*I*?l)@8+u*UMRMTosUgD|M@4Qxf_`bV_1<+32x#fF~w(f~V&ShdXw_?H)gHCwfFQMcii14 zq=u(&zpd`RXA61a!qeSxd#cOmz~_PZwM-#f5x8ysF1fGvxxj!aYa|)o7Z{ZAiX_1S z0X^n@&ycAT+(X0Wd+tcQ-JMY85zny5_U>T^@A4#u)^I0o=;lcZD&$UD+{nX2+(yid zmiuAP2kuU3DEGmB9T*i?Mv~VDfzjcIC274J7+Z0pB&(@`nHfe;nKi>C~gr6GcK)2~j3Qolq&14OB`K zr2^DSL$MImLX-MMG2#Q8q-~G!za|ISr*l)J{Y35Y^LAK1BUA z901}1G@Jn91`tPpxPqcM1H>I54gqlqh*Ln^0^%4D*MK+@&@2*gEbI0?i}6va^> zuA(T;0&y1_4g+x+h|@sa2I4pn*MZmkQEK8)d|zCOcC86Z?A{&(w8a@q*IpEODuV=e7NTwbXB@k*>6^ zl1imSK`m8KOOf0DxLWF258oe`fjAAsZ6J;VaUFQg7Y`SQqTy1o;E`_ywG?r{nJ?t* z7jgy+ISYoI2}90?;qN>n{)@SIK`r%X8kh2UI!<}g-6{4(Pv?YD?k-KHc)E7!z# zLwmL8BCECNPcyY{-@K%CfBjXh$NKk-n14K>^?Yrf5qr-)n(K*&j9#}sX7nC8%;?i< zq|vuWq|xujo<>}w`bOMIhY?@#7A^jZ>x}+C)YEQTaoo6lORzS;EUyiG^ME#J(lKrD zj88Rv@D^>z1N)7k9p2LJ7`Wa@sPViuto`f8oh6nViPa1vao;TCu5)9Iq|6D%@Ev`P z5esiOMy?yJ-Ti2LRNKwAGLAiZCXl`ibhIc zmo~oaNn^Z!jW*%XHse0;E86{^t};@ef8TiE^?w=@C(Jh{{bQ24=3l7jHF}=d)vWg} zGYCHOP^)b5Gx09>{Tqwvcf51m?Ba7_3Kf$0lQ*uBU1_v(>57P@5w(!Wg+wnTf+10? zD3J__W=KRsq8bv}km!a)I3&s;kq(J=NW?>;9uoPG=!Yl(q5_B#6h#dXML<*mQ3gaE z5QRWg0#OP?EfB>(R0B~CL_H7%K~w}$lA@>yq9{dC6+~GObwLycQ5i&O5Vb)R2T>hF zc@XtM6bMlvM2QeJLKLYes)Q(0QPc@hC`6?Yr9#vSQ7lBY5amMD3sEpc#SkS!)C^Iy zqNp08Y(-HwMBxyXLzE6tJ4Epi)kBmIQ9r~1AT9uL0*D(x90B4A5NA*ncYrtq#3dk3 z0dWh6V?bO3;v5k7fH(-mMIcTBaTAE6KwJglEQ;bT5Qk9|mw`A9#BCss192UQ^FZ7O z;y{X`)xVAl{rr(M;f)PTpO7%4Z@=B1zGurBapCJcaeEqR@zuYv#jh=^ z_0L%=*GB`|?b!?Dy67ftV8%4L9{Q#>IBkSn2lX36rcU(?4SU15BQeR7P-ccPEV8?2 z*ufMdF*MARxFOz13M%JGTHMAMzW0b{#LQc@k^ZtWC9<`4&ys*lg{-Q%r*D!~)X!;S zM!hLhA9rfWu`_MSIcJQNh&Mbb**lE!m1cOxXS`=jIF;hLFKwQ2e^$IFb>c(D1Loed%}uO3>@91tE{&-@SOgk0M4nf-Cu zh#|9G&EqurIln%l9i{Q_h%iW$K_X32q74#pG@=d?d9-UiJAf$V`x2>&60MMkRg|cO zL@xaQvje88|4$_I_e3)!q9IWYiEK!8Ln2&Jq8t+GiW2RRh*y-TheSS&=!YnPh6*4` zprHncB50@rq6~;SAPRw~1fmp0Q42&dilQ2bauh{95CuV01W^(VH9-_bLsbxE(NGse zVKh_*QJSKt4Wc+jQ5{5iilRP<0wF4dC=sGYh$11Xgnxcz28(Q4iCllfqXG)c2P`75 z(jIwx{P>W4g{10T?#3&YSEA}&>CK%t+gYkz7Ocr_`FH`}i5Km{cg72nrq)$6x32sr zs2joZ%TlG~`}3mRCoi_^zqF;d=h8==oowQ8o~n1d??3mbuL*ji&9}J1(r(uqFRSNj zGO@kRA{Q)h!QvJyY{8-yENH=E7A$1JA{G&QbHuYg4zyXfG0^s_G^^c`L+7nWT4$o#F1BFhuXtk}!`4w|TJp3m z_2jND+>RwL);&dJc`5k9HJ5DS2;385(KLO|Ar zP%OB~TlEiXFVi2azYJM}Sy9$uhOEU5S&tdACNpGRX2{yikoB1%YcxaFX@;!T3|X%k zvSzcQtlJDzB4+A}*YGCq0{gRF2^&c0K*9$SLXa?m7auYI zxy3#q1us5=@+q}{@e!0`)jnY+|4eTF=%@AxH})^C^Osjs^;KWLSW~rB`~SbD>Tk(^ zzhs@iwrW3boxfjuD zzKCB=?$od-Cuy{NG=14`gqDBto2dsp=9fV(9Z9J!KUs~@%zX91ed*0NoG zlk@HD2OVEVyyN`xKndse%7=a1S0*}koT=^1o_)ZvbH_rb)wiSL_;Y1^KNK73IN=-P zJXyAp_f)So&Ql+5cAO5je5V%`_ntXW!Fl%a$Gz1)n&YfKevh|?+1Y1rzQe0BP^qon zN%ApNn#^yifh7JlmFO%v`&CYvcb|20xjNcHbz8n2ciB%#$MH!apH@*mG2~N2J~`ym zD@qSQdO=Zo0@53h9)a`Jt=+R->E{OcR(@!Atq-}!xqjrg{w>Y>`L?7y?L4=!oipd@)86yzH~4I0 zwtEYWbNLE8qrFA?HS-m}Io4U+R>m7t>K$j$&cogkIoCT&zPHgEyyFLFsfC9e zrQiL+S>{o{qwGIdI?LVlhC_Q~i?4jt499hMzU8~VUW%i_EzkQZmVerNL*4s*Hyla! zRw_QwS7~FMxANW&zA8&2yj4FN;0$@HinrR5o1N9&XT3G1+nx4FMI1Fp6?WEYea2C{ z=Wgdsm3KJmMA&?FetgeS_vk5K=(c%|dMDTT>c9W4x4{>$_!_=A*W2i=M}1)rPWCpQ zHQd)^=p9~%XR5Plr>@>+dXlqw`!5_VoZX$_rMfs;mPvQED!#=VVJqj1_@soR^>+<@ zZC>5)X!}taU%SWFJK8Ti?7MmR>y8eOZuB|(eC+Lb*D_zH=C64>M?LH7a{WKNU7Nn) z?0U@YjVwRI8F{#{BkD+sGy0uTj&2*{o!!%I-X2TaIAhiYIeNMt^u^A3-RtVK&DSf{ z@b+$a!r5oQ7;oQFSRyyOqHZBtJ;>Z8sZ!<8*EaYVJea`C6BBh7NZdg=RR z0~z@wO|5no63~#Kh6FYwxFG=!335oFLxLR=@Q|Q~1U@AA6-59L5kQ0h5rd)#0wM}U z5e7sYPzi($L_!mx0K@_j3`8^#;XuR#5fDT~iXtS4m>`0JhzcSsh`1mEgNO_wG>F(B zf`f<-B0PxrAOeJlP*H>k5u>6A5+X{7Fd^cE2oxexh)^M7g$NcRT8MBV;)MtpB4R}m zGDOS}K|@3h5jI5J5P?HP4iP#;>=402L=O=@MEno~fS3Tp2q0zvF$9PyD2g#a%mHE$ z5R-ry1;i{Mh5<1Rh;cy717aW$6M+~B#7q>$P#~tFD8>RY7l^??Oa@{!5VL_84#adI z#se`Qhyg)N2x3GKGlCeBqL>oInBXJ{`M-@h{mMvE-oO^0d#b$Hui%llwDftNxo`id z9((i2qdA`3)mBOdO4vX1l+@mE2iulrMGzlWqjj2Wmi5OD7WZoSw|r` zP<~pnte+4UxIQUP)=h{ARE&<0^%ANCZmeHL)=6mPu3X}*tdCIHUFB#cPt~u_xI;Fc z_EcNF!(Dypc2A9Y@44+M<)rGG=dRW7h@ASq8@MTav#fV8H&D0Ya#`nKav(Hkqo;oG z9fA7U%RCMCcMUYmD7e%2KecMgAGc`AFV@tTp2^7bPU6Yqa*0mz_`gi@xNJ+-mUor4 z^Oi#CEQN1pDS&Beh^J6KXM>lO0_ktep{eyCl7Pqp@ynr={wK|vOTqOm9xWML>8i$g&ch@!Db6l9?&8jD3K%7RglMWYmD;V8)BQHrvF z6#T8X(*DJ5@|W)GxXkI!r32*mO0ThB@!Kcvv7KzvL+6pK1(m?xZvC_?zAK~X75~d$ zz3PJ^a|gSA%KYjAPa(O$BWLsFRV`4Gi8`H`oRc>p=i&^w-2a|W32@mMCbbjCkXVMq zGvBEo1*fPEZ8o-qvdrd@Qk6+-(R;tz4-ajl_FFbvul8F7g{b|As`~-^dL=_+&YrPpKXNq7Bp)hRJOy$T5H%2gtF2 z91le~CXnNzD8~kJd?3dNa-0<9SV4{#%_UZ5mG9gi1+UjXI{k_J#ZeC2!4wmti0W5=R2Pj<#sW9KxF$4W~wzD^qI z*)_4d@lA9W&+b8C#-93hJ$oa&Y2RMg*|smVk+$z>9ozSN1KNSDn{5ZzZPN1T(_cJ; zC?>ZB?M4_spSh*JqNz%#!p~=MsYPOcx+<Q z0y!v<0|PlYigJJ;2T4&56y#t*4jAO1K@J?`;6V-`|ad08o@5fCK_07$5-w2?|JHK!O7jAdnz|1PUZrAOQmj8c5(k zf(H^niV{SSK!OAl{I3U;%3k?h)xK2I7tRy6oSEz?I8XfBuCx7{>agEglaa1)N4c(> zJ3{=QI8P*wkzm1jV!^71gkR5s6{G|7!=HSp_Al#CS9zXz=_M|^92WNFxiOCz>E-UU zsku@|T&}mX4d<5;AG^LhaD!`m<#Yb+E5|u^oN4OHo_*T6bH@so)i>68{JEfou`8j`A;vZ;5&1mmh0@}&- zlOJ}~Dz)1erB8D8o>bH~NxU+j+Bf%VUy;Ic{^xUFG;$f_%bl_#rp`76F7p+`9Mq_J zEuWJ1vY#KnrVk+fpqMvhM*4$&`UKK1iqbcb{!x@Zg7g!luOR&e=`%>bLHZ8Te~>JEZU7B>7%{ME`SM7k+biel=iG z`O&;+HRQ#%#NmBW|LIw>QTk}-ulGR_nF5tg2wt2js|F&}9OK-jgj(=XRLdvyF8h(C z_a*f1i-LdmDYJAn9W+Tk>o14gRdx5JBM8ma0rm`%EQ`*h#u zRX_3;%qsow&noSIW2U43^iO@aoqW}C`zWt(z@kL&z~oflplQ+G!F>n$^rVK~AuXJ~ zq0wc$cT}k5OQ?6)n{d3S@6PK=JMP?3)0ueWkmIg*i#U@u`W?gP>~W4*`i5g<>W9v| zpPJ#gXTT!osQbKLce}@(qi=iKJEqP8-`FBqu=f z0wgyminshP<_GFHxfe>_o8R0hrnNlTsAZF8EgHqN_-QM@Q;AE5lgmp7vR~CBZ*TAH zq@OO)-G6$)2hQ^gj`(ez@6RkWb+f`N~s6Hh+$*=|&su#2R_ zygQ5KL&FAhH}y?r=jwhAfHQ-&qm|((d07%KBuC5R>A02`w?k`| zbA-P8jalDz#8H37d%f@Id~&0`YfK++SL>h|S^g16lyxl#|m_6RU(;K6Y zGkd-e<&BMg+U|O2n4_1ywb}cQNJk%ARkLsB`i{QeMcCtNmvY2?RK*@&_@E_Xxf62p)vcx`Aj1q92Hc zAUc9*38E*6rXae4XbYk*h{hB}XArF^iryfaQxx4nv``k`Ra_mKwuP_XFxjVd1gD~rDWrJac!+w|t9?ZmU>bhx=%{oQe4 z9r%2gfiEFJ$BQ;yUToL$$lE8=s+hUA@YdCG(j)EX7dFsr&(*gVdbG4wczh{)kV!eWm;vGwxL5>4P(373blG>+6$;I#4g9Bcpl(obxy={Y5#`lz2cI6VS+@ja* z+Vf9o<)<0;>n4oVu1^|cuh74rRx!G-{f5@9v>WTUv{$Ot$EaMglD$gdaHC4Y3R;z; zoxD|d)iFah-Q=zIevn!HjpE)K^S(3fDXkqfCx2qrimU3VJ$#@2rtou)I^90D*R8nI z5!&E2duYxXZ~fqZ*z0HS@HW`*wl~ap&)aD86J}W2Ja6M=_n1v4KIC;g<1(8jnvP~; zo0-icA9S?nQ^5=mO>neqcEW5Gbc-V*-P^Ux{btu=Uppehp0h`8{=gAk=01D$ zvX>p*4i2z)pR?NAW9xV`CiNw6&lR_su>&6Vx@NXBd$mjS_D-p8_6dph_KmZdea|-Z z#)a=P3;;Wyx$FFVR=%4eo{kE4&J8sYZz&>EgA;-Xsm+gZR{EoqCQ|)@p0q>Bh zFPTHbKK0&__^_E!#_JsxnP?6>__Q}MG}=tuknBwgYG@`cj`I%xwv0Jq=2*u_e*^p7 zDg7MxcuU(y#kF#{rysJ9ZduteW|ZGPw&EE_a_k%S#5|os`;zh&S*{(%N(Bb`a8{q>}n?DZ`8oYQjaPA7)N_6}M!lQ}! z00M-F5F$i~7$Jg$h!P@9MG+@Npo$_=h)^M7g$NcRT8MBV;)MtpB4UV;A!3FI8X{_l zup#1x2pl4Eh|m>9?200Ii0C20hln3y01y*^7y-l#Acg=j1&A>~%mHE$5R*_8qkx!& zq8J9mG$6(SF%O7=KuiQ;BoH%!7z)HxAjSeQ7l^??Oa@{!5VKJf!-1F%#CRa)12G_o z2|}i^F(`;hDT+}+%t}!V3u0P|Vq6gOf*2UY#2`inF*AsvK}-!| zY!GvU7#zgp6vgNuW~V5I2QfWGF+Pa-K@1RLf)FEwm?6XvA*Kj1Mu<5=3=(3J5Tk^c zrJ@)n#55JfI3eb#CMGF`Er9Z_>1pNp92j#^p&&$Z#;R+`el!J2E2K9z4goQ96De{Oz`@*Zs{^0z0ra7E9=%B z@b(QQd#ozgY5S@lzVGqQ;hVO+^?r!j8(Asq+w00_?yGw`Yu}M$nfr@x z&)UCnOXl}`GqMgWeJk_eIy39gdl^xOm%JMvm}^EInLaoE=%fduj*Xfee|&I4)DN+D z#GmMJOVr6GUE@#H8=CoJxzPC2CAwsuKGiwv%)YvrXS3>Loy#ndnUfxrb$-GAOxvvQ zvI>epFOAsfGlueSQ_sOn2s#@CoPC(1tVv*R@<4^61mZ|U*c z!`+|X6#mTdI^Fc$bt{fL9@?PO?$Dg4lj{fHw7Y(Ga&m+H#dkN%h)Zs?xpqQWT10Z= zWyKPjOstaZc>3Ffrim?&HyiU&Li5N<$6NINS3-E*)5lvjdorO_@$JVWsxI0cu{Yy* z>obqcz5LH{>P)sXuG4AwLRYLz|q~^=fo%X*jg?jCbdm+&lP2&Vh2RT zyPiE9)vH~V_}=3-M)j$BHokAaWl??4WXH#aKN}Ud`*c=(^`)8dYqn?g&v_>Ew)q)Z zw`Y&b95BVq8kliw=AeWJvj(TN&eUV(#t)eq7d13&a{L{M5m5d9sfXj z$*75gTEtJ9^?lS84T4_0ey6sI7yH>4E?nOGU0)CSUH|*aeg|30dQ!3q_Qt0-S^0%jy!9BSzUbVcsS6OO4Lt&To zP0H9o?OPmx_$S0eAwCN6Qiz{IJQd=r5O0O}E5u_VJ`3?$h~Gjy7vj4R?^P84g?KQ; zhap}J@neW5Lwp(H%@BWvcr?VPAzlsfYlvqi835dT&b4~O_T#LFRmt|*=k@pXu| zL;M}$@erSfcs<1LA)XKMeTer%{2!776eSNpasj+>9wIq`eewb%H$d_OBu7B<1SD5L z@`a-03`pKkl-vQyACMdZ$s>?l0?8+koC3)!klX^vFOVDq$up2#1Iagvl5-$=M^SPQ zB>zBi5F`&lauFmSL2?o#FF|q>BtJoN6eLeUaup7m|AwCI3Qlu%hH)NG^utV@OVh1hU8~Rj)vrENUnzDYe>$9s4$0+^d=AO!kh~7b?U4Kq$?=dp56ShAd=JU_kh~Ac{fd(RAvJ)a)B%uMKvC)e zNKF8#3m~-tq&|Su2#`7fQY%2}1xU>RsT&}*gQCIg_Jp(ynPq^5w>6_DBj zQeQx73`m^;sWl+=2BhYI)E$u815$rLY7j-KLm;(?qSPahnnY3R5=d>J~`t0;yjhHH@OvF_2nDQR*2;O`|Av4Wzb#)HjeC2U6!iY8^>8oNLP3oSzL|;FWQarVp|p@m{}=rL4to^iFD@X!eca#OSnl5z z0yy;4C$_^&4jF;bi)9F~vLR*eWflRDxo9u!qa6GX-1>jvW0J)9sPN~v{^xsZy>tQp z{oo<749i;nc|A8+2#hcxW$nU_5##LpuOV97oy^H;Y?N-eJ zvt70B%dl#%CJpFyW!|c9(=cxI5$0?&-onjx+J16vUb!l>!-nC1(tLw2BtjMrl{>aU#R&<%> z{^(_ctZoM@`n%7$U+=NCnJXrBpx$#u1y}5V4!Y~v6RuwEYU;hmWx4u<6w&+kd)w9b z%pN^1e2yz_Pa!M5db&S;%{Ny6oEQAJ&0lNXo}KC+FlB)?Fk_H^P{K58aGKMv$GoBs znL5xlH0)9Rj>HbGgfheRVUaam!w&Y)6GMx*5;ru~lZx+gB`wzU;d?)HjhNZU8u{5b z{<~AkTK6nj>mSuGV7aF+@Q)7PWQ`d$%|Eu{n^tn{2!C?UMm;6sQCCX#GJSld;jZx+ z&*~FS^>E#ncCUVaR%2J{#QypN>6&Zeptky?Sw~%0bozYtbNTZAhn(k3A$~93Fh~6I zsKkp?Ve>iTyfl6+-A_<5r zAku)y10oTKOdwK$$OR%9h-?%^IuQ9Nii99CQWPmcn23WvZ9fJ3jG^c6{G71ZfITFy0Lz# zK&4vs^~xm<2C5V;rB^u`?5_ItK|N&S0e7|4pXt??e(J6eriu?1>Qenzj=Z&x7q zacye?b-MAmwpRk74R~DJ2X3CBRX_V>H_y&$m@(Cz`?$6wcjIL|uC2TK${rJZIH2w+ zD0ob;dwiww`uL2O+!IdSrr($Lu>1b3c6#c>ME3*f)%A&kqTQ38v+1hf{l$w~zjVRt zn*N=PD)!B-vYBnbY6I%NkJ9R0>RMS^MlG<#Dx<70+Es>EyUH*tH-FG_2Uzo+W{5O+ z(Jn}ZOgHAxgN-1Pfyf449Lp2=&_qH289}53krPBx5LrQ_1(6p-Vu~U&h}0l*gGde{ zJBaik@`FeaB14E2A##LB5+X~8G$HbYNE9MdMUg5*t`NyWWDAilM7|ISLu3q*GQ9Yx zyftbcS@U(I4UxB^NE}{#RNl*K|Kg+armB5p&%X;xOH%vDpM5Lvo2dO^2X9mRf5*oU{|#;RDjyqosog#+(ZGJCmsgzdESeDO zUUD=wu;R`H_ghspTYB)(z$g2k%zfm^^KZMqsM_C__0*ez+yxzb8@UTQwlH!RbnK-u zNU`r|b8UZ6S#AHOike#3ap_5ob@&cFwN3lo^CpkBFstsm(FoaegIR6$k4E*Sr_37j zzB2474ed22ziZTrD`T%c{7dbo@Wb{x-QLmaR@`V0ZIGsg<{UKZ2Ya;o*`JvW_7Bw> zW-K)uZJua^r9ERdUZxvOCXO>5&vY`HCO&O%Huff?d1SJ^MW5nEcxarxWwU)otDp#b zL`X3$;@c|r)@Q!e+N?WkZ~N6pT03tgv;C@nX*W+lZFZRbq~;v8-RwAJk$w{c6k2hGTvVvWdCbM4VZn;OwslkMGhUvG4uv&`(Vb+{3e`mEV=MGqr( zz`dqxW@DpQyZ&bH6wT-p($?(T@2JuDY&A13{Bt92PaAuD^&?vR+7NsHoXy&8^KQd%cyzJC9_)h1~Kiz{`=AQ<&%ybX6%%w9YWjlAVP^n|oj7h0s@@7qn zP%acR`JA<1R$SC92n-@Jh|nNng9r{HI*9P#g<3}h$UY*32oYXfG_$wC{>4QzTd4hu zi)QxH`1`z(M1;ydVpSBu!V4pdB3$+nFGRq6{QvrVN-+U`4~zg}1`tDlm;%HYAm#uu z2#85Qi~?d75W|3&2E;fN#XKMeq9`T;F%pQGKnw+9DiC9Vm>!2*F+GU!DT?_)3{X)_5MqRiVulbygqR}47$N2e zF-V9>LW~k(mJq{)m?p$HA?67&P(?9Oh>sq65Tk~eHT>HfcCfg4gv@)t!ttA8 z(kHxV-^q*Z3Lbe&OMk{a_wDa>b86$vc`qz=zBr?J;H7?xobw+U7R?-I|LS# z?rAN&rDkB^0f+T!$Vcv1x5ViGI{Pp8Ys;JHi?=`Ne*M|<`jU4>xxEv^tfg~$x!*|a zrY}ov?tU|}k-mIDMfZx(vie(XPrB2K2XwWR_uftVs-iyx)KcENzX-g$#BXJ+Ss8e5 z`Wu#S{_Mc&Q8TRfr#uk&AU4HXlQ1~2wn@CTE+*CeVYxO|X4oM2rQ=PT$L$V$?%ZK* z>G!ICtNkV47vWF%vuqFhwpF~x|7G(O=a)GP+}p2D*0*O*bMIL6pq`yE!o71^f^Mb7 zxW7)iMc*~CiTj)Adiw4`<=uPg2kUzy!vf!4SJ&DXS}yS24M(m0#g7E`pZMJRe(&bM zfvqd7gX@+D4!yToKfGj-eq{Qa?xT}t>BmORbRQo)LH{9ky!%9l+x3%8ZgcZE zksr&obDu8J-#UFNK5%AVTkC9Ao4~ouYF18qNZ|Ye8FJ2NysWr$r#r~;e!;j|lN5K? zE}iwR#})=6!v++Mv;NO^bPewljTGsoO<;jij(d&&jktFNoP;@V#5~5i{EaMt*k0x;rH#aL)~J3t z0r&Lf*6466FlN*&Yiz~$1Ie)ytmK@X?v#j`dP?@{D;hVux+^-Dxu~3X?dH!;nWHy@ zN;Y?4=bw+tah)k|@s+&sIB~DsvA8RF^xp;3qxu_jX#4?)`9Ta2VuBDOgqR`35Fw@r zF-C|vLJSgOl8R!K5VKSi!-SY7#5f`52{BNJi9(DNVx|y7g_tVDSRv*LF<6MnLW~w( zSejo9mwilEF>h&pF<5(y-kKoSZhsX!77B)LEmjG`nN zNTPux8%V-|D(SF6;-OvTNHa-^|3+eh1tZS|BhSPE30hH-G$c_gO0tF|Y(+`hki-p1 z-jDaY0Q_YJ<~f~V(Sl1CF~C?0skeLv<1gzD zRC9ycr~Gs}^RCH0P66{LV2oCe}H z5XXVI4#asN?gMckhzmiS2;xQ%M^d~n$RDEiaVNh1{oWk4k4v$CaGj<0aVz$5EQo7C zoD1S!5C?;}7{tjSZU%8Qh^s-I4dQN!;&2d`gE$?;?clXuBm!q7Q1TXuz#-ZH(<~BE za8bj~@%T?&)F4Cf^T-m4k|`kBLQygXBx}HeiyD93iyHsCS@9~bc$Abt=S3SRFShG= z* z@WSK>iA45^MwnN35S9Egzk3mR&m#zuaN$K;B`>xs zc;sz%p)=O(58igArO)%sefvlE`(|6u2eDHEpFEnAJJWDT$-w4E@3MW~M-Oc2x5c*A zz9H~M_*=Ft+mgVxiqG4&Sx*JNl5h7_=77NV>{*^2iwt*m#stsKX=B`0+U=gNllr=M zO>FP^Cc342_n;b{J@qTO_eQp}eS2Mtz`oGxwtYu$2<(^NX8*=hf$#V3vK`pECU9`w z8rz|#-U%EVHQ#f*>CnKB<+^!Jmq@XlK9y)YBkK^H&5E|2%WPxINpEO7zc54=tt%sw z&2wa;`C(b8u8ODVkW!vvuCt!v&V!yHN4BSg{WDKV&F2ZWE%gKkUi6f*p7E64_MS{6 zALl8%a-K{ee_AGwKO_^!lV#HQF!852nJgYD6U8HBl6ZZYAYMf#hj;ZIU7CogU8tFXD{mxG7ieKA5zk5(euG=1H^wNYfDQtkWUG4S$iOLC+zmlfY^khSOvr`AeI5K4TyC>>;qmb2}-K3 zV;_^v5Zi%R55#^T76h>&h!sKX2x3VPTY^{< z#GW7)r6@K9u_{HeD~M%5Yztyt5c`5y7{taPRtB*%MX@x9twF2}VsB7c92?jiO{|WF z-O;c-8ny?qK8XE6ED&OY5G#b(A;c0D#TFsf2(d>R774LQ8dgceE@@aM4cnw)oiyx| zhK16wQ5sfC!%iWVO2bwa#abcu3b9y-%|fgeVz&^>rD3}e>!o48G%T2g4b!k<8g{HG zmJG3FMX_dxJ=3sgh)qMR8e-QF%ZAuC#JXwNH^jne*f_+>Y1lc$(rMVbqF6f(d#7RX zG;E%R)kEx_hUG(SpN92A>>rW>AZb8RQUN3#KvIIDqy-wOK~d5JjTE6MX@W+ofTRmZ z%7CN|Sny=&^QwRTQ7OY|ZH(bNK&^0bZf4Qu+**=|2Z6jk)>Vh9@m|r>)CEGF*C4XC4 zjXwR`$kXLlE-xhF$fw>cA%De-X2^@}>XbPb*DY`L9{c$XEh(Y#4-gwo1P2fu{6F^2 z1ip!C?ZZ>nvIi6>l%=dKltNnCQlKq?Qc5XHWJg(s9Rx8VAS%$gvWSWp6*n$w5k&MV zH!2_(QKF)vB1S|(78zw#0VCjofPC+BrfG^afcH}`-s|ZveP+^hGIQSl`=2vsX3mg! zkmQ3TAS4MPi3mwXMM+3VQYuPfLXs1bppYbmBq}6XAqfjfT1etTk{6P|kR*mAG9;NH z2@OeVNMb{hTk)@P%cH0V9+Uw88n^sx6!pLZ8{l8#mV5H32Ob;&f1h#7i~Ye*m9QLn zx)|wi;>!i6190YFJyv-s`6phKTja%bjaQpSNCT+6+d2Uip;@<$%=TX~c7)C9LhEF+ zy3k^5stc`5S)0{`*4VAO(6)rTtuC~$+*TJ_ZJX7FcGhNfp?z+%y3pRRSzTy*+*TLb zYi_FxZHe3JLYwWjy3j_utuC}#HmeJ5l+Efw>uj^S(3;v*7uvz{HmeKms7-aDdCI!2 zF0{jLs|#(j+v-B|xH)#}*y28SX2&5$=J7XeS#i^}tdE|uWmg!iWxsfbt;-RI91lI| z?)u3PO76Z%Zy9~}H?#Gx zvsd!BqHVy>Z_0gge%m#lJtgt0IG=my?YA4l;$CnMA3fF> zQDK35WLAzb^2ikT=;%0O^!xqYV@p>u#y;1^eeHp7jd2h5vyJyvl>6FR+a~(`L+cPhZo3-Q<6Kxbc?HgZU7#saq^xOKAZ-|G5W(QZ?6du>B9e!}u8&S?KG^jU zqvi{`J|q?Re6Z`Iv77mVt`A8K=7U`yU#aqAnK{||P3998oLT_2L7{5{z9aahdVRsgXBMX>~kEhvgLKl85f`Vm0Y=eSzK}uKWsd2w3C_ z; zw@jBmxX7V>pS8#KbJjm(^RBe#JoJ;J`_s?Zdu06JRjZ{jL`U)B*1t*0%CWfI^Y5gZ znYJppl~i&PAEAOn-8)$1_fx&B17tlQ>q23DAnOEKFUY#VT={I+zp;K&Q}$NraUsu4 zqp5(dSZSuix2~IFe3q!|2UCj~`CqKmB!AQ9<>gmIyjs6d`YLj{vJGchHe~sb?_jyP zGA$bz@_PumT|#cZlymI&$^z*jtZ2xuzkfW{ShTK%mUsI~eOvi4+Nsy?)=s}LLp$^K z0PW`~9^>p&ZDlubpAmL@ZLL_lf672;Bq3;~6Wm>9{m^? zE2Ebq#^`OYVf2YAYxJ#j%IJIcu+i_E-A4aCn~eb?+C z=l&D7(DX6)YD4?qrVZGilvGopZQj_>DI)cu@+{h(w)MvYK)((usLe z3X)ck)PkfJB*h?U234vtLAp^SL7Lpu{;X4N5T3i*dGN8q+o*-tdN2oQm{k{wn)JmDcB>#A{E6ZAyx^oONeDcY!hOg z5c`B!D8xn~R!YH6DOf56Tcu#F6zr9P#VU%;Qm|SIc1yu>DcCLr>xI}a#DXC<4F8L* zxGWwmO&eTVJiuJ~+Nv0~O=MXP$C zC2O@|lLKv7`9O#Rguo)tSv>a5SMVlZ1AoO=(HKoik4p-*N#U|MUX)()V%qlo!_R5o zA9&pJ;}sd+AK(7Se)7akj-NhC_MCbn$$NTTFZ-Eidpdqzlw?0UFVRtYNf%Fe!|U^^ z+t+y_N)7TxeAw4h<3Kxa&F51*ksl0n)VjB(r}jV79d%r%J$3Jiazy18x7WMAn4^B{ zQ}zbk_c_>0(r;F_|KWy+edHoen({0PVu}?hiY4*}wZ`^Hv z^E7|xT5pSC!##H6c1L`NOizM7*3mL;n?2Ey<47vk(VkpkjiXhmk3A_`B}dA8;r7;F z#(LX4^Oe2rI~BaCcW$z$E&s-we(ef-ySX2F9lhT5v>)@Lw?oTkJsF)J@OHdnz9%#O z1xMy_mnW<80!P+2#q6EGo#Mz|cfGyKhy5KnYcx;S=i4~Cy;a)Y-Fch0$D$P;XNOO{ zS50$!dd7b5=rwSnr+2ySj()8U*!%tSu(yB491n2{UJNHsrwhCA8OclfYl#xXiVniJ zy;=!1w7+x^?lrlD{$n4P)5~E0@;mTXEgS*H*%Rf}QeFwxzXzKA!Vq#SQW~;FXOV}Z zNCYAiMUe_bE)dB;WCM{7L_SbShzXGqD}|IGa)L+-A}d9a7DQeUi9uurks3s95XnJg z2az5`eh>*lWC)QWM2?CgNr)^V(uBwpB2kD;AyS3N6(U)PY$4Kx$QL4Eh>RgphR7Kr zX^5;L(uT-eQ6vtLIYjCZxkDrmkv&BE5cxw=0FnlfRDh%dBqbnep(v>VNe@Ly5lEUq zQU#JOkd%R>4J36S=>th2NE$&>36f5bl!BxcB()SJy&x$Db45abf;20eDhZe#a_7RH zBe3@fOovw}7H7GA7#m8`ONy&r5t5k_B6$N#;3I))nf{vmL>jjUC&=CySSF=aUm-fzs&bctz-Ul`3@Dbv<{t0%BikMTgOHR z-I*C@Y@JHJUfGxZDYqspo8*N?oF0tjTc*fRs(|UKe`SWbupIz+kF=2wu zId7)>s(yF6dyXIJ?$v6%yLZeKICKD(CJ^fhqZgjHhj<~_lUZW*hZ$UaE}UK7oZAKX)Y8*Dcp{nUMnXPNER%`dyBJ@UGH`ie*0 zGj3k$&Yhp@MhbGe>;L-`5U*A%D$J^Ht6`z`Ke4jkiIx43>TU|X&nYriNPu`LO3WZ} zgTxLJKS&HAaa5F8LgJ|?F@?kx5?e@oAu)!;84_zqydg1%#2pfQNcS+`yBLl= z-7dcq(&G|1J&`0w4e{zQf~k;B6fj~|JTM*pW&b-2GB9Aqq9r+W2plx<(O+ZnvY@jp zk^j5!QrU{+^tj|uM+RkO3A`wY@?zTdOpRN2ty=Ppy=wIIylN3I*s6y=A65P2es{!| z`|@h+tYNFUX>4BPx+&gT&yM!iUew=PXI{3q?zA@EsPVDhdIM{D>vyf-ZP4~-Z^PJc zyp1AFZ{uWJBQ#Mxx(1I{K}eU7FZUUS5*e8JId=@LiWf(4G|vt~P5 zOq$}bj~MNUA9SBLVd8b(mT8l`i9LIGlWO<%CMU#ulh34hQzE-NQg+w$O3TjCX5DFT z+nrS$smp3O($=1Eq|ZL(Xt(eahhyY!NBfz_ysCNvLw}bjS?H%!=|VGw|G>0ZROg0N zikzj8>0z!gu{jA@`M~eMJ7Mz9FF(VTgq+KeGX(NVW-;Q3g}Ml3K18^Sj0fQ+CXl$W zJYoZh4>%-j#1IlkNGu`ogv1mQS4eCj@rA?~5@$t;H6-4U zm@7)$A+d+VAEE(>4j@{9=mDY$h%O-7fan9F5r|G8T2U0eKs2K$x`Aj1q92HcAUc9* z38E*6rXae4Xba{_Ou{ZheO)Z^OUkrGfkM-J*m+{rP?~GpeHq#k3 zagSba*z3;vJzvus zc5K$$%xvGn>{LG4%&J$}%sL!lW|wWQXTSH8+2t#np7W1gX4iL)>D?Bb)Vn{uRqrur zr{43$!OmWZkL$gAt~C2x`8U09!h>etGmo16t4uZf@6I&`9J|`Q`jw&PHCs~6fsbaG zgI=g(4xZk~9P;QlGH>o*@Ruix_075c639o45j_?p<5T2T!1VEyJD{4rBK!YQ*ipUU4U zhOopUm5LRjR{kBuLR1S;uA-E@Sq#Z!NH!}+0*Zp1ne^y{Lp<=dSPcy0;$CqsDF1>s_-^ub=ZTy+PY&^oFs2*BeF7(;JtYu1A+x zZbqMY#EjYZu)oPvhrh|zTg|2$#+$J#2bs;5b~ob|v@@H}vYRa?)idoQs+jSE()EOi zHT0HgQF`K4r}U)S#q{L(-FoureR@jGlV-}UxAfLO?KIo0Tdud=@rs%H}6)Vg?}?0Bj=j!XFjHP$h_9f7;fkt8+A1^GluD%N;WsM8g$gNj^^mu#T)C{o8$B@ zd&Bjd6_xa^n-cup?mp`Zw24ZD$ADCu0v(Up)DDSHVc)avQ#3#zDW#G!K{8l(iwMvLRPLV-nk3(P~!DR^q z7!qVipcN(9kbpyi4hcLY_z(d=L;w*2L<|r?Ktur%21FbXfj~q85eh^siXs?@XduE- z6!Aa=1Q8KLNDwhW1O*WlL|71UK?DX78ANChu|Wi;45K+Tii9L<|%U;;h`j3}x zi}-{~Iqs+17Rk$T{{nN>k~5BK5u5GSG5_(rh%b+MYV3@Ts=4VVPvp8;zFN;t_SIfA z%2#JzKVRKxoqbW`Tl?w_Z0f7uHPY80wY;xk>=|F9$fLf-<@WfZOO|s+pE&J|*>_5B zGIfdGWb0?nrW-zR#;)`_n=O6b8MojGXY*P2J6lY;Pq&YlsmBjmVJ1wx!)%%6HWPb} zGm~mhG?Np$naO8*n<M6UE&DN*7>TO<$Fx&2IuBR?b($ikH>FIN->+KdE(;XvE z>g{J$G*y@wrBr^-@tc7CgqJ@52ghj9nL&6LRwW5R@5^_k`A)$waA0h#W3?Nbvh|8=nA(DW|0wN8F zJRlNL6q!Jzq9}5KNCqMsh;$(Gfk+4oZHeX`TLB6EgBYepT?R?2+I{8u} z2Rc)B$M{;GN_Vz-rL3>*&M0T~IFQ}FRY#w6>lK;r0SC~+LuY*$3(hYgS{X&g#mf8nV_+cfi4lt; zPVm1s|7R&5KpSi)`haKzq7#T#AbNplMp1MF(T<|%2cjW}jv!is=n0}Jh^`>og6IpP zF?hM_>|#bxMz(<$rKY@?79+U3_yfB=|)Y!&}p~c&U#Wi@N(Vl=E^pN>XH8aYWrg1 zqk+kb5@_Yff%5&%RZGh`|M&*yrR;%z!v<%;4yaJWlLn{VLw#@sg;KlP-B{vm}ILuwfb&LLV$hf_<8zt2djH`7S2@|M=B z*h^Zg<0p-j{i#~(Pi7cx-n!ms`_cQhU!w1@W_>2p6Z+TBt^bBw#jXg}~V>CC=I z%Sijs=ot69ky*E-)~Vw6T9%e#7=4DTS~?LQ5gMkPt({Od-@1!VL*IB_V8$O0k_3i5zR1R@g(QlTIhh-4tLp&%WId?-kWf{Y+ig2;)2q$tP= z{s*N+ieKoeyTW_*Oj%XtrAhff$Ade{JF7-N;nmh6y}NeO7qs(nPjiIX|^|V z-F;@QXJ?wfD%HO$tHJFoCvq;MtLNtF%l_(xwCh)h5P+H2( zRZBV0UdZo)vf9P+yOYy;C*L|$nsO4G}g4aYFWq%61l8|kIf~f$0dg9EtkwY|CS&& z@ri3kZ%|vwEkT3TZbE)b;tg|Iy+hVMo7N8{#Gbmy#Y^3t?YnyR2FTa&|G6@)?moKs zSFuSKx<9>eQg@U^GjCh`@$}QHmV6#nHTrsYwTPwC)3n4^{p2%CmHFlPhguTWc zxo(W7*0TdVwHI~q)S1`TQ+HZ3Pt^F@o_Yf-dg|w#^)zVf_cVi>Gn9H$2fL zzOhH2Fzqq>_BfhMz1`7d>+ANW8(y@>u3Tzww)6pe+=4mw=Cf|Hx0p1>-s0`9#&3D? zUx&=~j*Q`3y&W4Zc4TI(_I4^c(~;HSVQ<#a$GzFbZ}Dbt{+qYU-htkn6~nzkkKdPVnT|t>_Xj(Av9HFd__KIi7_P3ERR@2;!PpuiV}B7>>=@| zpaF;uAX;D2h6$)iubesdk;&baNSB?8?o$b@tSw2hW~zIz`T& zcDq+jr^wmUxEtkkikv;I&__^@e)i*l(SEe<-E*uUf&^|C2-dfD_;N zu3r7Fe$CboeFGmqU=CXOqHl2SMsvu52YmXFXUw6~UvLg?o;yFwdGQM;|G-a(e|8oP7rLO5eM8}kDCLhKb_GRd1iOH~t6yIO$qYz# zKr#f9C6G*kWD6u?6eVjQnWHG#1IZvo$s$N5L9z*wQIM>HWELd5*hn%Al4X!ggTML$ zPsu|5?_w8tl9_yt?1W?}BugQg3dvST#zL|dlDUxVg=8=!iy@f|$!17KD@s;FGFwry z8S{iCqU@xa51c5&LFD7I3;RNSA5OwNZUS)>h^r`yvq0QMQ5**1G7zVM zxDCW{Ag%*(9*Fxu90=k<5GR7T5yX)ouB0f=1aT)taVUsOL7WQWRuIR6xE92@AnpZm zFo=smoDAY-5J!W!8pPQk?xrXX2XQ%w(?Q%0;&>3(gE$|={U8nqaY2X^LfjDIh!9tV zI3vUz75y)%d0ditoD$-e5XXeLCd4@*?g?>F_~&%dUtS1mZy{qw#M{;FUm*v{3YX30 zMd2WyY3T@?Vf)?gqk46Tx5>d)awzp=RP5U|r<^dw|S-V_O?4~bWUB? zynou-Q=QZ2*!s6yxVy7s=!?rbj~hW`hYd zB-oIELxK(oJS6xK0YF3m5duUE5J5mh0TBj79Eu_kh)5JgC=jtg1OpKbL^u%fKm-I4 z5kyE3F+l{SD58S75}V6HSR7nj=yu7%O{u_i#pmuU)^Y9Vths6{=m;9Oj*S{S)jHtU z%36oVeWd@9CO#t+3Rl5cB{K#?3?aJk*nQ}%RS|eF7c5q`osoX%)U3GnoJ!R z)nw~3Thk2>*p zZS7}nba%*nz?L!m8F$A7cUWWeXZ@4!2Fpvyoy z^YYk@1m4&3U6FA}5mIXwrh0<{=K<9lp$-Cy3^9Tt%#cu12sb3;kgzLC=po^UNB|-O zh!h}lfJg!&3y3rz@_v?Qr^mGpWo@NplXy`$ z>&5hyb3Qs4Q>ol8xuJcd5wtRH$f@U2H{{GK?y_#k+3&J$$a&jk-H`LN%eo=wE|+yf z&Qt!FeRrBow%*~lZpInsw{FJi=C^LfY2mkS#_8%x=+;7SnbzEu7*|*6mp`tz z`SMA3+m(+QsY@Sqr!6py^jW#?c9Vt~juAuM?R$4LI&{o%XCyW@IyP+N&a9oGWqtFE zzfaYLd3{R`b@lyjT3)~X8P?4<#jKk*FYUKH^0}niO{snGFjxL=s=$xLaYP|A-1$zF zf0Zk@veOIQTKNyv87v{%V@1|IUPJx!-^2hC2Y9}x)Yo~RxWIz@WF$V!2k(>FujYgI z$-J%RgZIfit>%OG$=s#pgZIfi?~iGr=7aagbanCn z#2gZLNbDi;hiCwz1BezNdVpvGq6>&NAo_r41fmm&Rv>zTXoiArAliZGM^Q8c(GgU+ z{~y-U#o8(&(}E+dGWpXHhD_Z4=qt6}ySi4)rUzp4woEos;+JVL2{&j_9ou~sV{I5u zJ1>RuyX=zBYhCuC{BHeOoLppDQ4a9OgjM0UCcowQMBd@AG~c?ZYj2WN6OQvS$ujlJ z5+VzVTw$`H@9IYyLRK8I@{kRHYy{uIhCnt3vOzFcKEoKgzw1U34%uuOT(C{ckVn6P zmMUxYki3|$u=vF8B=lj~3 zCnvdnenm6R&XJ^jEKv&^z0*}JceGJFyQ06u(8ET_*!%sZGNPon@NHLk!(K-Ck)Ql! zOWv-P{h+z4+@bPX`R8ABRd}bJQE}V=f2C(`)NEbvc14_cQ%-PQ=c>7_i(KY(%vGmP z1Fi0z`(064w`le5sNt`lI!S9V?lFJEg!M+FMwzb0RXZBdC8J%@-(O?K9CSOId=aTP zeYdJJ_Jvrz*^(jFUt9OK&Q?FZiIQ-c|# zJ>~2ew@lBha>l6!GiaLb%=)gEo?Y$+pEa04F=x)I>U!6A_BpM=4Bm2bOSJQj<<6^m zzoPdXduRTj2FrZbpaye&eNPP6`&YTvXANr5)n^TA(A;MYYGCtOgBrv+tw9YcIftcw zX%4^oTcpCLJ|D0vOZZ3_7f$#F=Y zLvo!$zC&`JLf%7ipF;jaJb;1^K)is0A3!{Tf-gY40pbr3kAV1uqIdPz14D%p9!aQCA@e_!rKzs$_Ef9Z!cnrj6AYP*=egp9wi0?qW2jV{v4}$m*#ET$) z1o0$@FTsLg9>kxR$D<%V1q+6G5Wiv`&w}`tV!<#E#nj*NFy6<K*2kR_DaM*`R zPL_v9LVOYihkf`+%?F2lSf%FiPChrb_=i5lf?*%5vkbvuA0AMD4-WfqlbR0>`!GPw z2Zw!VtLB5lK3HcNg2O(vaq@k5FT{Tp1H(Ru4>KPe_Q6#11;ajwFZ1`{un&K3fBt3v ze^NpJzf6TX%q%4pz>C5zfazL>{Caoi9M7hbH$-fDAYu9L>Z;+2_J$*DtrTHmKkcOu;KYQ!im8!!$f<|a()d;2OsoG>TL*>8lgq5 zALcxE&YX3S^@OaeqO31uomn{R4Ow@XD}Vbv)}Qv~rS`ygPg1dLajvX;sLsIOymHl{<^@KvmoF^n z%A1itUHQqI>NG2J4k=j0tWp*-{MzXp_MSzmV74HmJ+$Zow9qV zJiT*@yUi;Z^7PLB?$l*N+-Wbjai`B|FHi5R<#vo}AW!dXZTmfkH16li_5B%!Gyb(l zfNF5Ni}a6{yp#{A&&W~1R`R3h@$GnDs~`;k8TSrHDY#5aZl>YCEHxh-bEJuyFBo&A;2tsSU?X^sSUL55q?{(DV0ckUJ?4XBjr>lR zP)WM-J)|roZ51VTA?XWAVMrQ7QW=uYkd%g`H6*nm=?zJ7_-7jd8~b3r{~G!F2Ngd? zWv4#=osqBqODyh}$5S$NcER;j)*Uy>6se_PDJzq_C>(z+l-G!dW@~(%W2zs%+(Ji57*DVvim3h z;Z@gZM?T2)AARVU_KoL7m;dHQ#<%w#@E^N+fOh=GnywRV=NaGidEE7Voyx`!$y@wC zoXyluMwD`${QO<*r<1Myr=mMJ!>8S4hM)2H%6$KoUUpea-xXh;)~~F0jb8JoxAe&Q z6ZBfI{%F>|X_~Ljrn7oo{Sjx>ZO`lVvRC=)4}Q#S5H-lz@M^o>NNeZB+!}v!z!&}X z2WE`7xQvZ0?TDxAT0`UB5oYx!4|&?z;?^dnz4Mzs`@zy7hsRC6>$usf+E6{ER7YpZ z+39BMZ**UqJwwd4A9V1gu5WLq{j;Gj{qY87yL(Fc9JwXU_5=TBb{Krn%xM3Tzo1vS z;-_Y(L)W_)BDeEPm91Xoyi`5sx#o+y4t>QZJu!>AC%taIa?mn~w}KZ~13a{nq!}nbRKbWKMsso;hP`jG23Par2zVBNW{hP=g0Ch(p0`_krPr z6pNQA!%xxw`)|K1eUDBOA#`ga%xY~^R-Y-DKCk_eEzb_m?$uj0J!E-uD-n$ltI!g; z)I_8uGX0e6VUWS*piBqk&|AqfgeQb?jgk`k{y!pkfetsJ|y`e1^_VuMKJ=187PV&KuiH*4Ady*peP0bF$suK zK+FPS7!cEd7ze~WAO-?45r~mM%miX65L1B|3&dO?1_J{vD2#@A%m!jO5YvGe4=fmU zSPY2yg6?QBBIYq8h#@J8DZyVm`Y?vY|6y7X<5Cp!f*2UY#2`inF*AsvK}-!|Y!GvU z7#zgpAVvqz>z)?V<9&<|Vtx<W(YAvh$%vh5f*e$i$OA9&^;|i$$UZg zv=}Dym?p$H6~#Ou2C66~3NccMnL-Q|7IaUGu`-XjLJSsSvJj(%m@UL`A*Ks4UWoZZ z3|LW27-GZ_Glm#4#FQb%3^8YjK|@R$V$=|`h8Q-)v?0a~F>i>0D~gFjj9gL79AfAY zQ->Hk#M~hU4>5W8KX3Hsw#O2iC$>mTQ4KORXO4O! zG9Fq`s72O&U^%nbJiPPXU;_AQhe}y>5QRFJJ*J3px zs|s0N$SOlt8?x$<)rV{Xh0UPk%6sbi*xznbI7NA(QFTho*Ycud%8Ti0v&Rm1zbki; zzPs~&`+GSzOx=?Anfs%0+l;OE_h0jI2T#f;H?~=`ExrAk?adR`Y(Lq0&5nv!uG#Tv z{|*YYp7$FBM6>~+h(UQ=Ps;XOT;A9?7THAgp&kNM`N57+pY zcaQnD--~OG&9%oIclc6HjHwdyUDOLH-)EhO`JvH_lpm`OjX7CTPdWKrM$Aw9JEWZY zY{>G{n+LBs^J@F$Kd*4CIr~_H<=XuEYrq<@)iFb_9O^ znky~UXXT(%^%09AMV)fPf}DrHX)b+XqE5MlJNQ2G?3}|(dcQj7^y{s4p&N{HP6t>K znN(y*ksD!!gjP|)3kfkK%#ct+!VL*IB*lWC)R>qR0^EBXUU?mO-P69ep4Xw{ z5Kl&8hrEsr+j}x=4~gnib-AZ+$?J{2-`(lyxBn_5S42^8HA~=C&mEmheXi&W%8IO7bV~K|e^fCHJ>pin!QX>-SVpP&;2oC5)O_#`%YAA- zc!%X%YCd>}<#IJ2yud^fiSn{4yeJv+V){x$emzm8()v|PKC)Gf9%@vJc)(vh`~ex1{vDTfH{)r4%}w2m z$aPoe)p|B3ulAzUygKvZ^6E~jlNU9_>TxA~)nUF1IQ# zy2R&E(I>V<#q9gg-el^{_9k0bMya7CRz4V|hL%_`HA)RFG3n|kHMGQ_DR~JKugYtg z);}+?XUn{#+HLZZ6RPGVpQ)9X5}6Q{vg_x()~BwFYO~JFYrEt7sMIIVM5V3W9+f_O zPgJ{wYoi<^UyEu#^OL*|nMT83y~c21%p|L^q5C}5D7wL z2$3R0j*22lh%6OFnh<$HBnpu!M5++ELL>{3EkwEy`9dTNkugNd5II964Ux5?NE;$= zMUgl}<`AhvZe*x3wh`)!~!c$2~oDwz;<2XZm~VXU^Ty z_o!=I>RaJE&NkLgta^1WhZ4n07MH`x5mD5S{FYpnI+rEL7#tPV(ZH1|mm^gE)*@T; z2WCy+T6$|_4!N;evbJA#2NqJOgvi$-*OmNiH!53)tTSZ2A?prVf5&L0?{Wng83l%5QFGgQGinXYQcugC0} zbARgll(9E?h_>&s&c^;~?X?4w{My0FTZ}`EpEC{}xMPxdL_Or!>Px*`ds;_45wbf z)%QfM(Z6aFfB&sRjRD`4^IyF(!?@ps zE3R9f`%Xqgd)zhcp~}Ye6@PQhm~!06oj=?aR!a4yO^J!C8-Yay8n$zDatU`Q52G8vN1kc@_8H6*hk*$v5X zNR~q~9g^*kjE7`BB=Z#|`zilxmk{ARY&Y%$aUh5bL7YfY+z8@GisDKTXM(sB#GxQA z1#v2fTR|KP;#v^rg18qfcu&IoZwMR7=oOG2Cy;+7D{q~Mwm=cM4C5C^5;q7Wyg z;HD5qrQoU%XN9<{qBtzXWg$)raa)MvLR=T(yb$+=I55P8Ax=!ejUkRq!IdG-Ou?NM z#i1#=Id2yp6_088n5baEI%2lagCgXK?$Q=n?xiha{SLfCXN&b2-d6`4 z@$vc3cehU5sNNP9rAi156&XmhLogu$WdQ^g5?DxZ75{p=yG}j)w?5tdvJ|s;)PvBYJ^o=&{b!~p?eUnr&h7K;+2ykGviI$_ zcR74(l$=s6o}Zr5G}=XkWe@_%NEsDNPP2A$(jhA2WJy(6r82FP|mwgs|1kZn?w?SjAOww0BQjZ6A9yF=UJ zW7B8ViLhHY@NW13N($5tz17TYL>~@>MWb z-cvioOZz%@iSp$P`;{1S`zpwCc~Lsbi|H#D@7(wGpMBWacE|R)93!8UF5b09hs@ih zYj=^+vC&xR(w$*+Dw!i)xw?^cbhMFOyn~UwIos&6x1o`+%sbrTyJ_bjGRo!s1R}8!p96NTQhoks;d34WN zzh%!U??|`ZdE>~&%3hRWovTm>@@n4t6(W6$jJ;?~#*^LmHT-Kl*)eWv@IN)4EE?wf z(J=)rL-b5R(-2)#&^AQh6f~|VI)`XoQS=VcJVf^p?NiV{BnMz%eA!&lM8V1TKWQ%D ziWeGeR+5thRfYW<(^U-lb^D|1*1o%QUQA#lAogj}nJzzabiZ9bpyhPp(+oGMcDO1@ z8FZ4{_0An`GDJd_l!fr3l$ICM)kws~Ya?P{#=)=ckadBq5B%kbKl$s%^^k#;vj!%T zRg{&JRTRgOHi0_n$&f@vuclle!vW-lhK;0|!i&;DUQF8-pR91~Kluzg`-lPd_(8Kg z2@{8STBeQiB=&6YNvhr1lbq1NlYFMBCneHhPuX4G)B03>dz*DfJ#BZEu%|97XHQ#u zz@9$)h`rsyjdsV#5A5w{?(=lWeBPch{4Gz%M)%t@GnRWgm7HwPYH+6~>*&3n?Be4+ z*_&_lblKb8le40ar|YJ1_HOfAd3xvUkII#>{OVje_s}Ywy+*%^RBdk^X$7xaQ76G9 zhJkn3YXm++gahvf_7vw{;d1^N?GS=2GP($k2*dx?C(xlV;)2Q`N`t5kqBw}^Aj(q| z^+6P~5u!$jA|a}TC=;Sih(aMMg(wxGR)}IDs)Z;QqF#uC6-C7mB`b=WA&Q2m z8lr57x*-aOxuStTkIM1>3tjpvi9_o%8-dUGiNVZJG#5uTXvuR?u+~N_wF4M zzNPbZ{%ZGr7p|&6&R@&voYG?2L;CXXLkE$s!_>tnqoc^q(dx{MoXnPsTBQXSp<06K zGnToQhkj+(;O@?f?qHH>3^A)8EAk_e=3P^Q8szgz$1qK9>E7Jvu{@Yj2#X?VO zh!-VGUQAy(d+asy-^-fpfA844=B86?o$r72v%dM!Cw*In-s1Xb>W{9i9iH=lobwOY zCzHDRx3%l#-`=95fBTO=xpq|e*tKK(F4xW@FS&NDdDXRh^H=`Q7Vq%yS-r|{{%xKA z^9LXDf6=3ffA5r={rg&!^Y0(>pzA=zBmUp+;{09_*2Vcr5x?8T`6@aaa-nPUL#05Gp+$Ox7ZPGc z2{R0$ZeeFgqN(GjXKEzNW1E(n zRij%ut3{0SRu8|{SN-JUj)*TGbJp0g#anYz183wqyUTjkNflQe^{kT*Nzx z{T!Zkvc}agmS>$TbTux=vrbm~qfhXx6S;VQY7g~%jHVlI@yD*@c_#z?aSM3fNxHwq zB%XH?<&PgE&pVkI?NZM>=~>35o_7-ewJZ7bF;_}tX@APDt*+KTed%wrZndlJj`#ej zPj2z2t$og)K6|CV-NJkPj*$=g+s}N~)gg1LKV$fOSI0(I`!h3Ku1+OW{aFoqy0VTA zaAg-ybY*XD>*}(%nk#2TZCBS#3I1+(pLO-ldDiR~x#%}(U8<8)ck7s#bfQV4Y`K?{ zQ___tjWX+Z;2oN2fzQwm47?vWQ6<0xwU6EJ z&8=;487)T_a+!##82K&J(x#B)qnA6_ytba*Vl}Jfl2EIgQdL^0tSj=%$u?F1vJ#LL zfxpShaJ_2#&~9INr8vN3-e$@JbSjTsw!!6onKXIWNfE2yqwYyE4VvS79Y}^3Lk&Sk&QZ}vQZmXe!ffb z*1GLfEwO^U&q_j86tc3A6;_m$=KEN2$jYPX5^BdBSo<54{(tQ@NOz=c*rnBOu6#GO zTR}!!UGvxA!tc1V8tk!U9X(*nF8-P=d-FzHm%U4DIV+yAb=|by-EICnTlZ%dyL(KS zU~|r!>AtGpo$j9FN4k5p8t?AiHOt+ndUtoMUN{(uR+16!tyC~-ECN?(S!>-M}f9UIP}ez{56_zS)F?LYc%0KHtK;~ zhvd7Wl>}-T?6jh(|ztLQ%W|;unhI8St-Bg?~`> zaJh{t{CoS_xr3l2H9vm{;UX@Lp^e0gQgFfMGZ9hV|IM?DH#@q`&-CVs>;JzF6RZn1 zukuo>fs=+D6AyFI8q=h-2U)1Ig}-V8XvnB+l|sf&cv{ z5^r{}od_A`O6)INF<`a+v^;aYM#DeSo??IQE4EJ{3WLZ@2oa6R>_iwtWRfU%>b)#TM=XU-|AXuih`~n+Jfi{qA`fhAX=o+GJ zh`wRKz0f)HXdUK?CNAV=my6ynW^9GKtbuz82;%VbtdXzdr^?0s0gAYh??MEc6MoYmMa>#kd0kl zD#y$&^tj2wd+|h30rOWrQ{(Jq)l*3X8NQ0E~FUtuI7Ru_-ez3c3JZEdwZ+Iu%I4gckEMD=z|GC1K@ zLPj^0TjC8WKz>V-$Yp@C1UWY;S7p15`~gaPpErz=KY%b=y`tW2< z1cw!u0RZ-~+BePRgHq2{ZL5d6a${oQlbRt~H+%svO2HRWReUMriy>dmhgbplZ>>aG zS*!d3iV|4Jxka`|-O|Uqf$8##k3DKHap!&BTv-QoJD*)Sg?Ov1ws>*Aw{kOm%lyTK z*wnxx&sjX1##itrUju)|SCLFD)8kr(N~Xe%M=YOtc|h>ofvc9Bar`!WQ=ZDY$ETjk zdg8P*W?$jG=?Bi(m0oAFrO!L#7Chl>KI?vGi%GMb_7Rhv@q_Yw2@`MiwM@Ism)LWV zFRAtjUvfe_U-FqwzLdy;&XnCTzSgJGoo!wz>ub9+%9*+>+L`upF=zUmGR}4j_c2t_ofo@K*K*Qzju|m)38rwVFh>eSu^|& zp@kyjf)SKL~H6-4O5_3r0;rV;i57gh$0e?pe5IsON0nr6S8xVa! zGy>5HL@N-zKs2K$x`Aj%QS<}R5JX21EkX1I(G)~i5N$#91<@GHm6)8rN4

cClu4 zVZm{SbG{MGkt-K}Pa_EQ5&Zr}Ffej^-UWhGGCbt?UE$mlo~9G!OLv2y4-{@;7v%;l zH&8CY4GMxIf)h8GlL!C2Zt%-P{QnL&Ky1V`S7H;w6)q3KU97P!%GY>N0@L9iM<%aS zTPA((v77i4$DDyNMj|DRd1>tguc;1k^|j4< z-_nMRMVf!bHMf;e&h1_Zp_(GYDv5QL&_cq?(*C;QI@(1&1h2TRZZMDBAd*uQ*+HbI zDDs0ykb(>$Qlub93X-HCOA6AY{HF#qK-zpi@}}g97A~Qq^FJwZv9JqYWnH*;qo!;V z^Kw8oyVdX44wG{=`m1(TdFOQrS&bqZKAn;GeI7lYb6FYL@NJm2D`xAs~<2`ORwgVtez#)m&TFN{Q>PZT_jM_a-CSzis3_ zu5Cvj)w!?o)A!@d9oz2o?|iP3x$D3e{@wGwHMtpX-?*24+!Cv} zTvHDnJTd00Nfxu>j5+c_wW&vMUpU5}bi>rBCAz!bgwgK$Gds8& zbkBANM@ugeR!dQHB3IE0cgN=8} zoOHeO(JR_}CGYgVH_f5(gt3qAy46rO0cY0NKIyvNzb$pWw!PFS*Qd3Q8K1VF=C8B1 zXH?y>e|JaCn3Y#Ad%C;+=*3YDnhbF_%$gb1sLC{30f7Vs5*S4Z4kSR3AVC5J2^J(^kf1>V2MHeaNB}7W5fVs9Fd+eje@IX- zz2!y61T8P=n3!4ScfE)-&@mCB<1$QopdCL@L((N7ySv~em7-B~31;9OG|gwM?uqj+ zwPYET5N&$4mKUX9>xm>a0ROZXSr+b~_`_=XVjUHQ+mW0(XTGW-zn-bFFzn!t@^Ycy zBi?Eeo9)%Z-*;61Da;e`mFBInGs#|a(`;|#y8Fyp&(1V!FPdc5nK#m`JFTx7H9pI% zH!#Jl-?fR^Aho91uvt0tybJyImvJ@GcgR)CT|Y1Mh^%JJx69X+rsb zB^7#GPhAUK`IpENt!{a1D*tzgtSB-}WJBN8Yr`Pnf`p7k5jIHZAmLM#5JJKT2_+<) zkdQ*c3JEPFypRw>!VC#DB;1gYL&6R(di|eBfzKfa#e(bqUUM)Xyjo$2nh##BFk8(B z@2Ch2EP0<-{!M!3N2%=$4k+1Iy&oJue00VM->QUnH+4BSbv z(XL4e+)07tm`8RH>A_ry&7VMi*hQi|3?$T5O!Q;&qKuFi)3p|kpT2R+&MF%#zWhYz zN|WLow)qab?MNF(|zL=89H_|Bt=%0F0t|+xSJ=0RfI4`cXru2{j-k5JKo2 zy~EMFEY(mXt5Ob)x*!Sy!htmTQ$i0&3DSbJ(3I{W7Epmx^fQXU_x|S6P@+V!XW;I4 zdu?;O@AJ&;yR$Pfc`D|ODr+4VQ})7}QGSOP#FX1rBC7oA*d`SgbquWda=MsG`i!W` zBWgDB@4YiHz&0eNN<(*4)hf4Qs+AraRXtD1CN(m?9a!VrjxjYqyBHPt{tHcN9kK-0 z-j<`ulmB}yu+F@OF?AziqUuc?*QEaN9|MEBza7({b>*mr^((~$2W*N8{pe7WCTCg) zHr?N-JPso94q~+Sa`t)vm(gChd#) z26o7HI;NwkYgETu<|du4yd2p1#7j-O?8_b4bwhMaw`Gl^y1)8OlOEG11oj;38`Epx znkajx)iJ%BRf*~o=xWlp{D8oI1#`#r_kJU)|DDh#18y7*9C+zSlR>d<0|)QAA2VeA z)Tp7(s!fK?+7>wc*#R+6js7xfM4x$0v<}w5ks-yJjH6_a%?H_vCFV=I}b=4ibBOp7?{rAS4bIB^Dv^2#HBZTtZ?K5}%3^qmVd-#403S zAu$VyTS)9e;ujLbkT{0KG9;cMF%5}pNNg)gd_!U!66cUuhr~N1<{@zpiG4`?Lo@)< z0YnQBJwP-8(FH^s5Pd*20?`RXD-gXvGy~BML^}}uKr{r=5kyN6JwY_3D7u1Z3!*QG z#vnR_Xbqw_h~^-=gJ=(;KZph)I)rEuqDP1(6-Ad2Z9?=3(I`Zx5UoP=3ehY?w-D_@ z^b652M8^;K(n zUwq?Z^?F3a5cT>?6MfX{FAra+UVo*>n`+sxl~l_!>&2?~&kE?M-v6pKy?T9it{2qn zuX)u}ufKMCr+WSMt2@;4oc9~5WjVA{?|)h-sZ9#XF_&F0bn()&NE*O%S8qu#&#qi5Cf ziW8O9`&aJWtlt0jhCb@`RZFj_*Z=qG>+1E@(+aEC*Ni!&USB(~wR(MBr&ek?vgv8H zygu+b_5Kaz&FcLd3v5=eZ}NUpy}s#(>+1E*H%6$}w_JKkEpI*kjarV{wOGA>+j?L1 z{_Ts-sMp__)lI#=<5{zMedp+x)$6$B(-o~Wf9~(SPz5hg&s_ONVrM9WpPvst{UO%1ThI;+ ze42Mm@!lbikPSsB0hmoAQzMh^`iRX9DyV{cvVKB)JE_Q8!VYQW@kN1t?G$Yhce`$gQI6Z`kw z=_F;S{>g08{JET7mk%divZ2~b&E)K7{3RQbcQK_jxYa}d2wFt_-(9M*Wc)EK^9RAC z6Y5W@W&Svx=MRKQC)9uAADTt}>Xa|*NPhKNBl%q{TNZ!P>fE0>?n8y8ulZM;JlLzX$;?TNB#cS(%cIng zxBJ7!eUN=jeEnO;eITaT-ozCowjl8Z|Lkcmh)ve}w;lIEVx7P5pAzrX2EQ}`Msg$O z+B23d(@x!Nkt6Fx!{>U8Uf}$X?t({J>YmZ3E9*rTF#2>(ogs#;LvQPz(WlSop3$eb z>YmZ3-_SjyPj8Wndgi+QPS2IgVi|dQf?T}A$kW{=&=`5To&*>pPj}U;w5%&5PuJ0_ zRV*eWPfOqDY+hRJ>l>XjwaljHc{V>x8s+)6T230} z`HWgl8s&MbT230}`3<$4G|KZ96NOy`*GP@bFt0B7^vdba69kS~oyB`t)kT8IR0wf$DApr>sNN6ZZctAn~ zKA`14E=-W(FLjK0LjDwPl!V9T$a~xSslQKJQ*Bkp4r)eEBh%BoOd<1rvI=q_l(3Qs|uX^4S;WXv(M2lxAZN{?q zbnNlxs}rs^rLCS=Qi!OB?s6iE-41^~dF0S&zBFxAd(WAof9t6BggyH?;aZDuiG{>7 zMi0dMFI`Xlz#IR{@E7X~kc2^ie@cmg4g$L2SO zx$=L%#^!Tgk17(}uF@stq;i3Ek6%`dBZ~oHo<}y;OPHP%HH^ zS!qMpl2}r(xrgpuiRk<6VaQ(QOW4zpy$#vpin7-sdmgg)ArSzH0!So4q5&p0 z>m({zCNg00ZXYWWA+jiwcN|b8QdlNhAQ1zJ8byg53ef|JAjQPy-3%6!HSdP1Wul5g zWKoDN3K2#j${>*j6PtGuaV#e`?*hJ8%Ur?8^Z%Ah$-*TDQkUv@Kng_rpBUx8&CM2H z_`GLQOK!R=zj>NF|M&0MeXeiU3Y@>`DtM&6R%k~HqwwmwMv(=TjG__6jAD~4hIN>i zQM}vNMv3NEjgn8EHcFM>Zy#VC_?h2fk2l*RX(eU`Ex@3s1c4Ym56UT!J3Z;qw> zCWocM((#sxvxitJP3dN-Jf?-kzh7NTK#xHN^DtMf+svrewzN^bNCl%tl}tvByV;Gv zLZvK$7u`m!I~gstcbqhyJb%qnXW1=F-RP2cPUT9wURniDwk7^=XpM1DB&AW;VYsu{nDQr1tTLZTHCv5=^RL@p$H zArY)7Q4EP>MTureL_?w)64{XGhQDiu@4qa{IV}C9xxAD2E>3uyOP8O!`0LSa4uAcl z&xv^h;}6f$aZP1cRi(+jBx|}2q7@;kl&&B=rWMgvb^oZL&gRr&v&EMb+71b;?2bEd0?oasub=_-b!4%Jy_!eM)!`Momx zF3z=H8aBy?aoO$v+eb(IYsYhH7;@AW-1no{My1!;LW1|jHqJ297Fu$7Z0OB3u}#xC zVw)a&CAQhsA+gQZj*M+_WV)^8%k5&@x9|$`?1Y55T^=|dNyUMS?DxQPY~N~oh$`t+ zBvIAg9^biGG$>t+&nq-ZROpXLLC_9Wewy@ADd2x|FqZ&$0;Putc0PT@#Be(a0% z=^m`*A6$|b#oYX&2p&fEQGF=I=)}szbUgP7BgkTXKYfqq5dFbE`XARPe7JIhMfW?;toK0SXCHNT5Q3 zRZ#*K610jExRBt51TZ9sA%UzY!3+s#7%uxhr33T7O;BT3ztm4%F3vD1SEi zdbed&#;nGwZ~kXlb7-Gs?TR4FI_FR$a(W5N`WX((2Cbg4anw>{lg(;utun}n%JH!g zb^na9?bZQfN6cx<&ZzyCU5h3fyXUU3M9KN~R6mB`Zd4%Qgm(ivp`XJMhi@PmHUu$DIx_6*{Y}9Vk zvHcf}<72B@j<1$s5!)^{#V*@voam6(aw1}Y>137XO(&-=GEQ~5VmaOPq~%Pl8$s{; zT@L!7!1187nWKZwe!o8G-1Q|v7q^WKy0pO=eR;{u=nrQ-7wvj(bo7-`{h~kW(;@n5 z+s4r!H>?@`NtLqEpOz>P{aK-qplexc1YNu18+82(pPfy)&r`9?P?p@?8wBxX~@ai4bA`8}7i$*N87Mt{n)jCYK7VkdNTB1cCYsot8tfeY6 zvX(Af-CD-GjMXjEL4V`5_sp|AQQ_5Lxe_m(#O>strrPqy$v(FoqrW`gZkJ(}P z_giBG^f+g!GHR2hstoX4t?lcU>P6nP)Tk0>sc|>l5?E-q5qPP$rPiHsM(v%!mM1TC zHtHDAwoz|d1Ec=@!bZ@5l176mwJZ%AXE1{M6|yuc{h1LGeAm)A!+&!sN#!OP zWwfIQE54MMGRZrW-%@^OvR5)cWSn+2jC=g^S+aWnQUAQ9rgWR++S5^AT=GNwu97okXjE?^FeAqMX3QHwIHM>gw%$R8WB<}LTW}x z?FgwMA+;o=ri9d%kQx(GYeH&HNbRX8H7KMORg{_(Qkz0*R7kA~saYYlE2M^n)UuG8 z7E;?nYFtRI3#oY_wJ)RwhD_+Jm^RU?+8zUo7#?1mqd4g9D8<1s;}wToYOgqSWjDoP z2S!@he#0Gg)$&uz+o|OdgNmtTZCZ7;JhDw4#Zd$DD~=8*syHU(d&RLiN#MyXBF5BFK_I&x}9?9qiM zT*nUNjy*nhyghcEzwJbaZuXO}N=V}jdo!fW7cA@0+ z_6ti_+AgK*Z@)C<1>5DTq4p1lFOU6nSZ3E}HJ^wzTQ}M>TVA(iF}-Hb;tsQAbvY|a^-T)D>Aj?F!|mMc$}!m)Xq7INhayce6l>|K{n zi9)sl<_}#3z3$oye)Xfh(3KBug-=|z7umDNR&;%|z1ZTnY}T<~yNW+M)mEazX;;Z1 zi(^Yw-{LCW?D^O-dFQ%(%k_`-y|>x!mnAgT@A4dbxzGJ#%kLU*uW+idt>VIN_DUPe z+A2@3YxjROi!ESa7gv>0U)rjMJ}Gk^9kW&UE#j(CCDK+i<9%0Pp(C+@pI&j*y0bR6 z_JO^wCodSWb=JA;b>A5kTkn;3?e*ukj}02J)ZSpqNL$0!)9k_h+SwXa9%K(`RNWSG zd!8$_Vw>2|QvU1_EuZ+*jjh5Z@0bnf3`Mt zird?cJ!@-MIGw$HhwZlZcRsOqtiHh3@e?ms=b}-uo%daJb-6b$w(HXUu5Onn#de>v z!qsC}&)A;*X1IDS42re4TyF0@y@#z&MTfob!1}g+*@oErhZeW>ztO@z&^MiJ;L%F< z!5KfX4PIlh5Bc<Y31e$q8+#EjU{c{jPn zv>qBe=H3bWxIl+(+~tk-FrOi|uwAd&$H%p>O<4G}ed5_lwn>w_*q@2A*e3UU(jGqN zYn!?RC*dT;-;B~0t<4d|iDq7ML?MsmXyHt(mmEPsW z2@qd^cmu>AARYnn35Zuf`~u<`isBm(?|}FR#6uuH0`U@vpFlhX;wunuf%pr=V<0{Q z@ft<(8;Iv9itj+Y2jV{v4}$m*#ET$)1o0$@FG0Ks;!hBdg7_4~s~~;_@hnC0Er@qP z{0rh?5Fdkh8N|;Zo(AzXh_^xf4dQVSpM!XvqWB%e^AyGRAl|1a{s-|uhz~-%5aNdr zPlWg)#2X?02=PdWPeQy>QT!6(nTq0@5bsnJ|Acrb#77}s3h`5jr$T%c;;j&Wg?KE) zXCYn-@mq-JDvIwyyjM~D7vjN+;=>RxhWIhWlOetg@n(oWLp&Pd(-5zQ_%+0{6~(t9 z-mNJ94e@YA@o|WkL;M`#=@4Iscss=3;lJ7Avxwza6YHnVRizRwu7FAbl~U}jkWn@V zH_AA<@i^!5^xfU38+>QY-Li%$Pt_lcyd}Rd^5wp0}x6G?PKxH z5N!BW^u&=9M@<|takRvd z5=TiKA&rxxBaVzXDjI+DoG~GCxn<(9cZ^AIylXtO_Dy5*_@zep%i%KK_oJs;lO<3e zIX(OE&qx-=pvCf)n{${_B(s^_!Wqk?$Fy6>YLdcR+P|&gf}F_Az=;)bx62FLLL(KkkE&OKSTl$89<}}kpn~$5LrN^ zp(yfzNCYAih*V%&GA72tc4&6>Eyd<*S1GnQvRiGRmM=%BY>=}MrvDfTliuPV-6nj7Oxxx4KslQb%_w8{+E%z(-hFb1lcdc3; zVBW0wZ%oGgLoF$b7?nRpm5|y9NJwowN@647fXkcHEJt2jU_Cizk|k^Y?}Ea`wb&wZ zWOw}=JX6|x@;LEak$UXuHp%%P95en*kA%@99Ni_Z7<)`O$HO1MGJgc&$R7g3<+JO{tQYw#I7 z27kd@@D)4-Kfz1z5j+I{7$}L{KdGr9eQp2LfA{Zyzmm9>BB3QM49^)mo)fHf9t7d^ra;WnU=L) zZ(5b%uDUDjSEtkHvN>@E9$NPHJ z{e$|pTT%Lsm;T&waxbJ@3A0PuN7$yMuj%%>&<}al)en7DNq_I7k*33&dz%h_x!!$5@8UjkalPs2Yr&?Yd%I}IMn$@h z?LVp?A6wdVeDzp&Y};Ax*k$YV6CE;{PDFHapR6**eRAqd{ZyAzrqfOLna=>9V0*L6B>E7 zZD8cb4gDfNsZucV(-K)DKPyzz|610v{@3ml@W1{=7XQ!B-|_$Ay)XU0+&i~icEn~{+Ir|lmq5gf0+`)JBhi6K5I@2;G|G?(UAAW8)i#c<$e}7a5 zw1^!G+wbHaMNqBst^hf)@Wc;TdU0NqsN(dBlYzCGy}s&y%`oEH^r%aX?aD zrEw&M^FYruFKKhB&&D6Gq%>;XLmiL?SkJn-J42ih;)aUih!9tVI3vUzAr1*~Nr+QI z+!ErL5Z8n_C&WD!#X%u13UN}1n?f8F;;Im5g}5ukVIeLHaau)jTZrRATo>ZJ5ch>R zFvNu+P7HBlh$BN>8RE9q&9)nD3DqOQnNs67f1~QsbwHF4Wzb# z)Hsk@2U7DuY9B}q1gV7}HIbs!MhJ=;i9)SJp=N^APLLW3QcFQCnfAx`Nc*q1I#Pc~mi#PJBiYX^TVAw3?_4mmsOilU z4rAe{Hu|D7=`7B6Q%#Gv)G(IT@zs}Z8)G@pvY_c;U0>_L^Z6qWl?iqp+FQnYIAdw& z;nfYDM=l<<9*rpFJho$<_4u%i&e#PrttXmabDo^K#(Apzd(PASUvZwPb;A07$Vlf0 z1vXmGmTu=f`~7Ry^Vv1)`EyTOFMN{Ud2w48>!oAWte2OJb$+-upVjqTC+C%y;;bL_ zspY&n{Jiz!hJ~D;wA^9+wD?`;XEn|`uVpQ0y`FEo^ZMsMT0j541zpx7eMxURYvvzuwUK)sQLHufuy;Z?|(gzvdGFano zH+Fu1?=$NUr^-6-TubNtaYu+X`5dl4nIz{?yH911D%399Rtg=Pq&=GKG6)7imJ&Za zq^F%3Ds4_FiB~+Nkp6z?{-XkAA42vcWM4w|CuE;O_A6xHLiVqs>|@A&hU{xa+24?T zt| z96@3U5>Jqrg2WXhwjl8Zi7`l=L1GOOZ;+US#2qB|6ea#3F$jr6NGw9)5fYP-xP-(e zBt9WA3W-xltU}@y60?xFg~YC+#4jX@SUA`Lm-q|8Shj;CdJhJtE z(9s&t`WMJ~FQ~w&9gzjU+Gj1as-J)16U(hdUTExJbY-{5Vnb>KSsisFi#PKLDlw>7 zWJ$jtf=V^?&_psHP_MOG`+JE(fCL6Oz(1P9eD&@8g%h0;NR-&csN z^-iXs+ULr}J~{I}SDkl5ZFNVkcGX+rXRF`!kUi+RthNTV|7UO5`-UyJ;7j&K4UXFy z{V>AbxcGWo0O z>Y;rEf!vFsN*_@%)PvDndv?`DfA2G~@qI_tFf8p<4~(jE2wzQZo^X+U6kk#>*N5&} z6*$v#?ttuK$Zm%0YRK+}>~hF%SCm~3+5M0ZfP?`g6d>UM2?_p0!a`9(0}>vP5P^gV zBvc^b0tp#N*g!%D5r zx6B;Wf3>;6^Lrc(LtZuq_kGLJsM0D&Nbpog;|y)hp(O`8Lce?4+~l))=B6LKXl{0J zlDYZT;pP@AdYW6#X>D#bJ;>bp=>W4$>+Wb{Z|P`jtLJDJRN2wq-|FaK&FSc9dBV~0 z{%uF6TOT_*e|W~x<@fgpJDrMYA9i8_uUdn!7Ht}p5swm64l_)L@I zDg7(Q2<;<>HYB@wWS9HqQBB?E(Y3Ca$M~Hzk1ep*JTCJl^SJMqnV-J?x;gCJbo2N_ zVde?j2Ad~tnC+OfWUAwtS>qg&pBv~1AJy5BIR8iDgMY^n`ke4ZhTK?IQ?QKA_hKGAd}6M28bmaTA{>Z#6h%M~ z5kZ6m5femEd^w_m2n!-Eh`HIYj6Xu|ou} zD58f5Us1#lF#w1OK#Txl1`tDlm;%HYAm#uu2#85Qi~?d75W`Ru(|{OH7Kp-Xp zF%pQGKnw+9DiC9VmVxkZu zg_tSCP$8xYF;cM{2g@k_|!=CcfG7uD)PLWz*sI8%>7~_ScVW z?WiAJ5vm_^jy4^i?x%aXmb=nvweb+4a-$^v&A9)l8;eveu%OkuI?Me1 zkl&yvzXS4HAioFln;^f7FXgvEejnsFQuv*U@>?nVUPbxM6n?kjWBBb^*b=`mD*8jK zQeD>mSG70udmF#2y;=I!R}Z!~*KIaG(B2H&=xJ|ue$DYn?ag89&Zf0F|MWIz`sDfF z-}~5Pnz8^v{j|~PdFJ6w&lEFT$rBW_dY=nwau>9B>q2effAFcw^8l!2AQR09jdiYoxqfcZ=IBAyn zAmNEeX2wT7(e^0sOYmT6rb(kq8l}=?6F-(wyOWXto@YFbILQJeZ=WV)1lT~2p^Yjl zPu@oT<0LXDo^Gu+4B_i z>YS(Gim`e8O1I79w{G|Bkdd2aH_p&~c4)~_c|vcF&(kzr`#epLb;}bj)oA?IPuvNl zt>o-dm`e7}D9i!CL~j{qLEW<_WUTpW)#W}=?i0c$g?*9y5FqhKP7pdQ0@uVavX6atjiJ3Atn}X|Y&>K~HOAjeJTW_49xhXW$dQ<3k zOC*oy|o#!OC zGg|UG{UoQ;LGn3`C6_Z&AL!LbANW-}ebALg`rs4Q^&xx8=tI}%*M}`O>%)!j^{4dD z^%2?yT^o`~ax-y~m$@!Ene&p5IV`!D9g>GxBh}tQsq$Wt+>0)Gmywcl=_C1;m-I3Ph)N+!g{T!O#qt2vQbf4`^-@qU<)?WM)XeiJ8lq}NQ8q-~ zilT6c${|Xppmqw1r=WTY%BP@y3JyTQ1t>TH1vgL>N1)&e6r6#AJ1B}nP;d!FaSDiA zKpX?&8W87zxCaFXfw&07Nhr7p1xKMIli7QbOr~VRmqTo;vm!jZQ5VwLj7R0q6&PBn!APz>s z#UM^b!Ob9!M#0q}&IWNeh{I8EIYn_g3T{Wi@hG?+1?QvSeiR&#f(ue`LJDpOaYTqK zLYxueju3~0xTK;uCB!WujtOy1h;u^RlY)a%a8U|QO2JJjI4T8KrQoa-+?9gEQgB%c zP785cMR8n+>q49t;=T|EhPW`qi6L$bab$=qQ*dSq?o7d5Z6isIN5T$_S( zQ*dtz4o<c*1O@b8dN=U1{}6lLR3 z*g%kt1RvXmN{ES+8rB^9aiffp8;_L-<+(S&l`k+TF5hP_4Du=7Bh2UEut5dA>W39v z(P~hkE5*YKPxl{GWKX)VqT^rIip^OSXYHbC#r1<>B^pM!OAfvoSE|HQ?$S;7$CbWy z!R_m}BF^{tPPbp?8F7B=7P-q^9~xJF?hdWONk>@4u!UNs4MV~zch)ukSuMf>dbH50 zjH(n?wQeP?T3bsPli$_|_&Th{k6&wng|f#5UOcVUTJcd>?HyaRC(oaZtF!E+yYBW) zarLHda@U{tdR$Qd*WC@C-x$^~B+MP$_qDJ_m0r_Af}aj+oS}|8v}BjC(C=P#H~H+v zxTYVBbvHXWJg)iHPVN>fTF14VQ_J0IdO%$3rwh4lTC1=&_CT$z%|EPNP(iJ|f3C0& z#eUQ}=J+P8vB9Qtn2P2TDPdvaotz$a`%|KC9bE#>FyOaH_krj zId|{Q6XNF4en*gdXa*~aevKK0@TYrVYLyH?qrh_o${<;zrjx>>lH{H*Rc!HSTelm&J|y{uTGr z*QdvYog3*Me`s*rgl+BI6E{o?o3upJo|!c$Z1Qt$weV5R!onr$lR6{pZ)vEqXQK;?%_(Xh3;eh#}0gJRp=X+XJegrOG zLGUU{07HUUQ34qf%!(4wkf4SHHYB(q0S*arNT5T49TM=6poauLB={9Y01y!7L>v%-Ktuu&3Pdar!9YX<5e`H=FlqMauhlX_;(5fRD1w5B3L-3sxF7<9 zhzue$h}a;4gNP0yJc#%p0)&VVB1DK76-AH`Q9^_X5hp~T5RpQJ3K1(run^HggbNWb zM8FUcLxcxJz93pav&>>=n2p%GOi0~oehZq3F1RzELF$0JpKukeV zi~(W}5QBi21jHyHW&tq_h-pBK17aQ!1A&+b#7H1!0x=YbsVIuEK+FYVFc6c07!Aa1 zAcg}m9f(#xF=pK0Q5K9Wp*N`IM|%#7~=cl{Sp@B%fxr^CX{sZBFroJYB(_8scbt^T})>X5C$^V>bir@jk3$*17Sp5#+WyZ#?dJ|$~5Cp4e`5}HiYuMyji zE#H(V5>H(|RZVmHM>W^~u6ZCt%2rLx1BqB!e&jrmh?w{PEDt1trldL#^pO5Qd=J8> zJnXQA=P?49mL-sxb+R_fW#@t&&8>&qAGaPp_^k8D z)=|!*EBZQ*m6|3g@;lDVTYQ`az4|x{t_XMfmG18JTlbzdWaMsZ;|v3>p(Q6dLvKFs zY?`j8v+1$^&IeDLcdSGJCzOQ`0#U&%HV->_q3uPQTs-pTCwHCGoGP@))x` zpE_Bwpw(q3%ls0^uThj=1o>5vUk3Sg6n>$i{7T3#Rg_-~`NfLzt0BJ}^6Md6KvA{= zWJ@SMmaUP6ZBsGuAJgv4?>*zu+noG`nkxDRc2c79p+PiuGnQ71kBKDPMMKE(7# zwL8Prm7V{!b|)fbt3LL2=OdnfMC6pj^N+u`-6>|k_hASyt=(CU&5(Bf@$Y;7@tJ(GNRF8WI0{1w5@jglDr9DX1GMG*l+2oNzq1OX8RL>Lfp zKm-C22}CFmu|Na^5e+LxIPlkJe~lkwPXxvLAAO9yvG|B#tmq%fjUqq|5jBnsZuqeet{t+F{2% z{bb2+w3EFvyMKrqXu5N)sr$#XO-*-W%DeAHl{ejAnaypQn@u{3-f}4OTKz$%LXm;nzu|@+_SZ;u2*&MbCK?B(PQ=O+h)0Qygf@M zM;znMIb)1WirCSedvHgY3^CB1w`rg$U#)`f{C)*x^23^10rQWhf=?9C3V!V)-MsH; zg-`q--MlYpMb}@{i!I)*S;ys&NevfkB|3a1lNs)Gm#ThBCNW&(E|Yh&OkVh`+xOlH z-7iaDx8LQBdb!U-+~s$@rdK%CN2|E-X}!|MMq1^`U3C9f%V+@u$C;{(HfvQwJIhJ` z&$a5lwPpB$BU;T&g~fcXxdT7FCj)!F=dOLgC6f%UcGp?=qh9x&m)-SVxvbZpuepOp zMC%Qvyrea3y+jZ0H$rPvd5Rv=sErnKd#g+uINTk2>J6DJu(iAC#)&dXV1T>%f;oDN z_giT#Cym!zZSmJyckia#UeBeqsZ&>PJN6r`UEyMS`ws7G?SJ&rJ64a z?!2E#{NmmNv)^)=%&)uK-tt|&_w?~vpNdQMz5}~y{jyEd z`-j%m`rjC&5A-dj4LsURADqcc8@#51KIGG@+R)dV%A|bv-NVO~m&y39xJPu%CX?{( zb&srmOD5l2<{q8*giN|O-96^s4SihTQf=Jj<9e9SG%ak`dVPG{AZ^0JSM`Z!n`x6K zkJX=vs-R8o*+~zdn_ZJpokdP^_jmet|Nj4+jC-|Bt~Xcra(%cw_GYXS1}zEhBsW`o z(S-cja(jf`0JhzcSsh`1C*U=Wc(ga#2CL~u}vjt2;jBI2VUK!^w_ z2oWMih#(=Nga}hn#0e3oqKK4&P$6Q42o@q*h;Sj|r66F4h$#peB4!GLhKQPiup#26 zAaIDtDF|Ir#10WWMD!5hL&Og;0Eh`tFan4fP%s3DDNry5h&fO&2t_doh*2ntSwIW} zVj2+RfS3owKp-XpF%pQGP%spTsZcN$h`CTO7>LPGFd9WM8w!R4F&zrV12G?n0YOX% zVnh%#f*2CSlpw|gF(-&YDT+x^Fe*hcD+-3CD5gchxFF^QF))aUL5vJyW)MSzm>R^` zAm#=!I7Klzh|wvE*-G?nQS^4qylCdiG)nbUF}F&5@YWR67U zM`Uh9=0#*qMCL*^p6*Q)*W{G)(z|v@AoM z!FH&y^txij+2<82O*yPsdCU$)|9)!}1A3gZ@ct^JHYrxE`;KDT)MWkD_DP%8w2q1g zW=tkdm^B$Pb24Q1WXKH4ijQ+7|G%FdWf60#SOs@Vn6tEMVkV(lN_`~M0TLT0zObwg zs-#&-zmt?E9sSkMWs#3R(k1MPhh7Vp7u6PR|HAH^X3`eVJ8D1daBC`mVZY=0A+EFg z&b239Kc21az8h1-buX&0`~HgiF4LTQZm;QATu(gha;MYo+0)zaY8h-Ud&Zy-wM_o+ z+B3aWK+9}dYR|l8wmXY^nmwy)s^)!ei7Q+5KrQ>WDXtuEKkv4@F~F5`Mt^s%@l9O0 z2Zy@zbSdY`+tkmUua>tfzh73jPYFMJ0rL%a!6&lX3x1tND|F?Cz3_=IwIX|t+l#J0 zrWISf-fkUdauaN!IqP@Crd3TK} zyX-YHWpf7>I`0bn^p?BUogJ>)2Tr)3ys*$!XWdP$?mN1x-YcDJ@4N0Xt;f5@-1|@)7x=n;+~qx5m`|8JY}Z@b__(h22@9uc6VKMM zPntYXdnT%=eR9vHTKHU({coDT*cM6t;$SrS3%Qs4h1`21IEKp$k^Du-@5)~kRe1@- zM7jY%K|}=+7EH`vh`?AzWDuc2#0C)@RHEYn!lQ`z00M-F5F$i~7$Jg$h!P@9MG+@N zpo$_=h)^M7g$NcRT8MBV;)MtpB4UV;A!3FI8X{_lup#1x2pl4Eh|m>9>=402L=O=@ zMEno~fS3Tp2q0zvF$9PyK#T!m4iJM-6qA4$g`$`R#4sSH0Wl7Uc|Z&VVj>VDftU%z zP#~rPF&2oqKnw-nL!Kj3?YUHF-3?mLd+3jkPwrE7^R|^CB!fl#WW$tsVL?NF;Iw!LW~q* zrVvAgm@33rA?6A(Scu6&j8;+17Gk)HV!9CHRTT4u7%;?yAw~=_V~8O`Od0-bj9ImD z)CJ~`l)vzj-_9+e@R&0!lo)zAInOd=2cw3>g%GrfE&TX;x7O-pb3l^Nhu zSnp`Nd_KTmesgMmtz6QY_&Hf`fPcM!6sJx}Va5GztT-UmD^B_xD-Ng^5Rl5Ybf+Lz zTqV^jPWl`xu2L@`#R*ta_{CRU?5fnSIO%ij;$N8fC582#iDkW2Q~iUJKF4~0VOoY1 z)_X9T^;S#udXqlKdaKp@EeA^#{i~;Xy|e;7pJTn%>!oeWis@AY)9VzS-TS!MFiF`gV(>ZWqV5l(4rrrM*`_y+NxQ`ZY~wQwreJWnJc z7Z<1cCntT5HLHu0Q@Q4q(X2VOW@$1deU3F(k>8v;n>LxY#Iokp+GX<3vF56><`f5n ze`sKvO#57{IkkqF{Bx|inyfi>mg&i;mrr%ol>BqDW~rJgrgmeO^Ov8T>bi;Kf5PWj zv25(rZtK7xz99A6I_Yz)ShjU)H+5qhD^6|gB$sd{evTE(n3Sojp2W25tT?sRQ}WNT z;u`fTrmlXH<0_36r?!4d{yA1$vtGs2RZwzZrL*GHR#3@5$BF}G_|H_D*JOhgr?!UT zqQivGvEo|wDyFuIGD+#sK3MKk)0*c~37^X%A5XEC$S!Z=M%gSk9v9c;ZoYS=FXx|m zzx#r7QQmJZ-R-?_MQQIvbMkpRjRp~m$HjRs(Mm?V)$zRdQd@?IWi@tqFE8CKVukOX zh?PCBM!cQrt%$d8cJ*HM+0=;Dz8`t7J~%L9&5Z%xYgaUlSar|FZSrZxAPcnU-&+4e_%)Lfc-o7!Q!LbF^x9Z zez7-yxw~50+3o+v*=-rV^pOVw&y*pf{7abVIa^lmzv`c4FJ_AtO}UoR{a+rNAAFoX z(&R1AM0@2*xX*4e`A~^_Nk^&V_nzmu=d$Nkk_T^~hK}~HBI9BC2grT0OA6Gvce&$# zvLZ_aYXo=FXIr$BqGu}TEt1kp4QQV zeo<@(`bI(eM?v~XLHbES`bt6iOF{ZfLHbR>w2l^a)b{x+I$EUFj8bF%?wav8Tw{_! z0-qaYnA~`rQ9=xo$k-pi=_PHm&!v<61~`Gt+3 z0VRzFQ)*cnHqKxK_bX&+RQfX`B>1kSafU-iXvq&Pp*KIZG)=e1()8GWqg|PbX65f5 z>Qqi{5hoOB4ob*Mb5I(2(gfr&4ZBY2y3oXHLp=?+6#G#=GyrKHOs=tUHY5-!1S2FM zAwdZVOh|A-0u&OYkU)h5D%*aur_vysOB9 zF|MK!{awW-b#z&Wg}RD&4|J7i;pZw@r=Y7;g{-d9g|oQJWWD3|&ETu~e(R(8o&M5Z zZr>ML`ArwK3QLb_6=(0#Dot6ZRUYHi{QJ$+0($I_o_=%ORqHO4o_^!q)r;uT)32Mm z#@#;B(=SX5ywpf~`gPT6?<^xd{px6SmIq5uzoJ^bX{Dv7pGgZEkV$&_6?QiaxvmBG zz2|OJ`ml`c?Q%EHutp0l`K~+k<{@{}bW7b$kNwZx?CLak^R+LzTO4^yYx(jBcl#Da z>>a<$qlJr!CXR=lK12M!jF~cKO3&S||4W}Ky;yxkv3Qjsm&c85USj)_w3g>Q_X*EX z5l9wY-ox`$2jt<&OncJeQhiYPDpgWAn1ASwq&8q@LUt%$z)pqiSjf&*lpPG&$&eil z+1ZdC4%z9D9S_<0kN|)L0VEJ0!2k&eNKilm0}>pN08x}6fdmR9SRerd2^vV?K!OJn zK#(AU1QH~eAOQslDo9{Kf=f{X3=(9D5@?WMg9IET=pcay2|h>wLV^$yh>&1}1SBLV zA%O`APDp@4f>cog6%wq960ne#l7F*2NzBy07a!%SunucvTDcu_ZpPTpl6Thq34)VQ@0HH(UP;@M0c*XtK7L8KWEQV^<7)ul3y74 za$hv^r#ouozrD-wxtcZ{%s+BC7zSp+y@m0ft%hnbXa2MQiQCqZpyghCUM`qYhX5wI zY%;sWlv5oJq9eA+Qmu-XnJ4cHze4j{<`NCE+pM1Uj`Ajt$s zLIILgfFu?m$puJ)0g_};ltcq0*`O#12bh+U9A&T_{?W%s|3Sa~O&6DCk{!*BGG1;x z&Zf)Vdrwq%J?zwX>}i_1Jp@#+)JIT=7ja1@=q#G zy5G>;kOqk7UUHD0Ol0Cc&(qvU*1||Sot$FZ;-S$@8=z+$cN0id!^F13+rK;t9nsGF ziFlYW8Xb|(k4UT2!&RM`Hfo%osx#9@jWelu_(QJe{zp63?@Wl!6tf|(mP?nr-!k~@ zi7l_2PIlgxjML3a8fR*qu$~$CnQ><2Xw&= zC-xZUu7_E29h>7$8=UsvHBifgC+stLiLlbA%jl&}FFujU{OA9ZgK&q$HRmEdZ2}4P zDK-?6r>GO9MFl@%N$)uLXAN(QELk7Y zR1|qCibNqYg-8`5S4EMmqR18^U5I=k5{AfFQKZb~LCz3K!?fXX(*~{~4$}Hl6*Jfl ze}@yi{~>$+4XvWAvX{6~w#$vj`Eduw!Cz8_X^+n?v>@d+;3-k#{KrqYTByLdb$7ifz7@8U<3D>Lo3{C zSCn+GbGFtZr)O}lpE1wgc&2a9XyV)8&zmRoL*c|J-gB;eqoyS}E)vxT_AK1-$ zp!Oc?!OrhH51!BLjA>TD`m^jyx83&Gr+OL*Qwf}!8Dyba$d+~y!hfiM^Y!WQvoe=*d8t_oXrG3?M+AI^-)pkf5|1s^d1a$Ea?I)Qr z9UbN}ry`-DI@o1Y{p`|Z$@*9QOQ)w;jlWM7I!5tWU4^hU@}zlNz1#L}w-(s9fB2I9o#T`3J7V5; z?Tnh|+O_gU*Y3HKT+xo27<)QchRJ*^%~2Q(m=I9y81C-*2ofpvPKQl~EI8)2@CfW^k-bM`*4& zVqjMWb!9+TlK)H>6PEouBP@2GW&F)^#)Qb_mWjvSF($q7uJO#;H;u{Tmm1+OhZ|nf zo_+8i3ViSx&rZV<^q zWXDfHdJy?RBnXiqM2ZkOLL{juvV=$zB2S1!Au^>PRSI&YAXy5sr664j@}(eQ3Nof3 zWeRepAZZG+RupL~io78bhe^G8FR5kZ&htnfB6~%VK1BWy3xL=F#0nsG0Mo|4e!*Zn zq+OEvH@HURXBPUevDM$*&FKw^(Zp^}KRcD`-&tE2KbNO--agCXe&dZLT0zTOEnl}B zy1m){v!t9%SE#x~y%FX*`NARXlx?}3R?VxQskPUlPNx=F_Q2`XkCv=-Uv2ZX{p06W zSUw53XM8&5p5?QgF5_Bzm*v`5@5(%&zV19#FKc;A9@p~aj@I(0Td(E6y+pcMy{Z*B zHASP-C>=)AuI|ibbosWdrR$b&jBaPLSh_oNT6%2y($X{hwx!qXV-|a_GnU@Zd~Woq zztz&W#}T7nu{SLJ>#j8hm^T{(ix|ehYjcc2cSjj?a^&EK3&6PeiwnQF=!*-!9!L8| z5@Et5q9~GOR0&ZgM4b?Y!e31f#ZNMt*u6{C%jZxq zM8yy#L(~it&$5cDS$^1AR-Q-Y5T!%Z4pBTr^$_Jl)UPNGz$V58AWi^r1BfF)TtQKs z0pbo2hk$8a%X?ec4qS&0xfUI!buBNewgaxo_P{wI?g?>Fh>Jp;6yl~3M}@d5#91Nk z3UOG7%R-!1QQQ{dxbUCcwfx`h=)WQVW04)ojqbT?b#NE&-q>BDMNN0fI%VCZDim-hTo+s>xP?pQguX4Km;EHa?q|OipA$N6 z^W=oq+fvR4t&z()9daq>D{>j<5L1o2BjxhVX?ozLcH%;V^x8YCi~BUw>nv}h*L|mg zUT<1ez5e{{deDHpdV?wD<&w0^ma9S>g`+9(mUi2(mPft zq<8$Tgx)!CpsDi*>Gdw(7BqF;@~PhK^pB$WbfWglqV!Kh<gQD(>dY}4li>i0& z{ffOPYIf=a%q#SPMV`|KUYns0y4z1595YlOa;dRCbY)9@*nz%sp?qcispTQ25rcB- z+B9F&$TpQsqXuM_iGD0H$)|ht)V-d_@Y7M&GOGM-LN3-jThhOb(xCc7+f0(IP?^nw-;fiM z%#bWeW=Q2>Jr~Py?|DY@vyz9E=9FY3JokY?UPXD;R3hO%;hBVQP))1&9BpDADY46Y z%XW=SE!#C0C8?~E$}Af?q!S^hgDocKG?IYjI5S6gwS zf3^W00Pz8c7buD!Ks*8B3lMLB_yfcvAU*-{3W#4oJOknz5buEa2gF0*PcI|KPk25t zAtk=TGTs957sbTO%a7YwPD)6vSIc+~&p&!1Y7K8!egyF(3cjQ$-bBHl6vd+`_!Pve zDEJk`vmm|&@h*seK|BoNV-&m$;%5{*4dQDQyba=SisEq;d`?lkj)LD&@H`5>2k|}% z{s-|u3O)$&LWmziJQ3oH5O0L|qoQ~u#3vQSD4e_N(oOkM{rqPcuEaUwQXJ7b z`4RUt+4L-D)8%e{(FpteGrMAAf;(vs%U!9?WTh;|sqs@Srwgqy&h%VkIrE{*@Ky_$D~g`&KoGJQPJD5ShSVZFYf#Z#mu?9TZf*p!mKAa;?;3>vizx-<05&;+k?SD()x-Cw?hS zY|p*tnWX0vjxCd??vrx&^5{3DdN{%EImvD^eH#Bbk1h}9m)SUT{`*;TE;#P*d~@ku z=fVYVIv33;=yXoqZe2X?N9U6M3#@NVjBze)73Ex3|BiLp?E0qVrR~-gz6Y!;%T=(h z{36b|%5=)P>P%ki|E{~Os}Bydt~qqlx^_h?>pJInXXJE$>-t%-&R^F ze0I%x{);cI7e2}Fym)Pww&|h_J;G5 zD&Edd>#nhWR>;qKEl+#rwL4j@*YA$Bet!Oj^@~gGtY2#et*9q4o~ zddB)y@21YL!+Tk8HydF6rdI>&x5cwL|F3>Y>vyJG&bVS3tZ}Xr&hPJiX8j?0qw~(S zbj}|q{?B>$;5%A6?SM7CUuSCu+e&N3%(bnV{9mwUx?b3t*)q(UIp&@xP0( z?^u6pwwXUVvq$E#<`{X|X?dfPGiRGdd8cI zd%;kr9j~nLoOawZ!!xMt;-Q{FWuI;78C14+WzV3p&6*o(P}y=7)u6I||KHx30N7Oa zfBX(JW-&PSF}9IqY{Q+w7_v-u8BCEa%gIj44M~!@m5SDrREoHkO0rBsifWQd)4y_j8(K}A%5qgB$ghBTg`VjeZSK!%2&{l#-HW>xIm zf4;E4$}@3m@uc40OCgJ4)h$`1flW3kW0ll6;@PKEm~#Y)>>#hMQaNiUFY6JHR{v&B zLz~}jPM>N>>8|gdh|eIZs3^(^QAdbELR1o>ln}LqC?-TTA<79+Pl$plii$#%6r!dO zMO744g($0{s4GNaAu0<|T8P?06c?hp5aopzI(q;iAdp-Hl9NDk6G)B%$yFdZ3nX`e zw229nc2avMmF1Icw1CFg2V@}8H8&=Qdsrl`1 zcgmmvoU*}3r;EBSRt+$)g6}w^cS>jDx{84NmQq_+AnYIQ#bO|iQ0AWF0vy#G$P4Mecf-}$fveY zMwGfyFhpY%MQ7l}t{VmFLgHM^7P|3iH;v z&{vM5vCvZoLu&~araBlV?x`cN`Z{569U-uavx*NM_e8t|hT3*g@zC$%GAdq}oItz; zhT3jX@xUZd;)98xkjbEs389clp^%B8kjbHeJXZ}LIV;b-fS#LT5~aM@Cd-R`HA-md z#POB4@480ck@1y&_T=io(I{`}7+dF2SqOWvhy>3ae-rOGby@(y<8 z3szauZ&A{x=~7ha^?IFo5EF)&F~pQPZ~haM0}o^%o&t+E*Hc+eVDV;a9>-s@c=M$x zNoNhkY}~}ts@Axvzjn3a;#0iXZj~4Nm7MZsJow98Yr^rHnTtmZGnd?Pck0p}PW`#I zYcrp(9c#W2o0hq}Ze4x(e@|t;^xG_R#XIq-TkmRXeh^eP^@FQ_(YNh#rf%Q9O5b~9 zcTEp)H5IxpT)kV!S3hwato)V%FhZ59}aMBm+wQqv@( z@jlBe+qiu#TaxRTWTEFZa;T_}9&sM#SGlg(lCARSv$I*6aAb~&G~xKpe$_mb(qzJ2 zb7#WVQ~sKAR@uDsd#!-Y&-4k2gE}lUZN=iFXqAJf{jTC5)#kinl^M z7UH!K&xIFjnoHiNCF1FvA8&_#pPDG%&)+YXrzWB@{GLDkoGDQv&Vw4ke8+~0Lh(6N z3ev(3`-oa8ief=j3#N%J{yUV5M>;lX6X$o~J=k z@rJYUq*YG)@P*F!LEELH|9R=;|NCd8_D*TwkVczalMAE|!t*=<@5-~qBMbhogmg_< z&kK@n3d#GVo5DLoz}lCbPR78>K$0>l@2jBlIGMO5^DgCGazk|_=QDD=n)P||LGS2T zZ;jM5ONUHRHc#dBj9igFh{FvSGcv*2&He7vs>lGwFTP$qgeWIN{KU=i`3F}S@Y$v7 z6!Wfnfz|Q%^PQA{Me;qY5@MMU>*R(D&%8;QGAr9z3wY(7FDaJK=dgZ=2SB_4;t3FM zfOrJNDxA#BXsX1iSHN&dUM(yFrgtDA{bFl1hBw`cDw$zNXSh2iU#R?@fa zY?PT>;&<&_=D0XgZasXw&ciXLT3p~m9zO1CVS!DCU1r4uMnhlRg_tB)Dn3lvZd5xOBEbo$kNj@cA?ve*tUG5r3g35W`CAShOwEpVZ530%$-uW(h zRyS35V?JrOoaf__ZK*sA(I-^3=E_=nWoyiq{4@!^v)|OG} z(`NSQ9rtEb-Za~+vf>`I>ikJ&wTDKS)u;6_W5#thYYa>>uSlt9)@&7R)@oSX^ej5$ zX&yA|BKY1`18-*_(-(saHit1dRK|ogQY%MD1}9lzr&MOypO8+B748GQ+8&5{fKj3 zx0@XEPQUM*KmVxX*$?h+cl)OW6^`(Iu>7b(6Qv^@s6c6Bi+yST@^PO(`t_h3HUGKi-*nMjS_wE&=%*<Nl za_s4S!oBCAuIAo^k>=ie?{Vzwzs3Dw*L7~5diYVj%`qQWdL`yS_);|`_u!HHVm^DV zf6V7=X1Kmse5>m}vq!qjyZc&Gafh~Wsj0Z_DqB-=Yn*baskp_XW7JgKZ~w5S;%@uG znu@zTBSuZdePD5nd+Osc$H&fy`El54*H67%uAkdK=K7_{-LC)Doap+s>~PnKqSv`j z{M5-6`BHsbiFuW6B_E7bP4L>Jy3W$WD>=*bjxx%&Kk4MjifTG-+0WE;S_TkC9eTr6 zNd^w8>9m1Wd^HBkT?(uC+KQdgsF`bCZLX$H&ruez%l+~kKPN+TH=6qTWOju$qS(d% zClmhELQ>vA;nKpIdtYj(R+EW3PCIK!jdwXNZL2w-B)eA6imTExF zX+@=W>bFYjQ~b!e2e*!_3K2nw5GsloLIe>aiV$Ihh$BQGAtDJ8N{CoO1QQ~f5aCo5 z@q`6`&ej#`dk8DvLtG&O3lUjG5n71YLIf8gx)9-oh%ZEdAtDSBVu%<+1Q{aA5MhRh zv!VzzM5Gl(s3Bqv5o|>fZHRCyig-f=Tv0?EBIJr9<`6+w6j6r=yP}9YMBo)gD(syhJMAtN_Rf$UI824TGh|zJRAKK7*($Lr?42PS(%#_wo*A-rzpAam z^*l3V-wjn^?+n?cM^xB5Lw3e?752`M4K&@T_y{!Jv{vy@7}p&N6gu#Dx_hnS#ziZ1 zL`eC>PC=!>Gi7tPDEuV%M7Xq5-U2gaFaAu~z*I!}SeGY5tXsQBWsy2L+^)`dJ?7ygyQ1qq-_kZG^|LqIrvrC`$KA3W$xy9kqpK~vo{*`NSqcZL#Q^#ma zo89GpZg5-U`P$9gFT^glEvp~oelheJ?ZqFOI+lNPv+bn=iybT8Pqop4z3PPo+iF)& zNBaHcY%kBc(Y?lDcUoP5ja=)x4v1NA*IchAOm=N3^`mP;)*)+E-H#r$R@L45rM0T= z!dYjosylk7wW@C0#%HanyS1G?Cij@i-Lso&nd8$n_3(in$;Q6NlC=-J)ig@fKPB_d zh8ZOvjMhrc=xAt@5{=TsV-3}%*1dz~yGt!FNBvTqqpp;uXt0kpilkZ->X|ce28%O; zK=qRq$$19U-@PZRS#H5mrK+SVNqMIuNOzdkStcE1vZ>B8j(A(IWZ&vg%Q*ubXWoej z-X1jhHnl6&W#*}7sdLI9KXomiN}6n45Da?qH55)BCV(!Veg8xqH=`2E7FSE5%#+xt*9S=_pV4QDo8lL6=_jMKId;J zElSDXQA_B3-h`+oVU!c1p4bKoswgT7QBsJSLKGFEs?ggHB4 z(tNJzAT12uH63IU0&6;;T$jplv|t~#@FG4-(xG9+ywh%_@cj4vlSSblLI`t4hk1&< za?qN=YmFnTsE+&C_cfYKRzkBLLa%xqRLPi&u=y<$`}AhYOw$wB*&tpG@ob29Lp&UM z$AD#*Rr4>KPF3eQyKGuUoewqOyeI-h6%<7oAnE{72t`o|h*Ch*0y2;m?h@62CH<+1h{`~e2BJ0)#et{}%v;7pfjAy0YX+u^VF!Ws z-&-`|;ij2|cg#^BXYrM@TtFEz_xjf_N}>}k;gq*|Nhcih_U|y~zq{T2?5j3?!K~Ac zg{~^5)tfv62caUiM# zQ67l;Kokg~LJ%c_s1ZbwAgTmWCN}SrpG2Vu2Uc}|R>ercIV~rx!pB_;}zgX)O)m|_$HTIV% zhgzqo5HB~ECK*!px;`O37WOF`QM_RPB4yBPr2`Vdwn|$se32fO=TYkxTtU-ScX@S` zd62#r>B(MPPKqK{aYWTRxB<2K>D~2d7i7wP)^Do|u@C57s7LICu-{UJr*~0ohYJ_B z1$(eV?h9Ll-UWLC3&9Z`vL3=K7I(d_c=e$t6uYfbC1Y6+;bkWWx<4{q9ZwTmxE%c@ zzqJ%{HvzvUF+sSxQ{En%QoQH9&Qsj;K5pokzv(f@vl(67R)xI2TUE%Nk#1{%Rf^jh zU{%v$4X~Q$Ue>RUF$jO z-gBS#nhT$VyS7!l-o5S1-sbj|W6o?eV4&5k<8IGDtKu75sOHa_`DMAu{yp@9QqgdnKtcelPxMD-xeQ6@^(Os#GK$ zO0qANds*t-IwJdO83LP3ea_h@4^EYrcQ{q{f&@8TNLHSkYh;z*<^Z4iLB@J>>4kG3 zIs(xWMbQ(8ra*KB<}?e`S7C3nfanZ=&ly&gqQW`Ds%kp;d(N<`dFtq6}bdxQo>nkw4iuh<4m}akhb)x3@Uwofq^SXLpHl#|66 z%X{`^k;Y0Cv}{$+JT7Y#*YF&ddwQ2a~9cerdmn7D;HZ!JJLfV z8xrbS>rw6dX&CWoU*zL`)@QsAu``IRLF`RYYz|^~5Zi;Eg(9&*!q}mr*doLp6~!hY zb_uafZnUs9qz`zr<^c-@9vX2K7(#=&{wK8oT(5I2JP)^f$Q z_?)+vD=tPDH-oqu#N8k+2XQ-y>p|QP;(`!2gt#Ka9U(3WaZ5#UO(G5Vgt#aSOg>tx z;-N|FjfzdLeNwSm)j(%G_7Qk~z(2Dfz-1=47f^xC-Sf%}eZp1;lAd~5bRcaV7i*qX z0_*u-GthZ-O3U}jv6pM-QOJ3B9)-~y5gr^K5f*Vik4U<>pH;m^;-LQejx7I`Qkpay zNPm$uRCwQ`nXr^|oc*)g@zkNwQrG!$^jUUY04IdLpfq}lH z*Br!yXKCzzsxJv?|)pAqBF^Ka*8nK#833Al8@=8O4>oU)m`<7xd`n#jogl zr4eMGvx=Hi_2J`dWA+!B5VJoeN&BeYTJ7V8(Hb)+4hA{2gGZ)lfw}dU?~o~JU(8kC z4U$K(h;OS0vJ~<3wf()6+U88LYYM5}CdSRn8#vjk+5GOA~sliz}= zOq^o!l-g!z@0X__Q?2Xl6L8$_Zx#Qx$5)G2a$P(V;+@bl#|bYbjHfD!w?aG?;;px8FBIukL+A;i7li4Gi857@X^Kg&A2VCN(O$dq(|$&)=Z6@rH?}m|+;^4Hc3u@Dd1OuF zsu?Y`cAY|u_QNY{9cq7Lu$=L~wkgl*HMO225FBRx^NzXB+x%9vL`nW-!wUSlcUjMN zvDhK+;v}`!SVOpZzNL%~lFV2f#(D;NlyV_?mvKkd_#*kMXFq#x6%I=@V z1G&q>Gnz;Se#wS6v*FQ-;?>YQn&@Ip&k_Y-Lj~Y@_lt@`@I6!lq7)FdfG7q;H6Y3X zQ4ffMKvV>xBoH-$fd$fcsCXD&{b?01BU-Og;gMnM6h~DxEFOMJarCJPiet83!Yw@k zB|V3dUeKe~a}N(&A#Rm3JnTZ2%DH=zxodHIjVaN)54~q*-aOb)v)}%TC39|m+!0#* zR(Dv(mhQ0c`nn?;^>svSZ{dzC*}|cgWc)wVt(IimdCH+piguJ<`5#A_UVk_O!_pQR zJa{nmp?i#L`aWP>J8p#0>oORYhBR36AS2X*WSN8P=lFVK;ykj!kzt;NP}_jtBzRLlmHAMQy}^FaiO+-F27B ziaH`bBq9O?5g|npB8V6%iXcHmNzt=@9K>rQiKsIh*&`cOEEB5&mbP+2OZR4 z!i2zJJ>mfYhln^t$YEfx-rql1k0}V=f6{Nk{~|@Ro69`(HlF1)qZR#odo--8$%qJ* z3(0$)hO^wwXE_aOo|?Y5$qXy=&Jwz?#{v zGr8jLLFrN7+A=DA+RPrk@>o6T^F*`7$rQci8&T$!pElB4J>N{1 zb~3%qeHHb#&-|z-kN8c$YDP)3UB^%L_QTv}huUxG9oz3UJB2LNJJ(!mcK%_b*|q4C zX4hTMnpYp1Vs=}VW-_Uz`y=DsX_6aCCz$omYb_*NSSSsy`Ti>w8Z0T==x2#c+IM7M z=7UMWKxU4q8D+WmX$i7EBlE}9Cpp6BNpF_PX<%_2@gqphyg&10AdIbV}KY0#3&$!0Wl7Ufk2D|Vkj`* znOb5zY#0#4h#-apF(!yXL5vDwSP+SJF*u0PDT?7ij89Pv5MqQ7 zLxdP3#2_I?2{BBFaY76fVx$m5g%~TuU?D~eFH-NMvS9lL(h3N|t=dyXOdk=9Rd}9LZ{?WXmgAUcow_ zge+WgRaeOqMnSyfCs#T0^qqsxV=xe-ffx>KG-I`5<4G<>`|!sU;|FbW^Y?@a))b*8 ztrw_p(`#>6Y*y8&n3!O9=YZOPW|RI9Z0aw?f)+~)T(8_l>kx$&eKsrKQc zlH&)tG7}~|Z%g=KdZynl$IoxjTAZA0{C(YygPmB!xz=ne62ZSvS^h9QtZ3ezytBh4 zM98Y2kTkGWH?49vr7@8(5i%)vYpPYKoabG7-lWUN`m1N(>X+laN(|qXvYI7=O8$CD zsqNhTaM|PK&nU%NP0D`6(We<9j(yf|34@pu^elUbSrN{!pAAzZCJImgnpFn$EPIIQ za(_8ux;vF{PFe3|750?%FlqjdSwl=4V%`uFhnTsdm^wV~el>9ezK1Ko%ddZJu#?~Q zFLJ$ePvED!*Xr_>94ULhf>U;UjvM(s$gyWat(eloj-{5luB}-fi#h=ohQHK^eEA5-@jDF`HXXQ<+r6%Q)TGV4wgSuO4PZOo)G+b&;*GY}3 zlhl@)OHHY+6gzT_B}rRr#{Lqa*ZsevslEih(TvAat?KlxsaAFRjzT2tm-Yjwtmc8> zpb+cdS>RSQmd=TH>PFZ*GI3qF@A4g+x-IhPH` zfjAGufgnxkDBAr1-)nvLPOd_Jei;#L*TX|lLO{oX*f^?*S<1zO&lX~e@V*R)cc zF!CR0dAH^WgcK=e{X1*aspk)h-jR0~?SXtAMXWqJr%*4tD2gb$D6wMgiwc%kx@c*n z?B^Uo7rXk=MrmBn-PA5|Ekjd9MB@?=PtSAin>6emZ*SDBk-f3s2CESHDlfL%2FE>|Z{mGJU`KeKGk2 zFSaT2VqcAD=)K=s{CC=Q6Ni$2W5bI3KXLQnz_K=ho^tGfVNNj6Q(j%ggX6wJkzY&i z_}~A}D@@h$)?9MxFXY6%a@SBW9;;$y^?!IaB&3AGIYGdowln zY>>bVkS!Vym!F#cymrT~jmEU6Hyd}Zde)eJ`%)wAku)Pn27(01nz3|@6$`Wed5fWE zHxm~xRib2wng#ObCVr@IOBJ;#)+g`Y+*oqv5-NX|cd3@EDmrV%TDi2RtvP!YS=9!e z-QsLj?5!$&RaJnkio^7lmv>}{rvaK(W^+~V5Uv^@Lt9J2U6>v!ycc#xua z5yX=q-URU|h*v>83+CH2Al^od6`q!9KUMv|YUW3F3pF0khS#&<`D}Q8kc=`}_Za89u)otMpJJv>z2$|xJ$h^fYex#=(a%~sA z#NHd-CFj)FOQruIGbtkV(#97uli~-xtRurw?#kEn@?BnYR4BVfuNc4IUGa}yW+mGk zN2O1P=+Qe?yDM*cL#wi4l~#5BLao|EhE{#rU0Tez30jST!?Y_>dTTXXb<}D#Ow?*u ziPh>vMrpAj?Ty%9ni_S#s%_NUU%_C#q6TYE7(DADZq6r0qZ!+c#*;P~_TeiH1_Lt? zxbSosU+Vn|*;6PyQz^XkF?ZrGYC9+@E$?&jfZUA-Ir|hg3f_2trSoF*T0SKJtRLb5 zFmH(fZ{SOKgrax_#4{AdJ0Knc@e+uqK)eOwF%YkTcn-vSARYwqB8VqJyb0n_isDre z&r%fcf_NCj%OIWx@ivIZLA(y)c@Xb|cp$_JA)W~FMu&zH8kHE=i0z7gJrY3 zh}4V>Z!fyv0s20Zn~d<1AD|z2l!_Z0`?DipF?-IFCN|-n*W_)m#o`OS>E|~-R+_%l zO5*&?soDG0oU*eu{1(GgE%Hoe&vd@1XD(V`?)mDJ`(lrejs2ya%l{CnKvjn&NN<|i z+PL}oUfTH4)eU+u7;V5vgNgCgv`HO*({3$o(!V#Z^3NZW!v0|~GC%bjrSoE$l1EsLvhpsOU18nYwS8MzL7tP-M_O|-;3G#)pKcuTsRrm(sycMRr^SkjEK8Mdh{DzAc zo+i}@bw7Cr`{Ik-=lPG8+GOxOnk8Uhp7PBa@et^J?WN)Y-6B2;OYdvZBL80PyP!tY zP)R&qY!AzeeQWGgWJP_Rj5(QqaFy}w(shQnOZUgO9E%;%+L8{}8$L@EGX9iRFZQH$ z)b@Vb)V*(5W#hwcHI4l(-Z^8{g8*GXH+QrnNhLmPNU*aZyHgDRvVS} zxs2#{9y0=stev!eAD1!u|58^Qu>NO*ju$#z=y0L4g^m_FS?FM)bA^r-md#?xESAe+ zsjN}5OcqOIu{;)wptAt_Us3Xq76sZ5vR5_EwHx#gtVSa(l-FR<((fx3O=)Hg_qT5q zG-R1ST3=;T6-{f$Pxq})kD)9ue({jT@O)P?UL+pH_wXu+XTbuONwPa z+l0xLO=rMah0)9plcI&RIZKO$rrI`SinHfPP?Rci$h)+2RQ3GTvmaE$^DQZ&$Y(s? zvd$A!gJMZvd3DR~7xs)jQ-jfR4M5|5tY#43BP;y2i1_p&N(C|qtpmiDApQjLDTrS| zd<)`VFyEFF@j33c@U)Z^j=^X6Kk<^^<5TMe;RL68`f`y9L%t!Vdnf zwUV%6u7@SVG_irgXU%j5H)#{s#NS$OxtA4KCci>0E8vu!rC(0Z|Uy3{t(sKgyER}xFb=?Ak z690;!0a9%$CgnF-OjP|lW8F5j&Zw+4`sW8A`Z|Nm3_2l3W{b(R=7_W2pfn_uRAn=1 z0+%wIRUVUfX_~L>`KxC?s5(b@mnQxQt7#!2!Cv6CNV&GP`7OKo^bE=veb&E|y$FL)uhz|cg}xyOn{stm)kuLuW*CKk$}i2((? z$cwvet2oxS?aNzi+kZ{5?bzH)4O0pXB)m+XdG|L?Ma-V??dinvJ!Fuq-`hwdBCA?u z{O0L?7k8`W{#Lag-11?u`^dwwVMVcHh%H0xnJqB%ZllW6cv3gygBewv&8wN77*;~rru_IuuSu5wzOk*9WfB+Ak#dB1?_Ix}r^&9+th!C6@o zb(^h=G*J8PMa}nn5%TqTyT9HlStP^8i_NniIcxf_^X*@Mo}MPU7*wQ)^Z@=JfA5q^ zboACekMv^Bu+^yQ_wGEWJmd7pEtlXRmx1STDLUX1+r%aM`$=-YHePI={V=Bl)HNi& zhE?HDLmk!{;tlN9YS?}n`aNghE1rE-Nt{^WMHMUOJ}WodyiHv5^SN2oVC^~WfX}${ z^|BnWRToZdV&{OrPIb>M@)6H|M3!vRuB)Ru_47Ngirpr5iQkq@{929Y%(<@;_Q?%H zou0Gv2g_jM>~`y^ZxTM=DCUfu=iF!Ij+?cKYj!?S95yR0bcW}&P5$5_GR`48iq#r- z=Y6xBG55{-J}WnSt|x){bQq_c_h{pH=BgXUA3Gf8O7brLC|;=^vDaP1NIhPUKl!_R zpQq1~tv`^?5A;Yg9Z78-mA%pv$!JPe%;_ z3;8sDgyVdB@Go={8BT-i;W!ZIfjAJvi6D*yaV8j8o;RKQfSPO0_vBe-z5Q04a?AA-V*P}*1<$uM7H$l6y6&rD zEPCdMbNj4FV{gr$9eZzxvi*MIR_)~Bezrdj^wmynZD~8bp@o*UOsax&D{DdbpSBgb z?UYvZmS|`24V8?L)IXfXl200;_Ai{F4>vQywG3zY%9phW_hM&pbCD5w@GV=3jC+id zuPw5bdeNn6Pv2uJJ?n0*%Xd&+i>+_iVo(3as24H8R_}|qjr!k8S^v!?M#Fs$XWW8&jYh9r>ufxIlwp6osWX1m z3@u?|1!t4aBeka2o^Upc?V=?neBx{#T3<`5{HrbLn@D-~{B)zOReFTc zdgHUUHjjR9w4Ik`OCG<=xN62cXS<&1M*HD+Iy*F;ZFKA~#M$wuceT!O(`=phy`Xh1 zG1%7im08-=Qk3pC|2ZS&qk+!u)9yEVtZ(D&IdF`T`ecmrnpWM6YbO_R_No$R^uF%@ zoW1`jW%OzGp0m$a4YhvN4%zy>TT<(P`d!oUvCM^yVzv;NfevAq#G_ z>D@Dp8}7f~IW%sKF>KTr=kOA9j1ir?IY%6wY>bMHbB@~C&lnS0$~k6bOJnTU$DKDm z*;gBvS<-gX%eei-Warcci;O!C^m9&|evfhIhL+ChgGU%?bE`OcNO_T>@>ZDsWo6+e zSy?Z+f`Zk%G)c&QBo5cTgS`66E0r>j+_t<5Q0DPE5oI163F1r;hk`g2#IYdGr6>*t zaWaUbL7WZZa8NlNJ2)PjI3K_P*>FOLBSM@J;*b!hggB<6I48tG6~##*jtX&Bh{Lks zv=GN-!+9YN%!U&~9GMMghB!1EP7QHvHk=#c;1DN=IJ%-ZJH+83P7iT>i1R}P03req zA%KVhL=e~z1&A=PAr26MP!y4X2!*1E1w=5|5DkcMK*R$gAP^CO2nj??Ac6uB6^O7v z#04TS5RrihjiQJRL~z&;9flzO_j7H%wWxbCa(TJ+4X#`am!F00g2NBjN6JuxQ_kJSD+ zFe2vE)-KxV4V`1MmWh+jtrrtCGg2!urFcxyTk0CYH^jO^u8A;;T@~&MwSRAfKHT0F zuI)0ySH2Y!;Z8S-o2y-s2X|^EG9Gi4d~LN>>cv$t+S89|rDqv2Wp1CSl^ru7rd&?&5Y6T+ydQOwF;MXtkOhi>ZC}Caq5SJu$KM7ih7kkGkqbIJJ6T>~_`vcBt0i z&6iyb_f0qA7Ci21^onjYo_?##{&+hhe$?!kgo(9`CY>k6H0@Q)XcpTiCNbf#(L6LM zCaFq@mh??@OpBA>Xf5CSBj(CaKhRpGN4Z*Wd`WBb=e%5XqvKEeVmik?pmpB2E~aaVaaz|`o{G8pXb-L1{8wBlAB{D-PkX}E zV||LzbKq^R)F&Gm*R<;Ix^}W=^r~{DtM_$38omFh>gv<%L!-}EEo1ssJFfM6w@OU^ z)BCgmOHapK|HV3O;0!Zn(3?+bgNJX98M5GZP4E7N>xTQEFowouxQ30o%@|%{v1>%< z{>F%-GhCx$uQW#O9O)VpTGbe{vWsi%*ICAmPmYKgmsv%-X>#Y7n^&LK#`mcg<9O86 zZb>X2GhzH@ZDRSOF_U^O)h1l zN56jcz52jq>E@tEN9luS&NhdP@2cygCz&_&Y@iSA(#IUuxP(5uZjw2oFe~7 zCyJY+jyKmwe|*#&v!{|i_SN0yjp--#aZkN$-ei2P-+bHS=6J_jx7W!`2c=~E64GjGp`)~CMK(Y)iuM048Wq57S(qRi=c zchJ*D|8Ay9-Vs!UVRN2UhR=(|Y_@cXuB@yf5@%UigS*%y`<$o;$-saSsSM3upUH|& zTg;#e^s~mr$2YOdJFj?KgZL)$YAWB%z4>WS@R?HNd^flLK(3sXMT$XEjq{Omkko^u zprWKABqbrK2}w~%szOp0lDd!-hNQBhq%m0176Gve zh-E;m17aZ%D}h*wqF4*WVjxxnu^fo?Kr9GiMG#AZSQEseAXWvjEQobMEDU005KDtt zo1$18#OffH2eCeg1wyP4Vu=uIgjgiRDitLy3zua|CwIKe&yQ~+Y}%$__ZI3&*78j2 zrc|u_Khiq4>CTs*M!2j~-qP2uIsEc9_xD>f<*U)JwEs$9+wc9+>uSEQuTOe$^s7Z* z)Hg&uIC{hP@0zcjxM}q3J6{|z_|12=nQuLE;Bdy073SMFzkYc0 zJ&&00^q7Bm%P@!eZlmdk-%EK|f4`)Dcx%J)`qp3X9KH9GXOhFCPMP5=o--qkJ*O96 z@P`?B;BB+S^e@bk8(|vRcY35*A*q2`;hTH( zs2W}LsP{+c(Lwd~=odQcmH!*5SGm8QUNz$fz1pbadUaQ(9@FWlUcNg5KbzntH>Pv-G$JL-a-u->5ep z`;BhDsk+Rb-sduPSM(a})tnPRA3XpqvU69=3s#jB% zL0l2yju4lGxFy6jA?^urQHYyDTvbur72>iGw}rSa#C;(y3~^(KD?{8F;?fYehPXDw zy&*0RadU{PL)=|aTpr@~#0##kDDDrD0Ei4gqyQoZ5J`Z@0z?`h@&J(th)h7FLQ&)b zA{mMz8xZMG6#0Nih@!{{L`ooX0+AGmtU#m%A}00aW``q0Cbx^Ejx4}4f?*$(_(?mbzk!nSY zN_9}>PY=2HptSUa*=*0G+alG_QK`%{u(yzkOuT&E+WcffT!|{@S{5 z!qKIwHISPi%#HA2ZU&~wF|zSsxeM!#X1Tc|xvy|_UnSIimFB)u)u>$`Hz(RJ<=1#A z$Hw~`*PQ!C@0RNluHclny`Ma}DNu;~_ZA|R0m@?m{?UI$Y_^yF5M#8h=z#IRJuoQ3 zW_u;YrdjWnQzpdAi0=3%O?gj{_nb0f0i4ro^Wtw(LhLXDV*6Y*P?9>N6Ax76V4x%w zC`n-l|BCjgOI-WN`9%#S?@wsiv>DA=67V!+amZV6l6`-r1JPVnULw&c#t@El%G=!1 zOH5ab-`pR38=>zlUDBNgqPh)f|7MSAR4nIVP>By~g+=$yc;EX|LJi|B`D}`rKYS$(B+3x3}zdems#8 zdvKAx?yp-i>TP}_z5d6~XEa#6FumccGc)4uzAL@aQ)4q44;_|ncix{I->zeNf<7j> zN$Zd8O_RGNH!I%3o>*c-a`R&Q)01qal9RTF*;^cGkkNAaxArSPD3Q_XfgSeN%Z_EV z8N15fcIKXp!$JctM~Lv?zX{}p7L_by^zqkqM%_WqCV${6tLa{Kk;(=!IXI4XV6q-hz0XLU^<(tB`*KBhtX4Na3X zhITEHKCIjo8N=%yOCSDoaK^|ok@k_F*GL|9{0IB!H;N{Y*^_A>JO8WX8(&^yA2;p& z7)s5Fdv4F~pZ4 z{tWSHh+ji|8{*#(ABXt4qWC(*-yuE^@q38xL;N420T3O4XaPhIAesQt1&B64^Z}v~ z5S@T%1w=0(ngP)bMbQq3en2z?q9YJ3f#?ZDQy{tm(H4llKr{xTGZ3wT=#8Ri4n%hp zMSCFn1JNLe4nedCqDK%-g6I-Nn;`lG(I|*cL9_~@R}jro6y1Vom!jwwM8hCD2GKHz zo4{E<&^sqK^=b zgyhssWCThOs;YL>#0|Cdp5b|mGe_;HAqXYU3q$Ho#Hnn*Ey-D z#(vj6x$gf>%dB_THOcjlbjfURLlc$J1xryZd(ocUlv3ieJGc?68%P@4q{%I3Kf8`^ zHM*(JAKnU3a`gT#@(z9**WmXl%5Q}HPRMVC{9ed!hWu{GZ-@MTNE|@o0TLIG_<+O- zBwiqKqbTtMi6clnLE;J$UywM1X>#uuGv4xT50upKmiRsUno|yz=@XpMIHW~7cO(f} zRK|hKr>M|3nWBP>0FhK%pJ_FeN}aO;P&Ha{umxdxx{?5p99)0@0rc{bK=Ef(?vBw&jH+H+t9Cx6bep7?~ z=FJ=8^zp^7G#ztG>5lKJniFn2u20yOWlrk5PoK25iat5{m^u0J)B2Q{J?4~&rasm7 ziaE8{X8jKH33FPLrTU#2x0%zcWSD8L{$|=Ai%lX|tq!3os~5Ca#AdTHGU>1uBUqM^ zy{?2B;glc|%!UyZG(F^&vPcuYN4h9V+Cb7rQPK#KPKuINko1D286@2xX$MI^MM*mB^#U>zj zp(wTiu@6PD5r~~AimgEG1!6N0yMfpa#C{+)1hFGUu_cH-DT+-&>`GB=3u0f2Vq*|H zQxsc+*qfr*9K`M*wg<64hz&yQP*H4A@o%z6)fz-w5Dh`n3?vgotcfCmCXHLP@F$95 z>^5F(p8YVV1k&Wj$%vPU3n(YZZs18E=r4QroPm#c_Env4dSc`s96a|~y+6X=-EuGL zeuNZh*pFPlG{sphYD;Q^iVJ^pf>otf-d~amE+r1Cvs7(8 zn;%j4c|nPpEH27-xiIA7igE++f^H(awCC^Fdt#LHd-lWTq;?5b7n6p$yQcAT@5)}X zDo%Pb+5G%8&l&iLXJ4fiXXN0y&&s{FXmfscHK%8jhpe;OY)|~mHKZ_@ZC6&0>v`X+ zoH6HK{hbxNDsU%hIXNG;Ro*FY+nYaOW;UAT{=WH}wquDW+$ZPRY^U4CK9m(y&6YK& zpWF6SLt9X%TOCDiD`_iQr>`T_@sure#~ycN*kiWHY43+`h zPYg1#sa)oa_R43{gCf-t)dtg921wB--C2f5wI3~koXt;U6_Zc!Vk<8%_Epz(x&OYq zBTKgFiE$~A9|!q?kRJ*8p^zU7)emNeAI-)OXOkb#Mg%}2fsKfOM24b72qaPzC1N0v z1BoDLMUr%h=lK`niKYA3^09|p`2Fwcgr1ZwDS@!=ajdN zA?>?ttZ_@f?Xy~MwIGIB_3&?}y4v9UUZ2B=6FsChthQwv04b4`+2Wh}6Y1P(7A}|an$g_SPh`@M@%GVX znVw#itotgbCO$YS*}5Njyi!nbkmz{l6mAl2^w_!Be5A^SwKFx-8m%IuR-{6fd-D@f z*flRUiEOJsgNQ2k-?gg4WqM+jri8g!=;?UjrU~cnfFaBe;AH#=m?nqlkO`ItL=}-L zj`d6C?%;2K=J!~AHRsgu+!62b-zcYC*AwBpJKg2xej83MnH}LpgMvdu>gSvCsqEX6 zhz?VcF8(VuUy1Hz8Mbm(8wIl*tN}vQe+wApK5vVdo1PdI1M^0$x=sF&9ktvp_m?Jz zBx*0_E$3U_A|K?%Cefny%Q%%lTX63Bse-C01Qb_hckP!dp#nw<> z>{oNj+h3NM`g-)Bs5PmB=B{=Rp6PNAnev!hAAPs`hJF*>L%R%j z4@vzXZ#&?dJ zou4_zJ9au8V@|kl>3_sMq3b8^i7mFfC)L~JzO~Xy_vG*e?#aJB>b~uW(>>+jE$-Vt z80wz-S}*qmdN?V}3J28Bj$sqPD zvl=wdzT{^Wu<)R0rEzLfoY=X_8kDuiCt&kxa-6dH+)umpm*hTUSyELLuHsBcY;#w{ zxT3^5B<3NpuP7-1Nd-tsKvDyeB9K&pqzoi=ASnb%B}hssN@_t;43cV)l!K%mBn2U< z2uVpuYC=*JlB$rDg`_Sdg(0a7Nohq%ZAgkkQXP`=kkp4*0K^I)mH@E^h(#z$oQlg7 z601OlR4GT8Kr@b=>1f`OJpKxuzVb!s?%6V_7G$N|9sYuVEMcctd@J>d)yD?o*?%Hxi`rDLGBTz z$#?(EeR|{5U(88Xp&HG(82KpSFsHmd7IHEyNRGTL8X&y_CwQkE!jc7QBKS@SxHL;w#IVkrnd-m0>mizDS zaoqCs#KiA8pE^5b`|Q(~^K}tbUYGWvIKK@1r04gDbHK)Ag zEimUAQzwN_Nvw7s%BOskmBk;uEo7eB9-p)}KfgV9r9-?~RYIjHf_mO-4OJW@#;kz( zm6!7lw3B+Bq`Y;nb9!?R+@CTsxsFv!J@*|KpeC1(VcTr8#2c-4AgdN(CwM+~P=epy zXZ-+W;LOzE`)P7aJ;%U0{VX>>Ony>07x7NP;FH#G$Qe19Q=+y0D(~`D_2NL4?EP|1 z+0j;1u3Nh8x~MX@-xX7~`cqNm`VWgKmvwtog%%xSDja@dZqyYwxT3b+HaEIRdspx%d)Q8mXNj;W=cj;ek2wwT&( zGb*aBc_-wgTPklzmZ{g60Nlz4!|g`&ga)5~qq1ukf#kTZ-TIHvS*M;nOMRq+Yi{-HZsxM2YI0)4Un7V z``j?(rXe@3C^rxJ0gxX7`5}t(V_=$`OBIMO)(;~7)lv)oGEJZ9>-5};w2{xhNY)Z{ zhPC)UYrfW!@7=EGM>PI<+YVdCc~o*p9QZ!#dEM<)IURQ&9(d@&R_*FTOSEpQmTM_H z-ZHvB@{q>UI(bqj>!V-O_W|SDahLuR`~JDLh_PI@l3Gx3UwO1bUmmSM`-dNO%k5NN zGvx$<#~hq6h=+ztk0{2?`9!hNjO~hzCv8%+4_~PmKj@&w=MyHZRcz9FqhjDeeb$n& zy_%0x@zgt|v*L9Xdnop4SkL0GcEx_>J88sYzmLnPaQ|QGs_=mIKdbQd`y*6%psTdv zptavC4o>??ambuqiu#cKiZ|T(t-<+*wtZEFhYi}H!o#aSt->Q(uTtTWVe1q}RW&Rg zeoArlsR@c>w%)Ed_VeC~H!izgk;j8`{+njDR^gkU@1?@yM^{&2$9>II_?By0DgGl5 h>XV-Cf94-C!*-S&MTj`r5vT0u7&MOeJjo#9{{d89lL7z$ literal 0 HcmV?d00001 diff --git a/tests/ohos/tzdata b/tests/ohos/tzdata new file mode 100644 index 0000000000000000000000000000000000000000..15400ef27e5a9e10c6b74060a611d341e87d0480 GIT binary patch literal 272428 zcmeF42S8KT_s3rn0mX%TE8;*p7)RAw2X37F&-p$QKuNH_pS1e>`ye^_+NCHWHa)_@1lrO3{V@2UMLns-rDrL zD5Ap?REG7>lK0x2^pJl4gh+$>M&IR)UlkXpNHC~xY)0PGwDCtOqT`3gDh-4%{+T?# z)au77h7D5;Qbk7U4Pw%W^mXY^=${bPH^ESrnw-xQzpFwOYdAZF;=zJoR4XSvu8KHC zUpj|Sy~RN&JzNk>Mr!qy3Gw<>kqZ@{3xd=^tFMk$42T^H4?S;HY{)}k4tc(i|P(&&eQ3Z#qYESOk^6b`M)mNPmZ>R)S&CIK>PN1j@jyT7)8mC8l zz1$;%ofWDCLlwAsDCK9voc!q+t5n9T2OH9_JD%d#=8uP>uR2y8t+()yszph9)0Q_+ zs*MIqC+Tzf*Xnz#BNWlm$~Y==;pAM9sB zLmT96A38-SV&l~bdM+)#p&_*ox$&#Q6_JLnt{^nBpkUhb?WBs1i=aA?lh%SnBd)*N z>+7QkR~u{%1;MIkUVT;HSf#=0E?Ae&tFMkSbZt3(*4yb#TONE86wzUZQZERNUGv7D z(3jd1MO=jbJZk)e^tJiplMqKWk{t9OO@@)5R^OK{ZePOv!dwtV_>PiL^Y2u<(j)mMirsE3R&@D;Wxq_1sHeAUsh>Om?)Ib_GD z_ft;!_v7}tul~#@Rr?Bg;`bwKAW9i+D1Q6nq@S#fUq$K$n_Izw+gEM=`%(KgAT&Z9 zA5V@2AI%z)ySBV_PEb$=sT0E0dUL4RqCEQkYO20Ae(FK`ud$;w>1)#ypil<~8mG`VKCne=Jv@?4L+OU6L!8pH;c~@v86yL*>VH zS3Z48OM*gWpv1Y+m6!VK;*SkiM#q!BtxJM3T1~d4Dpr{wA;wR8URmO#PR1^_%JOQ3Q8Ysr$c?Rb9xwl^%Xm-&YwO zrXKc&x)7LDaC#{6U=IZuey{Z)Jpv2XC!3}~Q>zz^845~&fFe5Bo4SM7%W3Ze1*f0& zeG=ZR4Z+h3)(=bwO^A9kdZBL^>7RNtf4WgCswkjkR8sxQdwmPDAi`heE1z)*(V;5! z8)un&))6bIzQpU&6R#Se9#BBCbpayl(Z^|8eHTSU0cqAL(}&1ZO8=64`oT2J2vf!? z@|AmS7VzL5TAw{jc-rtA^=sOz11(&k91Z zN;-Wu{%rzi5TGwlhuoz=y7t6HtqAU}iYu^Wis>JkOzByni(ehCHlR#FKJ%luHM#n# zID^R-hD+%BUf1foDx&(vs=~sRbPWos?Se4!2Im zp$5%)VJx+`(sH@_v5L@w3qsBpjjvB1w&v;&Rg%H|dLr`h_{#*-^XL~;3G?WG&Do$U z&#nVyW+AYhQ5 zHf1KYpOS6b_}vtP;|wNWm`(jxF{AwY!LBsU(_9^hlVPPlApD%U#>L^vTfvY75OOE8tANQ~*b+o|?wsa)Le?S|*yDByzS{Y-Y zK(F+o{9LQkkL1DoAZilzopcJ17gDtP9m${!_ESdczX+?A)BMJwy!whrWdZ#(#foD? zH%iY{t-eR3B2<4{!T~l?{>x%?`s%Rw2n99qdXLSENq={)zBF#dBwp z|G4@&>6^YDe|O~|J^k;sv6Mdp-_Tb@hjPdFTISGKUC*NUo4&4312Gycgyv5Q=q8O% zrJv;LCk#^Qtt%Yh)+owPd!7D3MP!1(_Vey)((j?w_oh@BY(9ktCFpzubo$g98lF&o z*qh?tqt)+3qp5_@0Yhzl)MT3%G;@??LFH4|U+eT~CL&%HN>}amg8JeG#eYZ_e=N0z zR7U&j%Oi=fk1|PD-)U6juNa1mF8EC%;Uc25hkaC0`b`IpK_Vr4TT(-t9v>buMu(G? z`({bN7fNL9sXZ@~tFI?ppv>_X_sN?-KI*~B*kJ!yE^Y;WgQPUiZ|Kg~mzsao>shFc zo28`m@AURqn;u`P>@;A`tC9b+r0ikRx6G@bP+&rGf=SFClK$*m{a6)Q(D~!cOOo=` zp5#q`ERDuh0~7T1z>*5iq^}$Q`tm58rj+$2NmAhfrAOD^`YMJg;%Ur~HwpPZODfU) zkMxKx{WK{+7Kc(_as)}$K~z4cib58^Bo3FTKh>27 zKjq-yo@)Kpi6Ck9J?W=w)9q4KQ1_y`htzkKlK*&ffDQdom)JmYrynYU`p*h*U(N80g+4K1ZlM(e>B)^j)d3e%)A?_*4GKGV-J+ zH-646vUc+g=p}*M^64ie=xdq9BuRpPBz>Jd*||S;jNyjPw>Qr}=<1_CwO0mPXh|@a z2VH$6{qSg2YCsztu|6QXIn>`ca+o=F0UirSN6JGANVO35Bd z(d!))N}`wK?H{`;q6es=gT107l?B&$Nn9#jFI|7sRT--4r?+vYluHI(%v)c&D#Hy; z4wLGTos|E&`S7mFICW$~y#8;HeCC`lJv2+|LSr6%*(pdyPz9CD*X9q+V#bpx9EEFH z(3>RDl=Nrh)mIss5Rr^pK>4ZbFF1a6up9Sb1zOvJWXx@fzg=$pYBHD--mt=SNt2AT zrt{U!F9n3EX-1^)P@2}HdDH?9$u~6rBh$?v2B_igsP`M1uaU9;7Hpk}2;bd$svV`aG4acQ%uXe(SvbW1y-(&H3v| z3*ujNm)aAFE`IJG4XwCJmSyIPpW08gUgL_>zvgFp*Vzx7mt{J8 zxF?UG1`mkV+bBx*_>%qsXKr>vX~dcRRe zfaDB~7lg0!=m*BCBlS;5NqBxnn3G4pJ56z`^%`Z8v)o=SaYkXFQG0awP^BWaFU{(c za{-6s>}l#Bw>!tFXt!UOKCRqX$58)TgP#p?a^B_%)KNjVS)>qW-{m!*d%P|Mjw@-$$#j?ptuN6eDN-KdCR5hKu@f z5#coZZ=m*){$Ib*+h9?jw&E3*Aj$1c6n_NtT@>LFVTywLU2TFSch`{qpd9^(SUqN> zR$Y+Xx1!HKSf%MOd`m_^ffWCE$oNtx9C`0;Ud2`{pf0e=H2$EN1>`UZ`pBASKuMJXE zl5Db9rFU}l)vCUV$T$Pj4}w(s3+WFK_0^H;D7_;QF`ZH)+7BQ+fxa6JUE)FwPR|HZ z=dM>xV(t%gIfZv zV?`UTVbW66ze}PKe@8ms@B~APxs;wVeW-m{27M2j{NXejI;7>v-Yz)?`kusG->qir=NA zt~_*7N7B@x;UX<{FHiA{?O`X{;t{PLs)#jECZz6n=u7?()5DvGX_HM9T{McMz(eYJ zg3edS(Wl%oSehwnFV+{^&rS)06$aNX=o_V-D$u*wK9FTtSocW!KT!TuL3(_7v!9{H zmgDzZNpE*^^at?tq26=l43Ku2L+@L2^rKaV#um`;O6wzn*#7$_#DyBR1}OdjZ_*df z*NgZ4Xd`^@@rs=g|N5Khu z=O_aOCntr{PyQl(vHWzV#TvZ{*Xs94C;fp)kADPBwg>ww4K$yERN+h^#rD>p*NkZN zXsCsgD$bC;*gyIYW%HJ11Pvrg+LzKJY(RRtP~|dM7)VnZ%J#gNo-VwZBwEipV$OF; z!|G6=7%~2#!-huEwm?HySlVx*PM;>n!qtUzNYaRG`ld~YzpHAfB20f*gcM0tG~bd_ z-U1W@B4|C@V2>y5-+|Ke0PzR#R4HvvGt?TPs67mi zFl<6dV<vX}1zQ?R`qFENKTttK z_d*OBX~GJMKRZXC?azYeWwfc24qi_BqCFGXmkg{p!(SjB%KdwS7{8M2d4pGrR6is8 zMAk&qk5JLgINEP6SV~81CXbn-zIwn=b-`*nSLsNa4-ro0=z|AK+-9tNPm|zFPhSHk*)JEp72H175FBd zM(wB3a-F^lZ9qvd(85W1ezlmO(|4r?)X)uA(z&EB$;hK0qonnYSbf_erSm?abgtCt zcT~ix4PM{*WdBPx>hyhSf00V>Yz}430$MM~bG}`bLkIMy88Ur+l5}wlrRNJ>`~h@3 zS46OfQXLy!U{5bye4q43=jz7?d(vdM-YHY*l0o_7ryeQN!2F(cNfwp=O3pNlqTX7Z z)QMEk0xiW-&>>wx_PK0EuD(mGD$c+?D$-Rno|V+itFNXuG1!y#Bo!F{>b(^I@?3p4 zMeJY$U6OP?PYB%1)pt(_HDr@XH_&)c(mGe)lQ!kYavTL8(p0u5|H{?(p<60w2bsQV zDc$@f#qXX+pE~$pPuj+zC;lzmzpTyG_f?XNejjX7o?^8wo?38UEKoAZxJ`AO?rF-y{k#z$^EU;bD|;AlMt(p zQQA63#ZiMBrtjWOj{npJYTr7$=jhWt7qppyzgW*9J=cWN|CLssYb65?3M7iDC4EVXcY62tnJ?I}GOI{nzf?8B>cy`SOe+#lh!wN`RS5C19;w&tv7)5HnpG9Y1;UyD~d6s zE=V&ue_rYI5-H{9F0DRo2Z%Pb97J1-sD2rD$kC^+h|YI_ z0h>%#%82skQjWe?T%?k>Gtjm(Jy%Rt`dXg&y$YR?l2vR#=}*at-+^g)4vl# z{8pJi&nIZxTiU*4U~QH|$hvCsq~Bj17Ov(SKgGxjc$f9a+y49W?Ul*`)O1d=p4|Rx z+w-op)j;P};0M_!v_C|;KIeS97PehZko94EY)y_nW*`iHgRE~SivPS$KbqPe8X@T& z*2%{R7l7{SD`T%-y`{QTHowRFQirWU+@SJ^tGGgRTmH%Em{iYrkF;s6El}+==lmEREsPQo{dZDCDPvHFbd|P{7 zHlrVnCq2|((~Os)`5)Pw-udFE3mk8#M9bz-eG%5^;vY)S zaxj!6*<7kGy7TR-qz57xG-zbWTpqsiecSP)Ecq`gpHaSVUEUT1QT%GsZ{hnk<44(o zy;O)c_y+{G^nWY;g(IM^pna}9{T5SLd%o0(>dSP0tv=n1Lo;{!8${W%Lu3zB(CWL; zvoUCkNP%0m=sYPYDHrK|zgC|r4BbDdzq=4*s}@n9M!EWhY!H{NrSYIJQ>*W;-~l#& zv7SS=u0N&!d!2qingP|L3TGKFcSySH^m(@;O_%DO5Rq-_P63u_^*hp%x4NH!8Y$aM z<0)Z=PG8mEz~G29Wt(SFdM4=f6QULU3_ajUw$+kCjnV4U6B!gShT1II*5#zXGFLyU zFFg^Bob(*BZCrmx>-6dVN<}1fdHPoJWZN{PpPQZpLxq7fWoc9&C4IE95emua1jV7%D)rozF@C zzD|FzqCf3W&_6*b+jX1t&uR6275!-k{T088-g~***)>*VSK|KI})k6v#=>A=|f^ zd~WOFr-x-J4H@*Z{WSg+_GtC}sqE8+^@8`d^H<6aaBl0)mo{@6nw=s2gA&r8s*7J0 zA4<(xKfBWM1yun3 zqbk{9Di0D}dl*1VmWJ)sBRpQ#)yF`3eu&<>!l{#`yU_br+WZNm0gr**tE^A`UGDh? za@yl*7ZAr)&>=g<^-)(IdME}Ms*M!?@z$iT%g-J>ATeZN$&RP7evh}d2TpXP{J9g@ zOV!VT0v2*`+2I!-ZxMu5xw|NyO~>SGlRYKb*)LjhU^S#H0*9DFEp*l!Z4L`IorF`7@eULy`{Z6}xt zLc?W38P2oDb`H(#1b1hAa&!_*>7z}vBFaE+?TqPFos&6W|E&5fCx!TTW;x4aNG9a( zO=4Ki-aHBN$23p4&l&#;JU7`IoE-8qks2l6MUQKC#Wj^X(BWQ%fd z(65Qid(W0gW_fh7qZ!NeuLXhSvDnJs<9_uK?zc`RkbfLG0sjZRQ!+U>r9-wu%3SwB zmOjeHzaW%Z?glP9q!-vMqNf>uC_l(GhGm6*E{QBF1`mgOrOv?uU8H<}M#bn)X2fp% zK`NsQFFntpr1pFb%a}=uGYO>yp{BWncD0r<6Dsrd^ru%3UeMu6>&hQ^waJv;Z0w8Z zs(#>w^51V)xJL7DNV4*Z28L5L19da3sey3 z;-fNi1K8Nkjw*Bys?etNR%93b-_JGf3n({)Qk+|pugy;4EGZGLFq25Lv+sp*Wxkh* ze5F2vtA>r8{Xg49e9>|DL&~Sx6F6lx&Bad2m9GrF**FwYb^PE<)4!8#iFA#)IF;t& z^!XOwr7ZVNS*wA3_E|d1{V(o<5A3}GK6r3N2)iG;XazpJ>=OKs?0E=2IrRvfizAQ3A``M@NG1OurAlo3bW` z-7|+aNMw0$(lz+s@6{9j58|rB|6!X6@PE{8F8Fcna^NS;k3;`SwmbBnRu%imrBkGe*u2A`99*y95>9HP?}aPjZTG-y3pdm zQGb@Lil>G^o>&ELePbrL&6+9Tw)=*I+f7LTw_nr;+##+9SUzc(MxevFcU(G4mw~-C zEM41n1G{x;4|XsA68atO&A}Zt{lT8)q+rht<)H7Cc@^ya?MC=_Ix7hg=+06guAC;zVoF;2KemXyO8zs2& za%WSHH};s)n~eiw6JN)sPEDO`?V9-7+tDXSTL;G?>Qi=8dW`6WzV`brPiJ#uvnRVE z8iBn*%g_kyJ#!m;e4YauiE~ar39NVSjwSecgIGE9^Bs3%_ymg&7@Y{YCn}R=$s46u z2&EJr;L`iWLSE*(`_M0YqDvzCn^`UZSKsgpqfoo58v35j*AkgMH(9{nyIyrh!TZ~j z;7;e$5}AEI{R8YfJr(TdI04~1&rVKefB)i}L}@?M92~w7+-3L|qC9;Nva8V?QAWl= z2DBI^$`y4*dA_5$Ku4fYH*mKqy+t`(V@}ROx246v-J7%!<&*Z1Jp#sxa?~<&fsUX` zfuj7~Jej$t`9o0#1&H!db8~VQdTo$`dp(&A{-i+~_>+@W!M%^Tf_uM+0QZ@d0`7C7 zEM!P*5ZptOhlBfe*bVMGxISc95p=12abvq)2l9d75eJdCEy-&rVDtWnV8Pll~3Uwn^6ny@ngj66~9(Y z=b*sSa3A!In9d>A>!3d*=nUM4W?6uT*^Ywz?8tt&4>uOm`T5TS;r{uFX>cDIn*koV zL`>%>udm=fYVZ=ce`#(bN7l<*WK)I>+pChW?oQ{h&8)(hBgn{bD-5j_3~e zujh*CoY4Ly{3k?->HMx#fADv%Vmc>YDG&Xma?RmBY2!vwUKi6jWo$$6l&xPuPW$wT zxj@IXZ|;MqH*N!--d9ZL49R7<&uAs4bLQ!<;Xlh*Oy}&MpMYmy5YstlxLDrjEZGV7 z0I!nSF%9IPYp)G{pVeK!9Twc)46b#8tw~^i|Je(TN(a~ zlf`r{?eHad=^!zk%PTjA{&Kg@a9?rjIONLeVmecP?Ep@>C#G|C(sk&s-c}OsYx{`x zXzesHo$DQ+!+m|En9kIa3!tCcN=)a*GiKml&BSzWTDbxGn=YJ(`<5}mqFgJcb6db6 z@V0NnbZ)m6+lB2xKfry*6HT%}$4*-@ox6^VgZpkHF`avUwuS$m6Jk2|#m)xrTP&vY zfR`Ke4-Afg`=L62iPBF@=i%Ey;3M_LbROL$K>z6d`fxusDGGdSpP0@Q5f$Nn;`>%` zKiPh>C?mym{$9!o{JX1|&eNCEp?}8g5!}yi93sl=Vmi-{`2&1@tDE^79jNRLmjXIj zS+V!(FFQNysz3+LwiIKTGt|asdh{Fy+x9%*7EIeVwzF?vWP|O-^xa1Es?6Qzi013i ztfNpp0yyy^iUt)9@yySW`{Fm=vh;DbqLI?25oOyMIkAgIhjD@V*I~3BwClIn;TX)F%O`1bCsA&n=uvAq@OC#4Lj>qzmBV?mL z8bDf|tPE+r+XS-l+APQ>Km7@5GxaQF)6oYZZR0mX+J!BLwEy%BO&JvMS8!RRj1mh2 z=3m5#IGTScEl$L7q2QS}Ou_NY8>ZmI={Nq^#J3q)YFdlU&bFv#S|yZGQhI%yEdO99 z%elgHN&7(O`Z?+R`j93yf2H&q)60z8!_A(V8u~1hJeGoX;_nVmJ`P8u$((;5ha(?{ zBU}zX4o5x?N4OmP8^e*;*H`new&o)&z4G$w`|+2Z{UAAaDNDERTZ(g1LhodH>K3HsVGFASTPsnYIm=mo(eY>m%R?)~6`#X%)EM<0`7Rox zyrUz!#E7+lwqeLAv{0!k$D*5U#!i|5XPRv`<@X|6rmRO_&8aWPsa?O|?0HU$FsF)! zg=CncKTGN7Pc#=^g( z#u!{`S1EAmZ8yMWe(nJMvWtyE1Uk&d#KFJZxa;8Z;lIGYLd1S>MeoMoN}fx>l^X{` zze=NT!BtC5hJUr=$f#Ne+&j&Bai5pjWg+D$CA(bd?9^^j?hKCnEqYy0HE2ORu87A>n@@ zi#eOI<#O`1#4yg<^8E(Ju&mH9BZL;^XojOEBh7Qr)u6rZ$Xwpu+yi6OqTJ)mm!DrB zk};RBulj#_m&}KF-Lz>&bJM5oOFNzxnSAZLL@IwmFRyWCEPGpTN}^_m#{I@LeUP2~ zB$Qq0pwQZhKyGue`@aZpgfbVuQk=V)cYizXMal9#9tb69DM1zQp5a* zZ3oOm{jvBE_%FXbnVj~@lU+hsRw@1j6~D@}#v10TH?84c?ObpObM-?j!8NvA1=n0V z3|wn&6%G5>o>DS~Wt}hXgs?P^@dnpbh;v33U5|q6xydx_U*A!h$+AI%79lJf$~S-= z{O*C9xiyErqf>tk`}_YmgN4u~@Ju4huBx?(IrA}ZZf+qL{-^GvH|tWnC0-($ps8t| zGeM)hIVX2lXA$k*w4fAl`NC9N&yE!y^5xLpcD1MVI9!gsL341~_MWo?}W>FcyW~^=EFNG3y_CW;C^Jj6IgdU)%Yt2pL;u18w-CbXrvFvEH zAM8=@BG}{3HApYncC*5l1!#740>>iWcE#NfY8qzP_*Scb<_;?-N&y>#RXD?=KI=4u zWkEB&oYaQK3pFflChQGi*;G{%Cv5xqpNxW?=PR&%xKSdrLn95iS;y;O$8s-{*xjk& zez4QM3~&qMrQjCF(!edReG6{2YCgF29yPelq|xBEE0J?TyMg_{?I%VHe01pbr5Q`P z+Mk8s+*%2C>FfY@t?mJKv#khrdu9dhSmrUf51t8sf0vcuE+HE@xX{&N5;#CE*7v}YE6muvoB3xfgl>O{ z`gbTR3G#FgHv4uIiSHJHMu5r0p3Ukfl z4&YilDuHXSt_ZF(?+Ms^kcXVqgu0{7fGxUPf$Q~uY{%~Po0kMP@IDQ0Sm`#{()w4h zWp;@)QWLC9XM?RW{sLRy906{;^)R@}iJ2*^XESFdxapeVVA~Osz;;vmfbGLq$Vp9b zh?9exc_e`y11!K!ma*XG&5D6rn26W6WzD*F9IoYc@%pxUAp^JGTNV0k&iw&yn<8G{ zcANHs+b2~{<8U2jEe6Zg;`MbNy*q{7T{?@`*EMV**v)ox8oRr@jR(6wxGSexS4?VT zZ0OIVMDf4(M?bG|nN6);efeGK&jY=<5DD$8qjxga$i)nwX`5DDh)^{J(JNm@i0v)g|(7vgC)1tb6J&^K?D{>>Q zkGU1u*^~L;_~vOWb1Slo9rLr}9?2|UrH0B`W-sb!#~CmD;4Wt&8P^Cb9byTV4Rpf= zDW-5uV}GMA4Z+6F4ZtS$#la@uiWR1W*-dbX<>%zAZ<=`sTvBro`lZhNX~*uR)0TtF zY&-}qyZC^d-OXk!2bUYa3|xNbL~sSwci@UW2ZAek#egff3IJEJ>I$xEp-vI#s8+%e zTwU4)T)lBcGj^|j-vwOba)V^%n#b#dYwa)r*IsQ5t~2jXu=$`?cC1%-)IqRC_Zr}O z{co7Fdwr*u;0E3Yzzr*11Y24!16#g&p2B)orr&|BGA@9vZ^nQdZ`}rNa$-U##)e+v`w`nIfD!JL9o zMHa7btLKlvt@oNjzs)USAMsWKi@%na{H47|Pi`UnA^oC@C4wug2^>q!K z0d})pk;3loZl8hOA6&94nu@$Di>q+l5WI(QMW(7s6`9_tBFBYrMW)Y{_?;h7Ffmb5 z8=vcOMdsNCXR64~RFP4Eow)}0WYfbUt-@FQq#yFT^YSFEdPE< zJetJRvR)d_>C~6BcwPza8M89L z7ca~NYXX~rA5PDP{v)G4;KylU@PAUKb~4A4)ujnIYwvFOKQlcJ{b!4^;Q#!VG5lYA z^La9*U8uGHJ5<@)Lw*O>Np?b&GjDbjTz8ZT{uU+of$IgC!N1;RvB_!B;y$>+Z+p!J zI_wgMLf^iYFZ3OHn8UxBaew$bwhV;7W7c+XbMrdj=0_u--{SEe=(kM$3jVDwUV(q> ziA~_&W?we^+p5ce+wGbJ|Mq^FVE-?b;4bE^lJo6|C}0zR0shp-rE^8NqLH8?Xg5$f z&=Ufh(XAZx-oB{z<+l^WksLO4$If2l!`E~Ep$x%XWmeKKdxtL?TO&j-(ukZ zYn9K_*#Fmc3*f&gYZd%AfB!rDx16~O|E)tz;lFKDRrsg%nh4(hWh7y4NhT;ZR!_XPZ(J{%1HXD6D&|M^ZQ_`ld3 z0RNZE#Qb_S+Xrl6tAyFd8s#b9k{;PRIsi*XAS~e)-8+jWSJfWwi51v ztx{Tptu-gWjc4>Q7wFKH-)+{2*JE%AQJxjG95#`s$&f%Xe!z-KcHT+nH-?uWHE$6cOwq;np zNi6g7lO@wQT;4MM_sg)9XFg;`{O}?&J80bTo|oa}XkR{-;SakE=Pk2bhX1>EIHwGA zNk-{?@5*o+Iz^rp&aov{q<(CN-ve91xb9qh$WvldK3Wd%ll6OeT{4%{MhzRvS^ixs_#ImiFo3lt#P;{!gjh`$ z`bRVPUmwa<6NiA7hj)t2@3ZhU6y_99u(jrroLS#cPM%T$dd}V3r?K9D*&K6&tS!Ua zhQEIe@@vx(tHVB);rGCV_%Ej;-fcsiuN~f2^ZhHsubaWYeJsQ8V;TN?(-HqoyK7V| zuNl1T{VTDrnZc_ze=M=@QHlL;&EQ|>iWY>-PX6Co5LA-$H@~~y`@+0(?Z?9WFBayP zKPPjcvwYnQ_NbA@?ys+0$n_Q0DKC;)-{VAA6lTwRgXGL!YaGGe$AWmb&0GGV3>PD1 z(H7=lG^Muv(EC@Ic?Vyf35pdzeJsrXVqw<1Jooo(37tKB@E8hlUfY5T^*fm+78`0> z>^T4amFm|o-#q3E_}5jOfWJk63AmnH5V*c$8|XK1ZUb&u zDH{Hk#@oP_kLSZbca9k5+=dHCBm zkAS{IkEZZ%R(3V~9b1@z9kY(Yzj@{R;O3Y9f`5z0zkyqBKMZblYCX91yp`ZK+r;y0 zyK=pqKu5cQN$_t!@dt2+Ud!d|E>{l&I}f}Ac4@W@;a$6=gWW282X?n#4R(JJ1NNwX zBu$`0XKy`A0qg9ovy;J{wu|}cvoIU%JFk`<1rz+nUIKSc%m(|%yn=m3BjI=TM|)8+ zxmlq1JbO`iW1MaiVV+$myo<`Qy(9Kg;qmmetC=5E->j)4e<-3Gourf%y4`xJu?@8vwvQHBZlS5`_USf>-%*NVY$KeIAm(6 z=AwK#CX?L{-W&)%J!x$wUm^Nk=|q0F4iHRP>qq62_(PlZ$G~k}ER|?Cur}?pQV+>`~tt?2$FejDiVX#p;2*PRD`0uZp+NbxQ39_BmW3 zll6Rmuwo(jEqes+JftMpf5PwJE(*&;cIXCKW*$y^7Jh{y*B)rROg@`$vk4-jleEU7F0>6_+OS=K6_=YzJ*jkyPZ}=KOon zuJq#HfTrJe7F_vO7;y?A)EDufnLc{X#DGTtC;lALb zvwi`mEBl9V29*)+YapjiNM~7gx&Stdj|P_;bQxSe&>vhOI6H*Ygo<|U#V2Ux-Xu)F zD^-K@i8@YQS9)Y$&Q5&3mw@twHX%2o=gJr1Ui?~=xIFz*d_3pE|MJ%M0=9zZw#>9C ze8Rug?$WJgn03O>VovFECWWuW+_`N~4wK!xoMxUkvKDxL)94`P1%0x?Kb7$Z|J+{u z>9L>hIDi*gz6xQzg-0ra7n^9niX^|Q4!5TAOz>KN@u$w#J{}HUZ|4dB^%rLZv0kc~H8^$KXW)&QrNF<= z{S>_Ej8zEhZ64VHyk%oa@YdkE;B7zL2B&q9fwvDi3f|GE&S?&}GiVifS8)k=cgrc@ z-Pf;x_tsshVZFV3cZ2s!CV}@aTnIjJDGq!vX;%>I9g=T`998D+rdj-N$5=Z9p1FRb1S&ImdVzBqXfSmS&*h~vE!n+U#K z_Zav}*S_GZk~QEz9NfTvT$%>HUPBSW;jV8V48HN)75wMCp5UA38i4=$I^i_y-P&XV zzTLkE_|D8f!FRn|fip)NX;|-`bq(Fby6y)*{PQCCalNJB$H!j;akwnm zx8SVR8Q`Z^)Zk~6)4atAHO_jkr^%vA-;b&EC?3}1M=1$9%Zjo}OTcwJ+ z)B4~hJnGWL%hR8X#8po%+ar^&cIrz%v}zmgxyYG+&G^EO<=K{-!Dnw3hdf{HZW_Cv z-xv=63(xDpJ!94_@Wl&@z?wj3@WUTULtl4GNm?xYpOk5m!trEvX${uiQu54H1N~=< zjp6_NRz>)~_~x4wE`7E3&z7^SJ>(B?on+TEX7gs^U6^%84VE)olspB0-NZra6gzfr zb@84Y^3Pf+g%(9>jLf<4<=O#k9q~;_d^1PR5W0wLqI|`x&uqs`&aEGUy2Wtntz~ik zEE~s zzSiJZsja}-i)iZ$A3xMEV<8#)iiIE@{1Pl1SUr)sn4%hG70vMZzer?hEPoC*vC9CP ztgZttVU`9iv3wQ6n`X`jm(|W!tI4r4oyg0bl zc5zrzd(};Foq6K0#C%XKgs(eF9F|yg%Ldo$Ps@9B)OWfBZs08rOBz<%3AVIOg1_af zi}+w=Dh^AmGPZ-QZ;Hc`##Kak1SvvsjQ{UQH>YDZHY1^>iX;jYF;CdT=GtB%vm z6^*JA>`3k*sdCZOhgi{g>I3#=RrH)SZS3rX*Q*&@lx$&QLi!)Aa-UGz^O$bK9J$K< zubXc5f@-JnMXPstgdNKUErm45-%f%p>(-JpTW-0R!qQ5}mP1aR0e|b8HPVSg?JqPoS?`qcR=>f5(=6;jdexGPeOYKRO8d`b^E% z6I;W-&HmD90v&DDHNow6%|-b3ey_kCCYJ)sn`*$$V~xQsRbJXrFhRRU<@)3z*xgr= z#_sN#=g{{kH&xE;v0(xHJ+*68)4u|HYuBg}=Rx1+*Z4Hn_YL_H>^HMJxU-k|Kob8i zKp1L$5Ub`NxI0S^4LqaAbidey+)_csCuNuPs*7Mp^5UcLbCHSQMp zlZ@yzfsWo0N9+nU#r2O83-h6w0v^z~Wn-|@E&<%!;ZO{z3oT4UOSff>8}M)WdK3MqSR(7SS-%7RZC8qx zZo5t2f!j|ME!_^YV!(2>Xz4nS{x*igxpWpSUDq%**v)oyBD=f0bpdxQ+YjtfzZuvg zOSE*oid6!8ofa)!?<-Hhol-?h_gziE2Ohp4pMPkCsRJ{% zs?elU(~ieIvNctNYmHnOcw~o}Zq?Z8axNHCYBy=i^6?M1JjqY+_+&CNu1AB#G1$aM zd-3Fa4%^-M9MV$RO5B?!ltJpn;T=tW6#bYq-}3FGhmDgs)%?&0UcNn&$w{PSQ4&w4 zYoFO6KJ%bxpV^TwS?|lL@=Cgp#`Ge4B0IZ}>{T+$IlnYXWI6ZOr!g#($CLrjn|TgA z|6n}jAiabm%W?wh6rG)f)IOsqiCsNyaqr(g!2K z%eD>%FF&CGub9&lymE~zIAuf|@T%FtiTu5*!-Ha2t{LtOUhB~sysmdW@OsOd;0$+8aS=jDe(4`w$R@( zdIET7=h5)r6&4NNZQBpL$M@?*j%ROKPw+mQNbtTaCBh#l?FT+^+5>!W+$(VIBtr>= zJCX1};COzM{{=qT?Iie=`C;(yP8-0dg_Ynlm1cm?XeNQrzFL>a;m@VTfzM}52VYp6 z6vOTrTZe)#j*kUv=6nLaqzVLI9?=eb#j82^YM(mbKYFyyHR$-EG=yaAU9fcUU9fCmDYS&e6eSbc!N~s(*jRoWY+{!VHu=^PT*7QM zxWw`WtWM9Wn+h(enF9Y(XHqiRy>wavxXi}M;IfM+vok%bE*@NNd_1`P(C*+0s_x*5 zJzIh+d9?&rZdDyz#i}~Eszr+!{!p#>3vhL5HE{L&RTJ5v#--=rn#Z1jYi+*(uD$9! zxX!$7VDmv`S)HCyHxF#l?H;&Z|1FvPp}x}za0BnT;D(jffi0~^fh}JhOJoNt(@?Ng z#u~8oO?PnPt<%6wPV|joJ)1d$!A;k=fo(_h1lvt%2(}L&#A>uhpg6dhM-bSt>rJqe zWoz(Tn<#rU&~I~YStfsY*CvYlgAKx4nUuL}z7o18>8*kkW^ew7{2p_7YbkL!b_5;8S8!cyG<~8P_vt!6aG&vWz`@h(z#*zcuwsNStI=+gzTnV4 zwgQ!8nu6gCiuyO3=$1#^=|kSg912bAD-^#B%N?lSG!uV`_ru z&HNLrow_|*>b!8v_ccfwe??L%Ki|Zg3DdE zt{I(`=tW4~g70v22Njz=OJc56?=Q&870yFeksTK0gRPKNudIZub|M+F`p!v^H8yWC z(B~8Cs8bC+zrwu>EfIgaVLVfal)a!DrFEG`L>>vq)Ecr@P<=-lDeS#}3E zcenv=VbT)ZvZj$41*2`_)xoWvUkA6|^8(!F+YuGnnH``>eyIU04{lN)yQWHFEzD9VD6YIcUr6a*!Yo>#}Z~K8e z&0dpC>VnUy{s`|od>Ytqy$`r^@4;aI@0)_VwCxG*I>-zhP_qpsKwkz~>BM073&ifeHJS~!x8%w^Cv^)dzjiay!SU6>xs_hLCFx|h zd#L=U%x}^eW@rEIbOL-@I0QaZX&v~CW(D}{t7C}gTv`(Ne8w8^g~iK~IDE#|Y2b_F zzXNOL3%mCjXxjl*9AE?v74}%wgA9bD& ze%#?J@Dtn7U~Uvy|7qEN;Ah20gP&!6Y0mD?Z}bDdIIRS~+~)~?mFfY`USy5JEc{@F z!YmnA3M?H`5-c0oD2c8k?IN*EW@#kJOrpa6w|0^0dMtXR!9-6k$$2;1&}S;i^iJub za*HVz?KT^KD!2YQ+iZ%ms4Do(=!I3nw;i?3SH4Q(EM73$m`;h83N+xKCjOGhtR2d{?r=iIfa}a# z1vVcfAbj0XQ@|G8E`#g!U#Su3sPD83+`xMhxM8JVz?Rl=V9QrKV_471v>Vte<7cq- z%@*LsTgQT%oCpjdHCjYbft#*r4z?Z932Zl|D)?>pdo*|03jG!)r86m7p=FJ;8pvyR z!L6Qe2DjdG4BY129B|u}ckq4fHYI}FPfQ1QnAI07SFZ*;j~*E!(BaZ~D%dql0d}(; z1a^0G1-n03L^mArFLu4VKU!LDAXYqamQ>uyz+Ld(H12}wU0mhJ*=FZXU9dYc{+U${9{=m+06v6Ci=LEVBYMG#7?*Jbb$(9j$|P`y@i#21}|#1AH3+*4uReESs5q0U4h;y^YfG>HsGoyvDCNg z{^K^_oo1XrA9vjcCvraMcHL{c@ONnmWM@xHexG~)x|9Lj^iZqFs~Y1_^HR(9Lyb#) z**lHhH{SF2XZdSJlAPt)mIuIRZ&ijoU+rZ&yPw~f0RIcm?cts=>lygsh4o-fU}x~d zA8J7Vkl-L@{p6jr!;CgL8 z_s2QVUA-Zk?%YLxZ0V!UjyEM15(p`NpxN5H+f4W zv((O27dQV4T*B-K{7r?!VAISLa7oQdaH%tg%_taI_LIP6Hm(GhUA)4a-OXl90+$<~ z1TH@`4*YR0{Nr3Wmyf?Q7f$WpyX@0n>k_nP!`n)l8=UZzTjKX^ZqSmlRoAc3!>O>H z_<#8!AY6_L`+ZB@Q*vMXeacq%z0Em%-ruKu%Ys3BcW?0Z|2H1%)R~g38@VOkTjVJA z#b1T6ocrqq*i{F|T?XgQ`rL;7rJ2(K?w3?8{rTLBh1?BcY2<$#Y%D(pHnCd^E^fXC zT*B;ou&FQ&Y??U)Tv9U_Ta5 zUX=!}Gj9RdJmJ2E)N}30Zoh%+^`Do{?)9D4gBy5%32s>F2e75J5^VWuT?nbsERP4+ zDq}j>`lc1Q@z$Z>CMP=jv!2bIPryyrGy>a>Xa}~NVhXko@2Md*x~u0FxS2;Auw&OF zU?72M`r5V-9x z!I14XwT5gz#u>81teTMWe)S-oN4GNL_*^;~fnCFDfZZBg2fMqz1a~yr5B8{c5$tj2 z8l;zOyC{$BgY>?#0J77HC6GRcwk2`=zTZs%`z@OX?i?Kr_Mh-2xJ!?(A-hH@Ap=@O zLI(PHKz6I*2id)el_;OsK?YUoXvXme{azm2^HC#kuk{bXpPV!W_f9Sc`RUqQkbOoz zfDE2`1TrM}B&6c=n@JpB-wqqVp`RWCht-(@R<>J_^tM*c-_(=QZl0rwVolcW=HVVv zdv6y%21WcWTZi@z^kfnH_FR9R==q^G_IRc!e=Mp;iT*%&VoI;N+)4In*S0>(IjJMC zy9Pf3?{2jQyhl+kmG$;kn+Dz| ze*oSuUEPS?515|>AGkafd`Q>;&h4D9?PI+Yg9^5%`)%)zcjB_44KRvAogn8TiJ%2jH8==fO9Rodo}NZ7cZJstw@V zdy>I-wr%y~@OM{k31XQ!HyM0y;#~0kkzarxs7HYx28V$kbq)nT?$8nZ#MT|0)u``2 zjwkEv0PxeYO=09bE8a<*bjtG9u>bQLHsBYhn}A>LD+_*=S_YiG@LoEV5kZ)qsbL`* zdjc#Sd>kx`$@FLU|9?i)sTX|S^uzLzEG@dFg6s7ke4e?!(@by!@9Kh4W_^lk>bj+UMFrt#gbG=i$hvzUG~gXu?h zo*ymW`Oz|-&gSD?V1^7n2hLf;$Lj`PAwhg7Hr)VAHMX`Lw;fZ;u6@Pd@^P8EQw-G!Y+)+YL){BcH0LIeEtC3>!%aET>g&!ijnQL#h>e6s`SmJasi{LPE^bI zp#{7+&ZDW13*>^KgNJ7xnBD7i64#t~5J3B!>T}up=hvO`T?Beo1I)YiKe<%p zG&ZjM0%>kM@LJAe4c9f|WUi0kDnN^S)a`7{d8}cVe{5+_n`H6n)V0cDsqBX{pUFAN zrX!6uv$Q%Xr>9`ZB=q|WLpjOR7gCZBy75Zn$#n5i9pXx4Ge;*HH#IBb$44%tH0ip$ z#CuDVxzNwNnvuqG)~O2U0_UXWpJSZ=V--33FR)t$|DT3+2mf574)~XVWQ70aMb{M8 zThw+7{1=^f1TU#M7rf+fYv?b1F$%nF+a&PvJEy@bCcFZ#-0Tic8QBfIYQ=2u>P|5! z9RHf{Mu6AW-vs}4(IMdVmD1q9!OsPpTD%H)WBotVINZkTPr;jt9RzPWRvF>92%kcK z%a8W(-+F&7cw5q0_@|w?4*l(YJit4?{SN*+{pWypMa6)3+a-ec#I^V--t2+FzdDKCC*UMK$d^ap5;2Zaz zfo~dL0pC1!9{ks}-QZiRwu0|X{3V3ry+3liKg$Q|ao~r+1Hg|u_Xj`j;0u0Y>jTbe zq(b{Ge z^9X!m!ENwwHGT&_2{A(WC)uCNIsDUhZs4a^0^tAbTpRH7g=66VV)tV3%drvQSE&Lx zd*PKdK6hdIW!NW@vAe<2!Mnh+n9FkVrD>!~{w$50DyFeCmhS|cRQ8lJ7dQVIT>Qyr zY0Rd=Sg>hke>rnWjS5`q%$PLh(rKN*Wj02D%dVaeHv4>*oc+snxtY$ge7_B8EGyU_ z0$1!P?!l_`$rs?t4aM)P(k2XC)%a8BSF6zxT>&2y&US}WUw zYY!9Gkm^h{2b=d2*O2O}rC=OJ-@5q`t_QD8#LVmZdmDSuw|9YX{07tzFO$d zveA>{aJR}B54Qe&Ex7U40pKPZL|fNpjxV_B^da!K9bpT$8{7+QAKnSlp+{TDW*#<> zj`E)V{5?*!PC{zindUDyfOFU0#OcA@wYPN$pS$*U9QwIyZ{nJ&Ztbn#C(zGbTbls( zs9y!)JkBqM^pZUlJn@c3 zoO(yDv}b!CH!-VW(&o3qJ;@5s`(O*F+^5j+uZh{)H|5_IvoE5@Ka{0wsxxx`jo(A! zxjTAke~%|SdwLe<)Q<)Jz79`{<$dT0-w9cix0N-d+%cgS9^&z~PoO){!y=KJ$`AF4 zX*cdy&gArKpNRGi+}XXTdnzG z=Z>NAndU^v#f;y1D?U{hJi1$3Nwkk_($3rbvvW3|(wQ66i>7Jc{-K2W9-8-|JS6Y+ zB60Ir7K+4w`j9;BL-9U71vLxc?6x{ANVq>Cv(W z3TpxK?&r>1_y5^Pwpmkl{OztlU%h|*;g)(IUTIY|hT9q4!z;@>qTXpAURh|pTy+-u zx`$Uz+6>k{yt1&ST{>8Urd}5FDul~Jv5=Q27e@Z#4l4ckKdy#eiRDufUVHyzZuxRW ze0+Z#x{3nIm$s82DbaNor3))8?^}1#l5*{xbr)B|+iAEs{87Zk;=g^S>4*9Z)7;{j z+x*e>qjj+;y~UIpK|PHhH*mBodWB74g;sA1$B)te$8OhbKxg{STDbSMs22J--9nAr z+@hhcbe`ZMfUz)xJl`j1aK9?!&9~vWxlp3NHM?bWp zY@}KRU)gF{XXg7#^0)`y%;a$^Y0d`FT_7bUpW>|6}hw;G#I*KEBe63Kpc} z2_hYiDo8t;NE1Xvu;8g!V6p7*cY z%U*}Y_^mN-iy_qQQ9j-HeJfk<_cHyRR<=I=t6>T%`w8Bkh)NCrx2K@uCRb3St~tA< zTI|(;Zl(Y3SI0>Qk9OHd`Y9`(f7;6AC#nkS27tf*;+Ml-Fdgbf_`2?3RiE~Z(#FyW3WZ{n*wsL z@BRvG8GZ=dK<^^h%60|V>UBvbg|pH61#DAz0c?9a1#GutC)hr3oRkG)t3l?09oKya zb{aAQESwwzcJ3k-QaG3IUBIrP!@+LNYlGdblEEG>&%h1U8-qPfY6&=Yc;2uEdzCx| zH#(pN_AWRL_Q`CJN#T6AZU+0MX@LD_&jyQn-UbJxZj!QKY&FPXa8UA0aIn)Va7gfv z;E>0cge>WN%21jbZ$3?3Q{&72I47y9m>KfZ7>P+xX6 zd41!Ljz};3{yF>lZ2QDIHBw0b_?}*;G4h?aP!s1>*dAT)_h0P1 zdp!()_3+2vSuoL4SZV(8K@r`DuW9~K-Vpl85LbDR!JF}|H@%qe(wnIgZ})Ki{%1C^ zVix6i|E7r*E!hwA#W~xj23RY1%QKMBlU})7-XWy;uVLsFDgAS)T%Hxk)BVR?-c9ms zJfqL&(lTq-q4~yre3BHasL({IuiJd1rklL^_HS4xMr*|65$-nHIGL&kS!UC&Lht9F zWR+891HpuT2=>mZkw@*R8p+luRmJEnYsdw%+g$M*2R_pi@D8)*V0GEs9Svn=oA%P}*|dV*6`c)qd%lhV$-yj!^3Lld6(fA* z^u{x=$POKOs zSg}7v1X!^@g#%czKSj7M{XfP26!u`n{uI^0iv1~yz>57TPJk8rQ`}FZ>nZl9$OS9* zr&tSC>`yTTtk|ETA6T(JMfoNN6}&&i-`;c3f|3~b#XSd=n-K}oUD;D0qf}P8QAD>Cx)1-Q zjUwdP&F9(>@3j5@M|%qXb*&4U-bF18)Y+#k>pF-kX8-FJXoZr2LQ2BUrcqMT3b05^ zRyqn(sgV9VY|>ISQr;ZtHrgI-%IywKZqm0^3+f{#)m-QoLt3NR3mNoePjD^gbKqL* zn}KcGPZW^9?Jj;jt6k6q_`4i@0RO;*Y;cf&H*j#=cM=wy*(^yE)0iZ-Ab+E*cMuKP z)dtj%Wy8O3Y=ZF1uBhe`#a=YBLK?X&lQ9Xg*7%Jcn?cMGw$^fr2g70c847B!iUbgN(!DF3>g zwmiabcB!wumwwEN3vz<#=GTh)4mZ8FTODRHj<&qmk2}lWSvp`{Ic&dqc9*f=eB;I} z3BbNC&3#}W+$!kt7qE!QAOA%g&xsi`V}fi_DmyH?Z!aQs?$VEh4d-g4kh+E@kZ^9X zx54gKjldo*hrtciSpsK=r}0gk?|J>b2`o=lB;@bCaX0*ZR+oZ(xBde5n@|Y$pPd30 z^~?qbq>fKvr?KJO`QV`Bp5S1oRB%WzKNYvJPG@jveOLI0z8JyL5vHOC4m-oo>{=_!;$eYFYl{_MNRv-ZDZ)imAAhU&Ju zq@KplN!XZuRj^mdS#YDLy!l-3jmN=0nObS|!FTJgV801>!Tz&{f<--#f&)@#OUNNG zIujg}JQy78GzlCM+z#AWrw=%^egHW1#YA0p8vBtgz+q>;2Zvu(1xIA@Ju8t%%qbWb zHKQT?o35l(%W=^I48bwuu7jJkcTb}au|3$@d3H1p&l3cv|^ z84_|xkZlJiKK%*YDtj)tb$$?W7(HeV=pXeZr?wwBW0TDi|r+(bGN)qA(f7* z4xTrw0Q{+uxA#{cywnNDdF5-uUT4|S&18l>E~b#G3v+oLZzUnt)LjeKT)BXd%?z3X z)|O3{5LZ2$SwvbbyRU?_`sPVsou!iqnNj^d;2LAus0ll24vYua>J|^Ko#+YH3-bim z@iGGI+Zcfj%p0cA2Sbfg@IPf~u1(=O2}Q{Eb_n8Yw__^UKCc7f>yX|b?6@u%>@*}1 zES%f`{FfIY`(+#*-UTZVpTD{Yd4K6Kt)=?^|FPvB7bxL4El)f$(d88H|vZxu z*O~}P6U@p4(0#Vx#MhU=trk@Uw=UcRZWG*C`kBd(<$K4fsneelE2&R4pywfVF}+Wa zP<8mjNsVj@Bbye$rZXB+e)j%EM$BErEXJ=rfstBb^2)}SR-N}1W{T;F+dthchV)>I zhcf8x3*ci)`@zRH@m3qp&C~-I^c#`O{_lUk9ra(!FVSzMig)V4`qDpUhqWptk`L?I ztOCGFR`!m)lHax|Mg3i5W~3swj->2VUbs)ZsBcXVXRYr#W|Io~Ud6*I?=xz#A8gTm zGW_eiXMiok`-0^r;cQ!i%T2=BXnBFwTMd~idxhhR_R9oZZ^Jg?`0y-K9uM*BB_y$gneeO8y7gi8kd zO;{hpPUHM%HwKG(P6G#|b`&vZ)-D|Y4oVII2RpR_hXh-JLmrRIrS6?S_{Ykx1bRkT z{}0PZ^K+?qW!=RnUa13!3=NlW(&Fmp$~I|n!6q#(j&kd471T9E4H|h?nPhekQ+*^4 zHzKQh-|+BOAJ1IM3b(H3dp@_D7=gD>Q5BKSm&oWk_jm5Y^&YI=i|ZH7 zT>}2M8Tzf<>~4mPr9-x@c&A+G?{0SYAK$lS#^?{G{_$C@is~QCasOz}{KvzwxtV)d z#gYniJ`*pWn~4s6UA&{9r*^!27Zp~>2e7vN*WE~k+V#rpg|XoBcK!ccfB%20p8Dtd zrOL|YJ)vjzGXokGRI#9*P)+v2QqXa3*IcQBz}rtw2k)3W9lUe>KydcZf#6+JT7!4DYYpBrcz`Ee zZ?C^EcwgIA;Qe*$f)5zPf^#%{gcRp{x5yzd06_BX3{3#Q?{o=V9#2WkO;yle(O>v%P!S@1kuVz#eTy5xRDRK21L%}-jx`B1io&wkKj{w&g z>ySy|YB|pb*V_{Ze{-t=0&=&QcNPBib*I3eZ7>G5yx{|Xt9m*JZf40FG{B1J% zc-zYI;BS{^0{?)>XW+obPT3qgf|@MIB@M1MC!3UZJ%K(R0uI&a0{_rFF}TUiFfiM& z3ml$f2WA^~i8yvdW>f>S4ZFZiM?U~Zx3K`n^r{*|;hOoVf@5RugPR-Q0LKY)!Eq%A zMOBnOFc87-^F>Q0)Y%{ZuE7gA{E5&1#Z2SgtRkRp6`CjUW=j%RVR=gdpIjI@vrk&Ay}()x6_k*v4M{IudZ7Oe>nv8V z$f~jz>zXPn>wSN~UDEWd$tk3Bx7-nvN=G~9kj|U6MnpP)idqrrf)-1`3rDpDFR~th z@QXY9gO^loDx+{2jq8Ck?h3)nOb(`y`?CBfgkN5|9K2$K6L{tM@!(ZcYk*g8>j};r z;(+tl%xMN*+a(jt@k*N57JH?+|MZ?vio-sJrpoTXL--fUT`i2ncP>nFflRi1*k z9=k6i_ia~CgSW5F1@G9q8N74CT5$Hl+2CD0r+{~-4hHXu?g!qR+z!0YDFM7cYETMY z_kd0#aE?O~__r}d(6`N#P`JFlS8@JH(FKIhZ?PYIO0W}r+C2k&hMNaItM?Q5oa-bp zoqz82k`&VOwfcb17mfvASUMbBxZ@k}#j(j?S$Y)sQn$w7%R`*NSHi5pSCeXhui0pU zuZKDm(e-cCY#<}OX{7_cRp$}-w%SAR?Pn*!cdqAw@1EKSzPEn^`2Nm3F`fTl^@$YH zqPZKu4=1b#KN>n6{J7^d@RN1}!B3+HfS>ud20wRd1%6@OUq;t?spA7K)<^^wzep$| z_mZ35;EG;$TxmXLaS_W?SQG7g8T9p@6jB?l7GRsgMPS?84Z(IhMuYA1;>0W%>j3)? zPbU6DgQlOGas1~hw_kErS=ojuRQ@W~jNvmYf0+xm>7(uWsOd%qX~;m$*ImuHnmr@( zLMutRD2)<#`eHLAdU>HWiUZG^bwo`5^E+JzFBowPnZUyCtH6ugm%x8<^Fu`xZi(Jl zaE9v&aK`IJ2*0e>FW_Z`qruA`r+`=N=muVy7YkmM9sypx&JFyR6{u=@CK!J4`+z?v&>#ZWk{qH|zv*;)8kJ^Nfl?$xrlf~#*n3)WeB2H|yQZUxsE zy9Hcx;IH6X-R6L6Ck_Scg$)7M@oEp&w-JL4%m-(4>@d^}1RE)}0~=MEu@umK`TNXR zzH7tjJ^B-NEH{2fHOl1s^xi3k2D!f%(;zo{XZrY8!`uEwfv|_)Gd>DM%vQEjAIPfG6RH%Gw#LWw%u3uotoFJ2f2mc?0upG<#<@K04^z|XSd;s3n4ZW>+Z zMKd$-ivyeBU#ztk;fphF!@uOt3;37*+%t_HFVlk(0jb%5gW$SSD=D$Sbr-nau+{=% zbL}l)iv%SpvBl*Mx}=s3uS%gOH-j5K%L035^#^-h%omWq*R3iz-#h0a{C#?IX@9VT zt*q?Q9C~IR7FA)-%oQ1jRdZ}?NWcYmDpfP-9;YvEgnK!6tGa|( zI?4n*Z`LjFZ)2;16SxGckj{abd-FvdNs zGWs=8ssAgmaxWtZIjFQZ#64AwDFv&EO2F#ELa_RHcd({zHdu4z8l10HG#{)jn+N}@ zXLoXRRLdR#uD*F5SZApe;dN(@0M{5h99(l?CvfGa2LHZI4d{mpbr^)-FEnone$TLK zN#L+`W5MC~MBs=yi_+*r%=B4?AGS;4?Qvx0#(NuYO* zpXSW6eQcA!ih?Ls6wnH%ue+)#k=+q>_A;TQoK%@5{KvC||E(p8f4HT`m)-AG3g5Bp zqsG$oJdMw#XFtQ#Rwu)Q!|tS>W?)8qHgj;eJ?jKTPEk+mhfC*fM+- zxPjgtu$65Z*y{B~e6Z2#4Yn!dE48+_PU6e_!E zvKgyZl$FhvbfwdnO_f10q_tg(@oVc@+=14qbsnm(e1zA>JD>(vRzq3K4QjM!BGh=> zc3rxT$%<7_(>ZgYX5-SJbq5WD3VQU0)@#!dY91K}weSgn*0=Y7TAElv8&oxbTB!!5 z(ep*SysYC77zku2=yajcbat`X4`Ww`#&o-zqc_q}j?O9#Au1oLr zP*>lrP`B7=PS;VBjn4DD&>rkn(huBddl1;WAOY;NxE<7YYa^)N zr~s({Y!j%cvjsFD)k~MI7uZ-89F)vAe}XM|$^vM_s%6l~!#mUH`cc1(12Q1qI{0q_cWiqYoLqM%xRdXyw7(=pf0)jd*=mGi5=z}{;wT#3)KQ~odMlQb zv8Feh9_ozcWbTeaCkJP?v8&LLJ_;+SP43I?iF_{0mn^@foB8|Z2AbgJdbpfp-~V4| zq2{eN8^u3r&}cj~O*i^Uh@;zd1IJ7gfSdV5fMfe9ft#B+fa6+R1;>@@faBfwgX6E+ z>vHT!FuOv+CG4vXPJF!&+-hMFxOL$YaGTT<;I=!)f|HW@Or~A>H(+s8ZW?`PKYFx; zw1dN1Fj=UfqvNQDV)l?TOEob6_NDd*l$eFAbk>{lsr9p%0nM6_RMDggsN7QASF^*U z#J;4w7qQ#u2Iv9$`APemWchDH3PpVbjNEO`*|Vmq zCzV`mDF!p?r}|44=?>e7y`(2P*so`Pobe9lESmmPR(7}vrT)V{R01E4q<6NYgO$Uq zOs)=#7+AXD>dG(MsMuv1+AL-iePmBx+Us>p{~~(Jj!n3>g1&0*FeC8W9pZckBU>*? z`Q9`P620FSh|Kz}UIJ{BT0uFW!<+B>#tLKI7>f6t?|PLoch1nbaW1J*l?Ni|Lw>!F z@ulNnljFHDlo|9|@D{_(XD0Oruj%hAB3>K7<&v&TNP+))bKWS@2Cs$i->BRL{+o*nw63qD3qi8xpPtR@dGJR=@ZFtns=QSaaubu-1jAVC{_iLUtNgb^B>> zwK2Ki>c4IV>vUZU)*Y3f&4O_?noI%L?7RtF%eo)9cH@~~y&97;DO{ZfKZ5H#>;pDX zYYR3w9uGFW?hiIv;|VrC)<(p!!(^fn*mSiY*esf`e=Q z0uFIrD`LU8kjE+D&>B;~p%0XVVyvW{qcs^(j`| zswgR|R_vczS=shf%2Ky)rGlE8uvA=A|0^rB**nW>by11>c{z4g9$0LJ5U`;@knOu$kKr;fh~$fhy*{gn<=vU+ln&xi8h|!Q~Wl zUmk!JLt)wya!|~Dxd$$v`=TK@1}>lbqQ$KNE9SmT1}o;i^aU&CzD(liP|SVl16Is^ zi3itC423Fe@!EhDws=D%pNv&f>_YpC4P_kJ1JyI?C+VT<=yu)>`CP_V+B`)mn0D9pJJ z2ET94T|N}{cde{pw%_kklKQyWUTWIHY9IAQvZ*a(u=nduYhis2inhy*ao^ERWi!mm zQ`!9#Vl`2DD$4~cQrTLtB9%=6S6aL-Ph}xRbpQTmEMEVqaVauKulS?;^ybN)sv3K-Ip}3&qh3u$UuAmM>NMi=NxvI9MWdp#QP@OA?wdlK!CAVtV7bMV zqN?Dn)rBb(PHr*feogT9&Dvse-`!gS=kIBM2jTZNyA9swIT`-@oezT#)LRYz9HUz} z|De`k_#Z6(4SeYKD)6T!A?I}*r%V6uWb>XJX?}|<;8TM0;M4B=z-PD};In#5z-MJr zuwoP0;o$Ryi_+*i7nY_=NDFt2245U20n5_6fiHFI0KPn=d=uGV@YSU9O=KH@uZM=? z|J|q=#L@L`T9t1iYYDz{y$JqyPvwE{?LVRWi6`l^(^c8*v|q1M{_=h=^B7zI%CfcP*X7mSj^O{X zf>`;@^s4u3u77tyT$fS>dtoZqv}L1^$zZ}IpJhwn|GavmY`V^iW=+5^ z4qSqNvDSTrFJ4+3{v~(K;a~dm%xo5Sw)$pS45`_G$JwNHrOk3l1+I6pN$U+834e3# ztKcT$q2Sn|D%qd?IcaCG>=er0-nB@>sZdMf>+EH2#=gK?zt9g&=nnmt2N&oUYa;F? z`us1$I-RrVhf$0&pabm952M%;uiWIGD_!>U2PM%r$@GuRf*j6*Q~jpA>mFV5|CFx# zyz>)~R8H#W3+aKW?82|?!l$SIf4vJoQ9nmW_aSk@(QMLI&zFH)_sj&hS=uF&+}rjU z1Wp>u4`H>7O9G3#@k3bc-9+FHVf+wQN7J?u*$~8Krv~t zMg(8@FWDm^E?e?2m+mwd^kWW5mCtPJmT+js>d=*Mrri z+iIFDZ|ABx7=YpE;1_wPq4i1TGo=NT@vU^g} z&>ExQAG&F(fVfHVeE5e=?+*??mkN%MOqP;=6$J0Y(4NJ&8{y9&-!m7DOvx1_t;-weQd?P9*vvoQo5Kei8P?^tJs*hem1e5#Y-t^ za4MW7Skxo&cE^#YmDFN$|qMUh1q0BR#A9ZgzX49 zew!xRo{3Ca*s;+Kn#ub&tgC6xZi6O!nNzw}Y>?kZ5%sgt3uW@ZVdgQ- z^3Y%}L&{u|eBJJn&NqeB^QDhC+Q>nlHhD`hVM)8()#nk%S;Qv%T-dr%U^8=uejP7yz;_a@Tw!j z57Ylyy?q!sb7eAk&EoVnBkVDsbsbd8$)(8d{zX&=*?j<)HfJ@IVfsbYJ=-|J_qYtKLZ<>*KR`BG1NE%HmdRrZ1nJH0=XMsIs-O2ej04LYYW(H&1P`j zd2_&mzQ>+W_Qs}4}x7o4Zv=(%fapq zioqT(h%p>UxNjyOLwFA*G8O&1)t&Kn$l_YpW^ zP6E!4Jf(y4qXsnsH(i~W@rN3-?^UwZRNr%Wt1s*S;iLX#=d!i$N2gGA;QT_eMa4dA zu+L}k8SC%Z=c;gFr7hb=33jQPvIR#lQOG9l!AxN=bI@Wgu8mxU%(HR<5=xE%j|26o8&F9&>eV#p`?Ie;IlHMJtta^Nvzy9i8{N{^VHp5x zwtr-{5xL)=_#6BmtiGQ{T-0kNi!ytbnMp>L^L=FI5$lPcb(3|L;8jqFjdyOpVoU5@i`$pkl4D+7BPAI>Fr&+C`KUL~u*jrQ*Wdl#gE zeOAlFEEp@xdxQNZ><0VKjsuH&E(8ap_KKl!fzhMDLCMX*!A@PlA;Iq8kjJxRzh4Hk zHbg4@Xo|@Q^rMw;FZ*@3m0iKU74<7)T0-Tljxu?S)Skq27@cp@TF68*>BZ+y*YpdnP$d(R_ zPNB?u^`k*Wq#HXYgEujQcEnj)kKq2>?eP*~dB57sEeLnoI1%B_Ok9s}iaGPKX%t>D zXWk90m^0r@m;B3Xu{R&Nf^Vx==krH^Bhcb=;^%s6VK|>SJjrSmXcOIdjwo9 zdm^~{=G9=GrK<$wuRC)hxW?EtaLs|=gKKr`4X&No0<0Gn2d?AQ5Ug+G0X8sip2o4m zP}2Zxr1WNx<&m3|+>I~m!{6jY9kA)HmteCsFTi!@6@Uf(>;x=0>-X6XHjl3cw&;FN zm)z^S^OFrM!?%GO=pBc@m2EoM>h(P-g|pF;fNctofo*Sh0Nd?Y1Gdi_l1AYi()q~- zj_br=ry+geFPt0*cJ4AsK;c}zuMc(&?E`jet_F6uiU)hR+ypmN=O-I@nyBj1d7d}; z$p&5}H^7bd^OFs{3l4&PG7Y2@&Ufo_u-}AYu>b7wU{TNW;DFSnX%sFndIvZt`Dbvj zlN1~h{4Kb#&Tw#O{b+FLi@5@h9bqcY;IK2pz~NVGfg^mEOKARrvXY7_d;9DE*nsII z%y0NLjRs67NT>_u*EH&OVejl&W$(N>aJF6&BQdPEC5AOjV`$wZbMTD$T3s%cwN{pK zRPIDpxf2-`AeB3j9TT&BlQUa$G=;S8yftFd|DNf<%3AB^I#>Sb+d=>9da;MGL?rJU zO7iV@mol65tnE_Lxm)$pNIx}4DX*W7fc#GeXrP*#-{K+ol;Ae_w0j=-40jlOR&N9N ztZWtd-0R~~I=>)04Sc?E9r(i16=~#NxMM2#;@Drnvh@DoOWjhymxm;RuY{FX-@U=t zY|5+e2?Dy#jhf}v_eNkv_1y$~TV8#C4ZbU{zRSQ=eW&X@SY0Y5E$X!u{BS}c_|ec^ zh|lAm+2AMb7J#2d&j&yA9|?Z$lnQ=fJs;=4)aeW^)<^{xzZfB)>zCZ*_p~ZK(+T`4 zrwRODXN7{xGHi7z_v2>R2uP}osR~vapbb{;WsT~jN_#6Q`Kvb50;>gRg4Kn0!5V@) zU`^elU@h(lSgR-#T-jXt-)gReJkF!J)gKvT`TtF-{-U$X%En=kn6*Sc(n7%uF=~@g z`{ZwFm$YRGie__KQc4{>>ZduR=~=&JlFr>~ffOyx<##ljH~)l$+6)CT>q5tn{=u^sJ&jg#`@slnzZv!~2S`Tp8y6NEXdriR+bJj^I zT;!?l_`RbBO$9gI7zvJU(;pl&%@N$pClMUmPZ!+W#2Xyf;t@Em)D#@=ei9sit&xCZ zM}pZyUFbd&aN_GcaI1x{!L187fZL?Xz-@O-2PY-(2DeLZQ-t~$%M()QT500$iz+^!ZukDvP24qS zKe`5cnNd|nvB0I_PU25heO_m^5R!jc2H!`|&1fiO*IpMfFW49mxq+xRoe3l zKvkRZee`Mp`kCaeF68^@)yFpjYwGfS^qMP=;C!tjzK>p8wjKUe&lU*iylUBV!PPf! z2kR`|hVZ&G`9As@W7EMk2M&XOt!@%@+n-zDqV%Oz8;(25`c7C2fQ@US;)2Estv`Fa`occ5>I4}jN@}K|HP4I$LHTIQRrqulqc#-=a z_%CjLLzmo_=q&@h zv?IWZ@>!}&=POLm4hJht&?bWwCTJUj>v-AFX>96+HQ2yBL_%z+sRcGtvH}}DYLG_m zauc*CG{6cIw70l8zbKx6_7d`_cJh>?~V#e9t;k4nuPlo65I|P z@_3)_JB8<8*@013HdRU~{NuArDOJC}zbe`t%<=2YOeaej@;REX%?{&^gr8FrFn2Cu z!l5G4rc37tNTY*};Laszc}s{}E!5E^ZN0g+h_uakQweF?X(2+=q>lSYxOM}iKBVHv zO);eH6NZC3*v|xa^h^dPYpmfusPoVXbg;@H>mHxQOqG+RQ9Ge0XXot9*5tBJeh!`$ zWJpAv{bTvqbm37Lh~KrMwB4=%DA>RroK2aO|x^ z3VjJSv-QoFXvn^ZJHA9iKZk}D^d*$oKc+8HjdIucjjdQAQ&!gd-a@vpn&Za4pty}K zqIivya+>HNi(=2EoinX?X77Fu&K0#~VCy$At4=W*q*@2JcXs{&3Gtk5PGITi^#sm=&J+#ten^VG9?tQY>790C1xXHl7ytnXj2kS$$vFeImJstA5^ND zfK_9R!K%+Zq3TYrcs*qV)zrDj>xNRO)`MM8?YV{=9aYa1LaPlafmYAThUz3;fa=be z53S+16IydXDzui_JZSBN&b+=J0j=ZN1gftb4K=8jETInuSDZ=M?naFz&?OzA#+R(2 zCZqZC#B`Sy*sO~cwC?n^936thA>ewFqfqm}+2qVwbh`tsZ$21m8FmEPK&c(n%7(vh zR!=s9t?ylu&gxk`^XLk8H#Gx$)CmWByyCzOpV@;w&tC?6Ex844v~4fcd#5ebXVE2&4&S*|!G0t6 zfc=Nu2a7r_0tY1Jf&)WFgM<9mf`c6wKts%?KpU$@z&})d4X@An-?hn&$>6Zl{O=lm zpf5PeG%uIsW*_Eg?-Vv4op29W9H05TY)MITwxCBWXF z&W=Lf@RHEUwSsdR*8f&kc3I#ep-6_GGZ#@#!yj?tcXL1wc@F6A=TK4mCW_hrR%5SP z6t93ykr||!>(|~SUDIrI0_oZT*SeCf3%Z8M24~&f zC!_N=zxWosnPeo~Coa zdpo9p_c;y$?~fcWrt2K2E(Yh=_XOuWAKaDPPn?@2V&9e1GDX8p*06|fz~ohh1O08GUT=&LbL=pv z*M!#touEclY2it25OppA8OV`8(LSI3l%iJ2dyVr3pE#N$~bmdbUO;I zuX6`#=~zX~oVf-{fncj@SHuiCtIPFeq}KQLiAimCtI0@hk1P?B+AX($+OHf7b@*8o z>Nx%zs8jbFP~o>xQ0MT2P?z{_GL9Xtw#&h89uZ)7t?^)wIu2lu$4j9NpXoq7^L~bU z7BCy~0&S3u`C%~dMO`!ov>!E=@LTHfRG-$9%O=yVO z0A5R;KpVeqC1#n+Cuaq!s{H@|IBvlijvcFWs;F?>yet~(%E+eA8*{5vr_a2l)26Lh zHr2Eht7bH9MR^smgR=_(nTccD6jyXs70Y6&%3i3+J)5zTqonif`rJ&?7qx=2p$GDW zq%WWJ7m*g{dBDG9k30NJw>5|Vs}-^Ee?7+?TsF>4#IDV$F9^>h)u`W7N~$m12Cm!> zDoaH7qjEo}>u5Q=y&sgK{AbIf|7`8x?ekdTqXRYtSuYb?QlK>TgThx+awtC+aZ#ZfjDye>mX^Lk94JZW8+)v0 zk+M5X5waEX*^?dOz~22FDypjV&W#AH7FvEIC>k7Y1aESRz55ASBiF^x!G*d6Xa{%2 zUhWkYJ9=i6*$Wl9TM1(&>@(*O<)usNxM3s-*J)^okgB%;_V($YQP(8D((bSd1CrTXBb^Mk<0 zCb>a#hg(9A_pZb1#%U~Fu_{->4heK#G~Dw#8mAGTjIe;_dmR@L=XbfmbH?{L@087Y zgg1U;iU4SH6IpAmNU)&Sh`T2aB!+OEZEbhq^^?m+*StoSMNMf${>!hhQ!{=5F~ z5_^!x(0wxzP*tS5E>%lxg;lUGZ4kAXf#4 z9qtA;-qtUh+)Y+=ftt=qfSQd9gVr6?2rB4d2d&q}1Zo~x4Qk>28d~4(0o2l125q2y z9BQTVO2o0l>dAem_0@YZ#5Mz_@_I5C?zVe(LG9MBh1xG%0Cku$1?reO66(~aA5@s! z1?t>(QZ~m9m)_q(U48pN-D0Dm?xyij4;N=%UwJ}3jhl)%c6eSe0<%pz!Hu?+g1rlB zf_)YnLVdS9f%=Usf%?xn1r>F=01X)ND4SzPC)s|%$G2lv`M5J**Jlq{llpW%>vm*M ze}|MRtniH!b5-e`w{l3Gkaj}qjPyWfWCiBeEm=e;zs3gM8yB3)rI@Mp9w;MKuev;j zRHJ@FG4x&`{IyiO%ZRm()r7zHjR)YW=la6G+Cd93^X95gIt12P$~WP3d#?bStjveM z>0C{)*`!zCy2E8Lbe^F1MR2_iyTRtMyTBF^{Qs|?ybWw=myPoqH0%ks(w+~t*8X0` zvBUauIL^1Zx&!{UzjcPc-PTU@YhN>^NWp*eNv`;e`o{;O?B<80-?T8|oTt z4Rx!x5b9oE3+hp26tv+FlX<;8U&gV+^YS35SKdxBaiiTyVDEMFz&;BmL47BWfclN- z1NEN~4Hb2ahX#E2OvbSzFtRr^$fqMT*ggAs@FT-5^_TCWDLcMYInT8?6l9bvx} zgTrs@f+Lon2S*-!1df{W654d>NoaJR0%+{Iq!^AJ&E4jL<6_s#h+EVi299@`29AIA zD>R|z0A4Q)g(f~~1#PvxJ+$=+A84CjgP?8Knn9BWTSD8-_7-#O5VuvAk+%QQR7~1I zzf4Bju~iv0p)25zRkrG7(!XHE5=$N?0=|giw@weCA}WLxQD326Dq?X^XD?WLSY~L7 z`KcQgjLT+jtRyfNlGZVZmXhjgG)1KKUrvO(!L2kAxf`C_3wNW#dSK&i=3;U;Sy2mW zI!6U+_VZI{-5;+*1>c;8)@z*uHILW~weVgBt#3CQYH2(U+CY0S)Ji2o#<9cd$xNvA z)fq*^HUmVwp8OH+wtIU(?bf%2+AnMhb(rE0bxajPo%+;+3X^L>o!k0}Id-`8HiWwR z3ZQPyN}%qhN>C5y3%r(Ig?bvjlyU6vJij08Ra^jWv@HYdeQrD0XVG4$@0Phxzmbcf z{vtNnPUfa`EcA`8I?L4bE(u} zT|w-f?-rVs$zh9OJ7*U*;^f9&uI1x;&aBDf>{3BJ)h;Y%YV3vT_AN5%F36n=ao!hRGaqMvEJr?Tfn+A1@{RZl8%4h!` zE>U2QSA6#GY201RvBUEMpZ$B4M1mV_=d*wB0zUiqS?msX->rQ1?>9;x>_3ao{zaW$ zf&)^k%dmbxu^l%5Nwta+K2%Zt@K1^3oCF*@JVo@Nu}Thm(BkCP8*-_lL8F>>LiTu} zqEq3O99I^dx1O$F&@W&9bm>|BoJsMmV~{N%{qH_ysd(Ci$$g>DZKb*NeO-G02zB)x z4t0xd3w1Y5hI+X8^ZKeW)YG_444vnB!5ZvU;sIJ8sIu3aOl*Ac z+nLyhkJ(Nk#b5vBKQ}Y`cr%8zZkYY9Z4`Mvjk9J8OXDo1vzyOudsRj1{wkx1RCc^o zMVcIg+rE&`qyPJ5$6uAIzgF3y#+M!PcU3j{A1gbAne<#y$M2`g4m@>yC53ehmzN+^ zn$YB<3T!HSu!yl(@yWZ}s5R)6Nf+`>7=2+^8)|7>3T>c$4{D`q zDCXE<^=tqMXMJOfgw*EDD-zE3kiUe~Zi^6Vzx;bb&S7>k)N$Nm34L%HG#x7Z_8cMS z+|C5-64MUsDnj%*H|I%kcds`B>QQq){2MB5;`Q-MxO-mn0ehVq0&cXwC)9h>TDbcx zSpfB&aSiT%V@^T+f1q5I<3!)I1_!iB2M0z@fCl*@lUH|hQ{VJq5sr)o4 zn>zZ1_!0PAm}02Nd1=t5LI+F&Sv1}%?53iyyK$~6Vx(s^Gn?Y6$bFB9i1mxBWRe=( z=CVl*3*7O2j1G?k8*iH=BX^S(BcK(QI-Y8Y>Hk-1e+4xx4gU z4|VljNyxdyj)A(H@=u?KOLwrxt6oq~<53d&;CZ1r*sG)~xY2fZuy;W?*k^GwsP9&L zsNX18sQ>KhP*G<+Xh5nRM@L}Db8t{{HE^)yS#U_u18`&2t>93Lv%o7 z1sZ;3Dl}r%9BAa>H4^#|^~(@&(-o7!(cg)|G2{Azo3$JSjqTAA+Pq;BG%nH$+CpCh zjkhb@yGu|@;}g&Z+Iyf@Di4HoeXA!jsP)y0B4V2Xqj^2K8}7Dy z*Fo*pFNE4JoC4-XV^?1=g$1RNLf822Oo*=^CM(qVa` z`*=^0!d027`At*l@~lP09`FZ;ZPOkG|7-~N8~?VHg5RCMiAwMpKo zuf=-y9b5>@=XbElZ_Yx@^r)zb_iijYZ&jv-=+d+MuN&{(u^^l7tIZ95>8I`9MFL{G z1BOCU`-P(gqz;Qqz>cZiz)qtsfQ88sVCT+!&xlJ@7by$Qx%!SpZN@D&K8M)d^c%2; ziznFQRTS9MxCMS!&kIgquabt~M%!zEy$cM%K8qa_=sLbzO2B@jbiw|!E`UXy9)SZ! zyv(6+fgvZsfhChZIo_+tHb3~p@~n^|x(@3gVY%oZ8SnM*bMUC3>P$@*5qi?FUBJqp z)Y9ll+tMX;4C&4vtt8OL#>m}vwOs+;l`jSF@sn}nzh`4!F6ll&E%@)7p$`88RYKu^ zU|=Ko=Uh1qKG=FE_|V=mg#XRwF~a{gF#-N3ntWUIesQJ3?pv1>x^LYE(0%JGtfGZg zN4gw0B#nbvOIF^vV(pR3n-xexFO)(RxB@AVMM#akbm{xgD|jg--LPOEMU+*$nj^+_ z3`Wa{^)*J}m)C!}RZMJfYYY4h&)t_18y)rs8*htDBQ{y#3pJhN2sIm57g~3aE>zH? z3bbD9M^N*KD^LsXlhFEh`=FM_8=(!fmq4vl@^tC?R!{aqt*`D85Zes+hS!se;cmNk zI@E6c7^wZifl!Aj-Jy=Dt)WhRBB8=$U#N52u4!~Vm)=28SKn}`TdXD2-P9iH;iAs# ztLjis5LXFy$CtkG6z{UvYs(tnz|J9*)+f>qq@! z433L9NXW%MJNn6-p}e~FJE}^xt?pvFT8f08_i<9HZLuev)wYH(ldf1<@}_}LYwhTX z=bv^jKL@W0$}L=3bm(cv=<}$&8`cy*9qse@_6E0dB*Y&+&E{fqH(60Hg;YMeJWd&` z7+t;wR*Wtm04qk9mx2|e%Rhq^qsvQ*==#=If0B{foE!^Qj4FQvR*Wh~ffb|5PGH5T za%2i!$0f>1OzP_E2zGO*0d_al1$$I|3~qF5Q5uEwUY{-@^;!5U*msHq?3X$e?BAyY zSd`oz91s@_4h-f~OORUwaBzJ-wS<@kadd<}SXM;ZLT{FoG+woR3iRn9aLa2!DQqC_ z3pRhkl6j8%2E|#26uweV{aXBE#vVCc{$b<$k!S)ERqeg-xKZ`K0y9%i3qQ=?0fj@ zMvjNS!4tlGHZ1rSY;-yt{>IxMfz4W2fb0Im=SYIkAHem-8sd89K7GIz-=u@47O6W0$V+6Eo0uS^F1ww)aJ6WnAG-=9@uWJ9oT+ByIgXY&p#VC2>wok zeuTg9+tu)QZpVRLV%)&4ewV>+&Rr4S-E1$|qvmY*H&mJ>EleTx z+B1`**@>+AvW++eRXbMNDk^dnUx};2{xORI+2UhOmPa!)n1kyXiVo{kSK*8L3hzcY zVE>NVow^i>WribAUi|BNI1WvSdB2^?Pf<}}_f#Zd-|q`r`4u=+9nT^{4=84JFY0e* zPchTHw^~N3bzxF2sdjO)jI`-LQX*XZ~PT(h|>hQif~ zGL(_lcHIruv)Cmju2X9vSYLSoSpV@Tu)&p)V8cAV)H2%B1+18^H$uj-Lor>i6Id}_ zFBGhpu4e<5PuG(ViSwnFVn|#Y|KF;7(64-5!pXa0jvb0=dKJ`n=`ui<{*TA2p-@lb zDQU!>7utipO8S8tZ4Ux_7bJjv7Po`?Zfykh8x;WcpKSsab+&*8qQT8fH3LJt=~2L^B2wQ zR%V%B-IMem6g|G5DjGlN`*oG~{qh5h)FS?!gYsK?(LqZvKYvOUHx`U?Mm@?I2X7BY zIhm7q3^nYW;>FN|o^z069=>)5dSu0GxF0=G7kX^M2x#uoh0x=vgP|vWIxV5==5^wY zv7GFo4fmTK9mVAS>2Z(ZBJ#hx;ah~icWfy5{`5!S2bt?q$iHZyrG)h1#Ov^X)QUI$ z^7wlr_&@O;2mhxn=7OJ@@hzF>9w!j~d07aq_tHQO{x2^b0vEsZf`5rsU<}xFHAzj;!+j0NW`1a0A8zm*XFmYhGhJa1`m>N>((-8qKv*4fJNw<*tCmez&8 ze8tJwDqwkyzui)Vclwdf%Y;q#!C!bo9oPHfl_z!DBc7gW!MLtx*wy^8ZFI36 z*!x%@*k@+|*mre(u-{w@u>S-#uxO|%IH1iiXkgL}Xi&hWB90v&)cmWTg@5ClJyXa( z^v%kXCYx&Ddh(jT)JIJI5i1?%kVdxO4u4kj2RC)+%M^Ldzx6~Jg_GC(gQYRV@|u5G z4{!^;6Ebp-w`n7WKHXhJno#QyI3a%_IPvicaH|cYz^(Is0n2Ot>pI|iNkjU>z1`$k zs5mJRDqo4>KTgK6qhp&STqij)iID5$BLa7}P?ZpOF);^sdAX91>sqP;?smWk+`ZsB z_?yM0;2!&wQ|QCDqYA;_ExZQq**P1Wl6nB#EBd^I9C{}&1%L0f1KcP0XK-H~DY&1* zk|GM%@4;~JfNEpG1CAww2j1iR%6?eg82sarDEJSW(UGHL@Penf-jD$y;GsXvfxjfd z3OuZdCV04~CU}I|8iY^PzXMLaYX%-^zaKo(oo{)Ms-i9-hf%xt;rvm>d~P{f?;#$K;;l`RPXR*zDiHKXpC_{%Of@b~TRsDSrm|XX#4#kMkW?MDFAIJRsr5 z?N|>UFPR`A{-w4J!v7L6jpy`f@Sh;=1)gwe8vN7D{U|t>He>+&Cx-n3o|xs!^MmE! zNlC51lljYXlSeM&d4H^g0!;Dq0Z-M8Mfj<0VpHhD)X99$!ZfQy@U&Z0JaF7}mzM}X zy?Yn%40Uh#&p22IJd^7T|Cs^RCFC&khAI4Kt#tvW5C%FKY@2yKS%go;!Zqwn+X5ic0Itm z%jSajG^{Eihdp86gZFATh5z0oeEj!STMyo6(*pkco^+Rx!~O;9il7DcNVo%<4dH*_ zWTb>RrY=xMREd-|yJVhw#tsRepN3`I% zI30Xu+d%M{OL0Z?;cUH93G|!<{^u4=1)tkg%JYM6;DRk3QhvW@=6CwyfBypixe#nE zZw%evcWd5Nsi*TegX#n5&iIdAwd`Z&(y-Vsv$LgPzx_#|NV(tc+)o8GP(~< zQ_ti4%3674t(=~R%3674t(=~N%3674t(=~N%3Aqf)MZ`a?EqFUzd^s)UtBMDp=*Br zc)wo$JEu^0lDu9%W?axuR4<>nz)p+wi2HT)E+0=kir(WB-hSvc&UfK=^t`(9Q5uE2 z=QIP6((*mPhGW@&0yG(qb=o~6SN5)gWex0KrARo|G#Qe{H%(&ru~Ee|s0v=;5c1}` z)}c6$?A8%+Kg8yq6CWy=4E-&l7v>0^kQTx}?;E~{|K#L^EDDz&nCVG+>ib6E(|!Ta zGmb92zVw2gt6rPev&PVZd(WWfvtB_j9619moNwew*Som!DO5JB6nbgeK}^ZI9Ni6k zrQdS!Ri_B>wRQ(G=)CJX4&WR9OTjl@gk_QYt-3#hZ=JCN-!A(Wd?%|K`0mAM@V)5| z!1r@{SkrkA`X2`uWi$mpY`G5nXp9j2Sd{BY;huD@34SVA3w{>*1pM4`2>69wd+CvPt(pNhIz^_+{z-8$ceAmw}=2`R@RT-iRR{Bl_tlahn zSS9K@ST$6|n!>3yRQ4oQZ*U!~KEfBQsg?uQe0Bh=b$uCF`_xi!)&1kZHD-H(YxeAs zLC^C4JI7)30ktSX?Ad4OfK4^k;JE%{sEN+fK~2oCh+wlUB3RSBNd()SFPz%wO(Nnd zY?BAd5m-$4sRE<&N~cilbhK|vNk6@E%}+x9iYe3ruwn|e5?C>X`YKp4g?c|&F@-t< zte8T*kE25|g?b6tMm}|VELbsh`Wvuf>U0!XF?HGrET1|Z6(J!9`P69#2e5qVbTu7t zqg%T&DV+EEEmBgSg`2^?Q|5sEQfGtx`wRh#k_UqW;>6&<;CA33w?J@k{Qz)?X*&VO zjuv_u;CR)Z65{x$KY?3bYbN>l)CYb7#K+&+_3qMD*o+PXo)B+e{e%mM^#I(+VtKZqv4(lw*fW?`BX} z-vp>ztSi*rv=P+9MUU72WA9AhYD)hefrjF_~&G|!|`X1X2JjLDnID^kHo2+k2>Fje%!MR{Qs=# zmBnwc%(qixn3~?RMCJ`*DQFoFgVxGQht_`90a|BezLXtwPl;U-<&s-KmtQD$JyeLT z16|>1#VFp-cw7pOGd`0dV{LjhE0<}u=N@Q2+Xem>69zz69o7}J?9&3Q)~-HSJ)#b1 z{ofe&WiV`UM**#B994GtHRkcCsm5R8PVQ?I7n25wod3$kWX83eE07jj`;ce5OIq3 zCWCX(>Dxw*W&iA!cVn0*eD;)Oo-U{Kf{l_I0)7cR`@BaJpfs%^%%Txhw$l}8eb6AQ&ux>b0PpiLhtTM zS=TwF7Q^)Vv!j&hedd{rSvTtr-0Lm>O2XP_zL*91Ca!|^8~GFb{f8yU*gs%^Cv;#` zGW-?Bp&B%Sdr0kmyqgee@nz=!zSjmRDLG2QaCb_QDrJ?EKcL; z+hkf2WWT{&p0)S$8sT?wZR<{YfY4S$i$l+U`#9#{!QjI)bni}{jEHTlDt z>9Zmf!Q3~&Hj#CMz(lx*>xfn8@XKeR8{YoGlG+{B3l}I81iv%9JI&6bJEz&T)Sg&B zO3>qWA2ljv(RVDEt(c!hQJbxIMRA*A6g4ht(}&6y8Gy3&p3WHpUb3# z|AWgh{QHVkNk?vyvJR53O4=nJI>h>Gb|zEr!=V+cl3Gci!|wI~!_|XCW`=_eFVz4W zEw=|FG6DtOA}5uDZv1VjDAsIZ;_dCO??{=H7FF?0vkc|GKUU$>#Yj1dzG+*6)+?px zIw6q*A|+sj=2ew;Cgf{7tI*a2H_&oMjNY%Bn~ZEZ8U$av<;)y2Cv_9^K+XiX7oA7S zDH`MzD+$EGk{`-@!?Zob{2-J1o19_9@|Pgg6E zFKdBbrUA};oYz@hXz#r0&~;PqLf6YF4ehf~2lU-^74#c*2lSt{4-Dvb2n-x~Ifc&` z6uJ^RxXT{s5bLSXp}}I8NPUe#&|y|$mq^&%$=EMky^qMu0bs*Rjlo9CTYwQ6y`A~| zk(25`M>o1+`O#5YS=8Wf8WhS~Q7guqJu8?OO<|oPdf^=7Pu9!Ac^Lm&a^N2B*?c~8 z^vF@*rXG#sSf^=UmM{;j7FYF6pWG3xH*5lm@5f;BU3AftO=wCGI<;pv`lLjwGuqaB zSh6Xc9+n)RsU@cp8bpk6zi7j0PVt&&f7AaP?KmoKZKrEN0%pYbS7KUA9DdEsOT2x| zT9o|!KG!;sVRWsW3!$rU)Yo54;)_WS8D zgVEagi)DO`Rcz`+F%67*r!fu7_R0dEB?K^yZp?+h@#*X|)+QNN(59)*F|3t_!YV;q zC=7)?g0}2^1+4bfaj<%$y`WXS4PXuX-$85BSzt{aaq^>0>2t5>!z-Qf4$S`&^`;YLElZwK)+FIK>u04fC1g6gMlNL z#_;)qLWe*HcbNnoV%-)xG&lyjzJ?EUm{l|Au)A%*aCLW)nf1Vimkhv0%gw=8m!aa{ ziEgx1#%yI8|M7K`A_W;XdpA+SXQRpo126KGs~ynmeGl&LIeWbNL47lfHAoOYHE+cSKdu zxR_{4jXGxn(M1!evdGHAxfD&Bl`ovpjEyf`Y2>?S}wDgT`LFb8bb6q9iGbk|+!pqc1AE zTP|TxOU~ytZ{tE2Kp|N)PE7_^HqiGHgDaoO;EKns@>w4{f|^26)du|1lp~@!6f1hb zU!RqT1&HPSCQhnadLV!2i!2GAJas8& zETvd|u}H6>234w@YY4Q_z)%Wi6xgWPKt1|YY@l?NK6!~%4Bjd1E|jmy#XS!07`&rG z6PH}&;~JMP2edC}T`qbehzM2ry2ckwLS=fB!Fe!!XtLPb@s6>pbi7dQ^gt;!Ezla7 z8kBQ%m$Dmu(p#WD5Bdcf1WLU>)YL$I4)ke&C~t~tx7h!I5f<{%tJL72@*%8TqNB?b zU&r_p_v88Yr(897%FK4^gm!`!nWezCw+C^{3Agu{_r-z~_Gb}M4+m0V&$%@s9SsYrvgcYm#I=adB&@iZvUAek6g}Hjk ze7sjc@#(6Vee*y_5BgX63h70{+vvo9c6j?Wil49D(X0Tb(!g&T+?9)y&4X4hP8JLO zC9aUp_tDPuWMVPljV7$U-mtDz9Nv5-eEb&_-Z-n^|4JOrTCN*+n3?ooKvQP=gBR%b zJ3OmLF6V-R(AJ5spFZgr(Goo8fE-7fA-@x?zl+P^(05usOLYT=Uu$pvT;0?`^f)+w z5EtgjJ-C6NTekD6Tz*a^{c%P6;|!(zTs+JzKu?!vB3}ximnjvXX!AOA5!(CtQ|P*> zyP)fxJ`L@&-~#Bo35^Sa->99?{{_7)G(}&Hgy*rziE5D6(g>*zU~wQef`=a z_}{G14EpBYW8OdfjXF6dFWJbzm`;jG8Hq@R!$F8u54xRn}Yh^(bn;!##uT_%S8U zm?vc>4K0nwysu3M=>1yH@S64?*&?uix`y~#(uV~=%gyE-8UF+JR}5PUtq%X=5!-OS z6F%FZPnPM9^PMt(fc;Me4nz85v8kCQKc%W)&&Ff593Gppwu?-#x#!EBs}{MnNk^eYW3# z>$o&H9sZa7Zo~hIvnBM^YW<+ERayXjy;L&xzy9!KE}!?trIYZ#c}y4nw|48m|Moh6 z_}_W*0{(Yr?t;E|N?fHdcj(g?eqov~S~6(WzMrAB&W@9^*3R6L%dD7_BxP2LK1;2C zYSjGq0e_UVU+06Um6Kio<+haAQZkSEbeyutFF@aW2l6e8GoFro$Ake0qB$EfNoFsD=I^gVHJl9vPuDnuYesvLA z4J&ju{8GS$+PukGW0AXuua+Zd!;Jv-~ps4a^Ed2Lv$nY!9S?cj^W(D=tgL z{*^)-Vn6*lxA(DoWrr%zwwB*P+vd!Hwo8wPuC-OPA!xre3&+<^Xz9hrIZRTAzoT>< z_IK(lu4wPvX$$;aqI$vKH7FPUZm##B-K_^fdsMc<`8>;rHUvE%w}gM4YiHo^b^K@e zd+*o7`RZ=00A25oU!i?w1z~^R3Hz|W-w)rx-~XEz@DFJBJNyHiIKe;2-w`_4Wes%L z#r@d7!LcUTKYVv%=!WZ$LN{7C0{ch&riuL{$E<~aRLdlr9x@Z;n>wG z139c#>lBF@)zMTb#)M0{eCj5CZAP`qM z!{n4iM|_h-d^(D;6ugy&IzP(@QCGSUx(czjHTuZi2)?okI%ndT#hc-jPbPAb@_y#= zm139{6DmrXRez9ACVB|1oJ@2KS~;0$J+yK%(QnYo$wcb}-fS<=%w^ggoynk1M)@S7 z@1d2Gh}uCbClUEUD<=`Pm9m3tq%W@5&DRIo-JvS9qSKn*{9_I}zG{U<`CX*S^q!(aoTPLYhJcySqb&)Nq3iHE*gX(AJ{LEa;XRonlzG zd@P#TY;`3#=IzyocV%y6dd;2BLZgk5Y;txMBNdHdiq#2mkcx_@=Bm*paIRIBlg^a< zOM8Cn@Qz>D=8m1j;td;#>2#)G7svoL7zrk#(HIdt@l$eQH8dk$UWZ^ZnB`|jRe zkJ2bpr1n(Y>SCH~=Wuj=ergo83QH$2<3U9+>c69CEzYE)7PZQlPgmG~Cr(Hc`|r3P zPwc~^vZIT$ifUQ%lH->)Xz#UF>_u(D+KWZ3b-DadtJs7}md14X-2!hmkK{ShT0PcwL)wc}7XbZB>QJ6FJ z_mJ`qikzvLA+#cA`WJSmzdfgWhsmASGeNe`@xy$6Xkz~$#Xm~{{ghp@cvO~rQ!W`&`QJmNA&poO2hj*Em_+Q=`B)W zfPV(|QySnO3avE2{}r^-0KYG^!T^8&;5a_7!T^6WYiNZ5epgLs&zI#vFVh-&yua6( zTxjpSGSGEXv!Uyqz7FlP;2!9^=>X_A>M-a(YbhAeZ8aD;a$g*uFDUd^=-@6(phK(& zLx%=WfUd9620F~@Tj;R6gTQe0D3O`1z=oGx!A8ry!HA3|dVK!KNv6<$$*|O$p?v5( zKB<`RylGa7LVTeW8l@>IF6yb!j1*TK`2Qa`vMh)$Ta*4VsB3mdEgO!XNtISI-Z9c; zn=>8r8Rk;l&Ens!sdFB*(a?!vZS&{_XnQ$N!urj*6o=riYye*gt!x0F3hgxT%N~pw z6nYTX8QkS!7T;%x^$O_F;9b!5H6}xcSuKPPySoewS05`f^B1t;rCwm8<^8~jj4@Gs zzQ{?gZ{};r*Z6(epfppLDHVx4<{d>rdMX_u|a!yZOe+_;~kby+IH2 z{-CF8q{x@ipqFVci9nmzSx;#1ya?#JTa2LV_}aphJVRq3dfTK!;hafeyR784OpSE;4ft*zl5AN^Z3LN9c%* zUnK%H(okeQd}vkuxaYewyW1%C~eumXOvG*;vyD>Y${?=Cz5Dz)%lb4A5EYO z;j|l-`wqUQaU6!HTwRjLoZV;~^z8gg(D8G$py%vQ0O$TV2~1dWM347R>=*-`G@%SQ zFYqxq|J(6kvXz}4@3)}IHgHK99k?%f^au2^{ky>BivqwEiM`>UGNwB8%5h`CRWW{W zU)?_$ToYvpu6OMTZm4isWPxb;WaGU((5d^5>hbSy*{~bjn(zbMx6NDxoi@81xP8bt za7WMY!JVypgS-5z#qsgGy&|Fa)LjkkZM_ffdr#$o`_&qWyzc`ZxcH+UAD6ziugKeJ z7?F@6?J4q%_&g5&+7$i=clMC-{+a#7foqw&b8wzRgWTYMXlX2t%W9S+a={k(A8t_v z{)Z=YhkLf`NRgw&_jRPr1^6Eg&jycq-wEK~Ic|3jJYjqYJgJo?^4Uu8)Qx%I=~Gj| zoc*!jnT>%epH_NH6loh4_$yR#00_kP|A=6=5dyx%Jke9$F5 zjnDtEc`EoQcp3QEZ7%qy)i2-^eJS`}ms6imsW+?)o8G(3RcGLRYbR1{&z!1P#@5L_W*_jV`692(%d=Sq^QoGXXSBO}8Xx zs6N(E&E8pYVG=g)gwBCRzA(a8nr_}Ycmh9rfyBpMx$w>K%33u zAkg-T8)$c|23Tu%70`aoQ`}GOn$oC1^aKZ+UmbJ5^Q zC0cG`WwWae&Mx#%jZMz9Rz)a{&GaD`KNl)rIQy|hdN!;guJ+E~RAt_&BV8XCI8f03 zdW=zh4#e>p%fUJ88+o#xyWyKX%!Jqt&}(*@UuB)%d7+G%aU?a1d2sGw=*&I((1!*M zm9Tr(3g;;1;TB&(Xa7woPHUAc`Jbd_*ZXalc& zXhYkV&_>lwqXgQF%btfeQF{q(a{q;d-Ayl?hc-KW4%&SCc4&*0Y0y=Z=0jT!dLX0S zg=!;4L04~a6xym+QWm?{@K^_J-Ebsy%_=jYZEAIcw#i>Bq1^;K?J#J&b2Ff8-L!+Y z-#Qe!_7N%?)8;VuD`>~nw$M()eW9JFYD2s9Xd|QDXjbBFXt%I>(C*DLp*?KOp*>ww zpzD+=fc7%Y$fCmpud5fJz4Mks*WI%dx?WBkw9g8egmx2rH}{A3`(+2T|Lkb!fWFDl zfwBFfc)y?~qoISlHir&z>Ioeh;sG7{aFz_++Qqde|Nq@-Y-C+d7Y;X6qjZ73Q@U_0 zOO0B6_oj)HM}lbB9*^(|qz?2z`mRkM?j$%3aCPJqz{SzUQB}>*R`i&-ji4gOmxZO< z3KCvV>K|j)yAyjsI9>=U=bqh4m7ds#g)ek}&KHLf#U0MMeA4vpFc1{LOQ@7~x z@n%bxgXZxGpv8nZuluZ8XFNE1Spe zar`^BSFAz1V+LTY-J0NE%4CYq8{|Hey3^^A#`B{@*H(EwvE0q6Jg4rhLiyFwH)sAP zPv2eShO<=E$-tMx|LA0p7r`@rjkn~toIP-u9$&4F;S;J>tEtnl*GGICmt@K;jI%Ay zusEaQTPe(>RCKwPbT#VqUyd(t{5Q$1>HB{)(9sKd$6rCgv&H;uXrtFlVGS?o^Mx~c zl@Cj7bnw-0%`b4u)%ZB(>_*~Tn%Vhc)hRwcPs;9d_D=@q{wM_#mh7g`$5p=0&`IN; zg7X5ef%CupADC=qD)4>_nydtuXuO2`l1Fo(m+fB%E??vdu1IVT|CBM6p;wOk9$Xb8 zE}^x$|8)4TiK+yycWn!9sBm0l!Cr9Vy$xXMzC8l}{+12v!L143z#jX4Tk^0oo!-xzs&wA*e`SUL7eB%AZz#^S}K}y&T2LV{#grF z!2fUyP52+4&xpG<+9$%=^4E|ITr{EbxT!cJQRuDv{6TgQspx z15ckC1?KGk4m`852Y7bXloUSCxp^bO^HTLa`Fz)PY(?f7gEwwzgEvp-fwvCa25)aV3*K3h3ErKx4ZQbr3YhzS5_rGY z6!1ZpEh&8dhs~FRkAf4x$8K@ppH?HlC;I)tr|MlqK8#M`m|JePDTAtfs3i#URWkJk z+Dh5IVpn6ZQnV3rFa6*==nv10w-0kE8y{F0*=)Fd50@!_Tff^E( zeNXE_?>~15`oKwZ==80-p)=MOKp*rR;Y`0N7+1^>0FR_1F*3Qo%aXO}lCJPKJG>Cu z{O9`67K^{aepP#U2Jn8CV|}5kH8g^*-m@ySmECjb8V%H-t+mcT*L1OPro)7qH?~6C zRurIZvrnh7yIt-@=vu2%q3yGFLD!x#7uq3ZA+%$EDYR4ESZL>FouOTZCZ`Crxz_tJ zfa%s|G_<>ECuk4vp3ok7!O(S#8$#E~v4-|~RvX%TvnF)iQ?;@L+UiX{AY=OM2#aF+ z_FF1r`pvV0_HVmi!aCq59q7Qie?SLyx&s|-G7&l?@DOy!^S;patBHMm^-nj2ZlHZI zOQ5a6&TnMQ@S7{38_sV6-RQt%=!lUnve(#F1z`@y_{&~4JQhshKik8uI@0^-qiNpa0;}JQ2sH7gl zfjz$Q{;^=HClasp=uIN`_JMoG`u5<#MUBDC#P8vMXpHk%I;Wr>69ASjXH}N9MpSiZ zjX(WCP3O`gk5>b=DqI!0P94;~w-3}wuq?~R>z=#f?lTPW%)c_XKp}yKQDl;o0<+?@AL&|p9R-J-%UF~zflK3|5*#bfNo2{ zz>(X>^7(>7CqV~y5uaO#;}W=s28++FzD6_nhgtQ2d)SlT%f227KR*7w?Fc~_ahtDa zIP8r~U96^s<&BXi2l5opK+NytuMXZcsf8t45U72qcBDHu6FI3WVpgQ<;<`B^X8$`0 zh*$x? z*Ft#i6yV46}%>P;nW~dv7nDV(&Fzb8w5o3!@!B(t=f3Ydpkkz<;MGDsX zSLlCL3PzUF{`NmXh>hn+NYRfXAthv#gi%69llEwx_QD8>gzWRCX4-U#uM{(;puy_z zh@{GFK@h%Q%unJ&6LS?6sPyR)INhcaT|vRXh1#eM#5X>Qf>nHKz%+JGkKQG6?_juR ztnUdPT+{|tSgi{8MXoCkny9rFxo8k*XWdNXifz;Qo*ihkRmD-DSS^(~UX%B{PL%pVCFcr^eG)ti7u z)w)Xgi_s+)hG3#rUkWbj0-9d11=6l&1Pr8 z>g!uzjq3jcO`h20VL0D`pHBG=%mnG{9Hq&)U!m1AL2aNZCPQfNxtZXoUg31YKx_0lwjP zp%n)BI%Giy$rtPOTLm5BI3EnPm(yNOX36SZT@fGcCkQ{Iu=l=V8W-F?RpGXZ z@LJ(^-cgQ%pR;gF*F&|d0Sv)@<^d_QcJ?oz!;q!Ywr>bJHGG9swk)NqMzmh zurWWp0S1H5@U>LV?=E8+cm+HKulV`MC;Y2=IgR;Gyx@|BXC&@HBz9kIsYNQp@M_yelDOf5%n*q)bH~Jf%6bA`kxm&e|)Q z2mf70aOHV$uvm6czCvwFBOM$k~e%rwM z8a==;t6_=cEQH-135Kh85SiI6@husyAoM|1S7B;XtlqZb_NG>p&S~?f%GP=;l5+is zUZej?%4MiKPD(H8?@zftu=Zg60qv7~eIJ~1wVov9{hdovu6NZZXnovW{FuI^l*@*$ zhf5F`VmEzBH#GZhs8BD9Q>+ayUE`P=@7F}iwdq`Uw8NbCN`yXr>o$0X#%S<37lRxvvmz6{ETNG6IY&(KeZgsbWZYGaPG!9Fd=pZ zxKFDem~l;fw+DYWgwC87tjFg$G{qgv`bm7Zhx;2qXEzSOvnrR|1$_U^kiua?ks){o zA3jWI1ETO4C0A z!Q6$lr5s2SHa&}BCXOx-oir;4I(=+Dbhgza=MnGdfIHBd&RNi!6UF8@ zExlFHTFd71?t*siG-w^!RP3*NDkYcQ%cTv1F28XKbcNriurtl97zkZ)+(76`Lt8@Y zf87$gavLw`D&bzx2HqynhIS^qFp{!^>4iLKv%}A!&9|S0 zwpe)vx@yukXv^=Y)=FEo5lPV1Til1X>LoT8)bKb0ZQW38E~r^$EwoLo5xM-uCjYRM zX{X%<+V0$H=vp^Jq3yT+3SIk1XPn<*?qF!g)gjPM!`nbRPpt{<(&IaUH`f7Wq20nb znDErto6sIMb)h|7Gob5enn8P+UeDz(URU#>z4H!0*WDw7u9veE+GoWJDLeRXo&@dp z%QVF{~z9;S$6#PM* zOO&ams8JZEPwI-MPhGi@``}g-b)z`yHjnm15BaBUS}OumG5X~|1Oca10+beVwj(3;LWpfx9o$*-1PGPKsRX*gaxcQmw)Y!vp_JvC1z z&{i(32Xy(3vCtKMABp|-e(Mfhaa=d(N<$k!>wg^vUAavy=qllM&<5VR(1vz8&_>nm zBm!;5W$!?nl+uAVd7vG`?xq*+K${)D4Q;;t5VXb0Oz5gfE1@mF6L5UB5mTY7x3~yx z)oX=Jpsj|-E@~WQ&rZ7qwB5M{(6w&Xfwtc|2Dy_~twJ}boR-gmPU+V2-JyZ4{n89Jb^nB51){wNV>3u+=} z_rYB|L5Dbf7sKwMA;Hk04;SZh>aLn>VPgtM#=U8rEzl=r3-qbW<9|4EhElc=%Gm-( zQuHl-ifo}f-F+GQH{s~la@~2%oBf7tIkTP_5-w%V*0n}-{m?2!LgrIvJ}yzR0MRvR z_+}hG&#@Bp{O=>cWRDTxf(};jUuf+Cy~t)ZxVV-Y_`Bx+MBa6e;qxxJ))D%T6H(yO zy@BAe{fofmi>$yEiA`d7|CBKr&@0FF23N&IB(nSJ{;R+>QI6o+@V1uhzRp<>dcEs+ z;D!nhL>62EH{Lr6ZaO~RlK0R}klLA5|V%;7-C6;IIR z$r@ZGMBPxF&-uwKhsJb?e4&`GJKS?MM(rF8%;8J_cTKOQLPsCm6>gYn=4J6tV9e&V zH}UCf=mB(HR00)i=u3=32Xb6rt61Ruw>0wm{Ht-8X(KDIsgnoVXxPN?b=f>N0_~38 zmazV-#+h8&r{cV>{bzt~zH>qMWx8m>F^H?7K^O)HI_k#S?$Q> z{lf0<2gB8qMP~j1HoP<%Y_xnL7?CkQhWC%0)E7EB!d$|-<&(;9F#^rzn$(r%#NYJ| zrp6s8rQx~^M{30y^5pgz%r@1b3sIC*2Kvto;ZQwWT20E_IP8WD+_NH!x&J{I=mUD9 zB@RVSxkM~^WX>~!|Dma0$MF8=_2){!_1aN<$6B{LNSHcCk3=3^&Qj3bk^q)VZXx9_ z}qdz0hO#id}!e+o;rR38-KHCkeY(cH4pT|0gXK@Qfdx4fLLwKSgF9P*UZ? zkB&Ym5K^@o39Ufwft2W0M4~H>-JmQm6b0#5^Fi%UY9DoFcezaiH$@AnsurlJL079z z|1cBK!6&x?hpYpoJP_g(A@P09>`4<)Mw2(Q|2u70ysanYbvK%@;Ki?>UN*gcD)qpW zfTEo9>2p^OjC3WU)zZH6Iz8pL`>8rz4zHUo@b6`}-YNx?njn-M>0%nknoIP|W8Q}? z!5-H{y;z*sa@=kW_CHbE1U#uV9XwTPJN7$uV+hhO{gO_aBI}r{BUp-`@9z?%oMJ48 zS_-ceRw(S!P%}!p%JCpGMc++uD5*~$fPLw@xOj*~+bqJTAOQC{bn}x2l#L-I(`RA& zi|~x@IC1?qHbJWh)0mq7$*Y|utyq-Um$YJaL6k2Uh;ReX;e$+6mb7AVE>UFTswNll z(2CXPkPLH)*QaEI8~q+ixM;QUUOc$z{6ui`q2XZazTai+zh%QLaO>jn;I^5=z_hX7 zfZK<30C)6k0`6=b0Pbqw0`B&z4(_ql2lpD4mhp8~Ty|K(tkhf<#ng|~$zoP^%aSmw zSe=zJ4fHcXL-lPUAEtmtmy$r^BU3<=og+ch)D;46W=j)6^Z3c2#e|>0s>23=mVLT{ z)!MZHt4Guat$g^gQ6E!nThQ8!8w1IrhORbfqv0XtFE)?0B}|3Yc|}9dS-9J;$pmXB zZv!2srGSpHQ*pme1E)YccO3-n(tf#2pv|@a4|oo)`zI8!KvASZS#0%_>TE_s_*C>s zuU}c!vv#4ll+<-7ie2+!#CpK|NUjy@7UwcA zHGP2Vxcrl7ruvFYKF)h}U_Oq&R#66hJz55R{r-+PeslVfHo45oZs%f{Rjdwy2Ks5B zq54XZ59fhKm!^WoM`FQ$gGI>q*G}HQ2PHF?`S~g9FR#^HK7FOdnl6Uoq?jbv$i0cs z)$eGT(HvS181@oCxdpl>5zrYsn)6NkH%&Aao6NnE9HKvo9#EO*uNUs+37(=@|LI>i zH?vTsS*Xb`w&l8gsGZL3oq%!7UqW8Y*R?NoDw3%KgZ~%B`n}0!<2VW36RlIWD2r)e zbS8jlST-jLd|n}qX>_wZ{Ec&h16Z46OocX0P0VF&wsbOR9{&?)F<}5$by!!>vQG=J zTD$sS^@uv4m5(i0qqZ?g@NNEY;&|atbL!; z#Mw1hPX>?cpgt9U$T^YqoaCM0-1s?Q!VD8IanvSD_D}lm1@ydb@zC>s*aw~5cmue= zKM(E;ttH?hn?2y-TD3*qdYZ`RSt1+}d8`WD|G1G0F5TM)T(&PZ*MvT-dX)Tk+19Std-FBM)ZKrP52-5{Z8S~4}R;2{k zR&1IERvM}Y>buMXE4O|CD!QD#_QVOa$rD;5qs7pQ4#uZHLo2F67Z&O9{tDyx+s8pG zjN>QuhE^EI9}x+yFpl5J30h$szhR>|KA*xkek})Rxp90u?F!HeZU!_~V0pD{@xG-}KH6Wi%t!yx5a$X!ON>6Lz*4pnC{qv;j2MlVWDdfP6HemW;3ov7FJ@DX40nC)N*5mhfDDEP3R*}J>hx1(F zpZ#^dCGU5niyhu?(R|}r&`TVD?7Z0YBF{&T=Zop#mz0litdgg5|K>$NUN2C7h@#P5 zg@3&FTf4OjSB@$sS)?@V1wUR&y|`Tl1wU#nqLhp(T~zg=@8ZbR!c;3LguW;$S)aZ= zfyZreuQ$q|ChfJ-IgFR2VP8hsv?L8HDGPHNQc@OHOujL@{*8Bt(rsd^dTxyH`uf2NZyd zPI&uL+OA5<2PNeLPB}`-2PNf$x79a4fB8V(LBAXwfm)P3Ef60ZH(0@D|2mhrzU2y!q5&qEo z)%2nFH++o!4`fQA)6MChyB&@{b2JP7XIJ?_-+#0b`cdb5(2skTf&R0qR~AR(GT(}$ZZy4T z#jw`Q8^jQ_jE6yMWu-%FKkERkvoc@G4!WlTpcS*P7A}L9&%Ubgv|<$RXFM(i?#5?Q zWUNiEX5})?_S^%_XS=}v@9H)$I(O0biWbgqcl0?}>>S4NqWK8N*cI5%X<#Dg+;uYe z&zr-zKaQVsnALeb@a|PGTz$L9%za?POY^}-%a?!=8QbD`|Hxm?f{m9Zfla>s7XDEa zMnX4jC3a0T>(dRoxz|?sM@NJ~w=irBw)C4T@`*Fp>X2iUa^Ztl#=jI_X8q`CYHyy3 z%DASsd|ReHUoY=s6aqDo(TD^zk%hln_O+*_tn8iLX&Ec}=I5ZQu?8i@T}tOlio0T0 z@IR?ZpYx}ZuHlldVNO_U{l%`~lIAdedarG2r1+n==(b;XcNM=D_ZdYV+)-VfqhsHB zUtx-~vY2~1m)%umdGzqG!6<$n{xufG%Iu$)p_qB3%{m!h@Ru{XvbY%#MMhN~D=X#e zuC?nKXuswrSUWifbeNU_I>v4Rodzxkox3K0F73tQvTOf^0&i}<%Ru*LV?ht|UqDaS zULs%i1HDW~OZkh}+2+vRc|D=)Zt;Mwm(vj1XJJ#&cXMsfZ?qfeKf62_(7g&67;7)^ z78LpfI=Cxl2?`)y=`jF9gD@`i1iPO!M1K-h7NjE~Hbt<9EW`Ob=ndPCe zq!zCYi&r#Y%Eyp$VNM)Js-k9T8+s6mc-V5?-mV<)mKZMr*CrbyejGUHn8ohJjZV%y zxHyI%=$8|3v~&4-EQq_?zIoz*FG-Z~KDDR=T;o--0Idz$F^@;J)P1 zBb~{4{QFxrh%@H4CUk`RwwXV} zJ#AJlxP3?;a7WMf;Lg^K!Cn3uF?{@PFK6gIb*F=STQ7zC-c#qn{c27k?^}WgE_Tl4 zkQ6M1{SjCF>zk;pUR^EmjcJNyssY$Rd-%>MJ>pSgP*&U0vx0sIdw?E&|!X0aj{ zB*Fi1^i%jBo)8ZAY}f80M~Uz2NSj^oKN`Lklv_MGZnwjdfA56xYVf31vdF?=&8KdR zhCY32FqpHyCwOLK8}RI^*hD`5+`K{H`6=B&*-x!N#jxhi-q4C+%|WKnieb&wUP3E| zHS3sI@_7|?*PAb(6~mejoP$;jYhIEDtr*t)^E~L>@27(Id&Pnex~xd#^FC~z2tEp) z3_f=I3H;M)0Qf||EBI8sg~*5X6Dh6;x_{JS2<2v*g5}3krbe5*O;vB8t(0}euEt=c z=t9F9!FkXhZpMF0BArI~H}!dc)R+n$Me*wY>m}D>)q96h6eiDV1qDyK@B>y1prF}v zR4k#AD-Fn_k}G{T;A9z=A=yGgr)D41xIlQa8$su2}6=vo!?nMrN{BhQw(D7 zx(r$z#17L##fA{}l~-HnRik|R*{x(H(9M(#w=7~mxMVnmOpo)bFL}r~U!Nk6E}42{ z$*EGw)T57^dQ{TB%%SGB?IYWlyVJp3l`AnzPsvePo435nj&iE*aTNhA75*g5I(JHqO7Zb$jf$tARiCu5V0by#H?R zffDB4YF}ahy^qC~r1Zz<;GXeoKkTn)QAs=&!}}ka?*RWS>3ZnHBP&2>OMZhs()m90 z(SRSIPg&HL@o}eae3i@5s%*J~GVsxq7^c>p70}v82SV#?HiOpv!xa0MOZJw~ZbJE= zUqY8e%hPfEJRD<-ufx4rd84L)VrJIK+gsnkf`S(l@CxLEAr*Cfs^wE%pQ?d|Z>#GU zPQm;1v+r8c)ha6cFbZQv}G_$1)Hg<9Da$a=@IkUO<4IJ1!so35jO#S1u>B z4F2=l)q(%~2ovaieZ=KsitFbpZsu!tL^gi+h@VRw|GwN7?J@HwIRA0G8Sp=TOA3Fv z@v>)bV$UkX^el!VUp#SIvcuO0Ju4;1(>4Xh~~5TWtrcnEdHa0unVh_`;Du@S}? z(PO;t!$wP>=NM+$A}JXpyKBXanZR%w)sBni35q;KqAJIO-uq`CVosJsHDS&U8-fgc z_KDia*yB?tgLBdvz+G8jsRgYlut;^GKYIPU)-`edzkmI^xA!@(4R^wIu5-GI-+f;n z^rqty?7#Ws9#G|d((?81`iS#m-kVM}xqyOM*0}q)zE{`5+Okg=bhUQ1psPpNL0kFgLf5FR z18r@37rLg-9cUX3ohX4er9J*T(0`ka-^j`MJvZMt{GPkR0B8^M{?Hz`%4dCSU7C84 zsy5(@reH-ukAj#MC-{nS*Pmlhtq)xeT}#P&VEkVGJ4V(%&L|EgCu8~fQ1dK>6>6TP zx;Ta5FJe8gk}40CluD{RB~>1dpe0ovehNjlj~u*FgAP{|vr0yG7T+uQ{&AgO;K)v2 zdb>Qbvt;>5dI|q zcJ2N-*85g;l`s!rh^{lb3jkmAtGP?2zr@Li{t(3M!l3F;grwyEv;GPudMpp{wPN>n8zoe zPb!1gSj9~kRD-0(3u?BYpP*7X&0eDtx)GW)3L7uDe%XxvBdEoq_@0T+QSod=_reuV zn56Wxy&l1#DkgPA4D*wZt&n#O7O^V+j~QDbH%X;4D3=3C+0eQoZ?yuo9b3h)yLP%a zv~GECXhqk-Wf|NRgQ5<;gumWE6R_g4^I)asFTj#@iQl#^@q4=tiuSp`O;Z!z8h%yu zNzsGiGsSa?+o)@EO#Y0+ECT4Vcvv$s0C}VSKWzfGA|nv?(ydTq{Rc1LnIz%;l?!-| zgjO!#IZj62lpz#07&hw-EpIUNbd7}ed>IXTnf8(hw0WKNg!ayhfUdj62)bU5Bec&# z50LU1&~LOM=s)WL7|`t*7#Le#CeRiXngbo&B^NrxdJA-Da5i*(jRfd0t2NMJcQ=FK z>eEGL&H)=<8Ui+2{v#NXF&|Msa#G)1>SZtTS6Z#Kn#P;XMR#Lenrj7)j|AGpnb_34 zPGOvy-NlA@`p)g6oO6kDu-%Csuqfv4=isg)QxqlH5oNo7+fcuf>`0Hpx3!rKs8}pW zBRG!T<%P$AM$~$>7^%TvHSblVh$yE$>>~PP#a94<*SK~c&m0FXz9vf$DS;{ zkK)I{B*I@gkoDu*T)fV9(&O`c=Y1E)th*%`x?WCeXrF~0LEp`FLBG*~p#N+$Frd2? z7#QoV$HxcN*MJW0Y6=}H2X08kTepp<i+}o({=v@YO2PB+z*^iZvT#bWp?)_XpiNkJdFT54r08U4hstz`7m+v`bR^5`dbdT$iV z~T z;u6N1Qoc?_Q|||sL;wF4ir?Pn=QGeu!nGK+(i&(seSLH?siIwgqNaq&kl!zV(syN7 z0DtF2N~uOu1)Y8L7^ukk+P-hbASf0MatAlu zh~rx>Yb$nCmA&UDVXdt(Bp1x;Cu8c|XcEKJUDH6qESKFI{^h6Dg07Gvc2wyN)P=4X zcMkhkYJL}5KSs>cE7#iyU8TWB_#5c2f;MzHhVvP|$dn1R84J_kZ=BN+{w5hK;BT6` zMUVXz%ZtY+Kr5CPA2tG7vAlS@F3^hQ#eG7d70Zi%$`-@7CwK2~rHpbW-!1h=6$VJI zJ}c&^eER7rL3A-Z->ou-segQrRLQiFTn=4fclU$g>d7KA{{S0a8VxpDJ`pUg(WvE< z%5O9pQ8AW#sLG)2=zWbw)VjpYMh+6bnzuI_A;Co)5cG;RA=x{-s=BwzhA!-tY`?^C zI=`t8U1;DxXE@!O9u9?7ai~p=+?!HPE&ClUGn#q8u$KT{><6Yli2yTpH0OuW>G(O+<9wihL} zids_Nc^#cNP5#&=iRQ(a=<#v$L%xb*?$hcA7OiDv&I`tVhvdT^6)mF%a6kNGD0rlc zDULtd$O=5>y%X-o?bd)NYKyMO0Tf&M(2B>(zf z+0sS-Xur}FunbfAaqFjWnZ=;zt1a|Ah@2`rl@SDuiVh-gMI{}{Oc4X`q@8k z#_>lw7eFhD4IP%@d~Yi@@b8=|wLK2JF$DWvkIu$q$h)HX55xCt5}@{4fHcXLv_&}jOsd$qbX#PMU&6jm@LLSif6A!z`w4L zOVM44dDW}EAJo@Dy&u#EBCZJYYF%4tOM@~h2N%wv3Ri@w9qHoi=;7$>q^dQkYtXeS zO7JD~hSTFXNWRq=9uo>=*#J8C~0w}xA9k(b}eafhfid8~!AougdxA*%Y1|7JQji{V2Z1Xo*l(ygDA9?TLOhPd^7w6+PD1uB+v`QM>s1 z=Jo2ai?6E#j~3%kx~|SK;&pLDzrAxETvr`GhdL^{F8W2h?n-<;E!T}Z%uISPpeZx` z!3)%X56|j>nR$ZH)`_o_KIyoxP{|)2C%$E#K}ye&-n6~5xA-*OoauSibt?&}{LJ3i ziiC&+Jn1`5J7;o;2)WMt(SR#&h_fIS5o{ z`uIPt6jF4d76BoW>i6_Pg)RDUaAEnWs{5RT6qfkr&<(R)SZeu;R#ge0HT@iy0?5xRBk+2n}7&pAOPvop-%8^ox_NpkMB* z3!T5g8@gbz8Q-2D%oHa$sg22pEH(J09)D5qXCh^m?qrO2tr7JCx=cVGw5IbpXw8Wp z&{}$F&|1q@;&|=cdC)qtB>3x|+9vQ;E^Q=q`He}?6@E{|{(8TSgswR5C+JE;yFu%J z-4(iWn=t4q;q{>nyzQV3?QEfqs)tJ1!B|Th+N6{Xw8?{-aqMn-Q488EyDYT%_FK>v zD{n$qO*#l|`JFkAuQp-@boCYm&{n-N1m0?RT!6N2xE#7>m7UNwwc?;{^3UUoopyg{ zyK~#2Yu${7w%F0VC-xuI|MaJfe!BaKj;vrDbS%I9ic-X?$+b@UP_%l{^mbgcqxvnUwz5o2$ez6GzvA zPMY-uI(?i8v@+V(M09-qbfhKkpLe}G^ox_-pkMB50G+=f47y;k_n$}z2U@+KEnY|IPBw|`uhCR|y=4Nmpf#Pv*Q+@(7+OnDe7#!Bwpr5df_AR>dUa$g z;IDh?pdP!IOPdT`e&Y)03coM6WOu#aCPP;o7YAKw=m2Q_ulqw+Zqov~N_aH1fp;Bf zLpx7sqw3A$1lo+XjG#?QiOGq{1NTIBH@#>Gf3s`@X!GsQpeE18w=8y(R4~ zR2#7cx_ZlU&{n-p>#=(ck9*M84O5|ORyho9Q)@1?P5zxk+D)+2mO|T|I|NB`6ieETqHPUmx3GcG?#;_Ud)Tyu_H?}t zT}RUk+RIEsj}D_~YXa?^cMZDko)^&da`r>}q!=aAZWL|*fcE?4Ikf-miO>Ok&p-#p z{vOBs1vS|U9o%(1bcjdE&_Ha;QqwLd=oTfE7ld_LLM1`iB~wGEi-U^`wIMjT@V|>-@8U-P?p)n;ar1L< zQB@ZU-v;-7>htReTpzKRnX+cpb>_;ZW6;{QYRHwItXBtLkucY^-V43fYA^IUuiv59 ztN#wY!Du}6hTFSky#L0h-#~B5SOmTKWE1GrOV^>dEdTR5@3(d50qAX$PC}>6Ukbhb z+fC3rMoxm>**G40SC<&*-HyYd_e4yT@Ok!@mq72U-4}Y_lmGQ(_w3WN0_a`^Eu)t* zaOnb2``!WxYn{0-pmk4f2g?mV2bSM3AFR+J4b+=C3asci53Dq#2dHlm3s!C&E;7G6 zXy7FVXM<}aB?4_m)x;?_Mti$Ko2ZGw*kn-{wCM#g7@Li;gEmhSgRw;q9q6iwVlcL> ze+RmnR1C(|owZ~FZB}1rf;B4K0<9g@B;+jAEENQ8Q|^+4q+oNghKy-@XSalDx1)@V zS?k~;3Df=$E3o#maiGI^4bX97Z_ugNHPHE+NYJI>e$ch$*D`@Nw_1NdyL&c*_RyXP z?P=fu?fLL`u+EK|ao4u-|kr#H-ZLCR5ayp0Xj`$5n?jN{ z@IbJ|Y5aX%mRfoKF19f>Zc9mYoZ_9PfY&q)mzmtptRYtHUqExnhpIpA3=(#7P{N(69Cgc9b<#MHJLL%QE<=y23 zVOEMzk(+&@(2hsx8@H3%QyRx*K)j5pB`* zSd$uq)w1Tb2m}?({e8c~Vn|Q)Kzn!Efu9Dks^7Vp#Cl3Ug1ZJ83dBSmrv~=j0NQ^8TBPoW>VhcEQ}I9&pKXlB|b(IVU%F`8eim z-v_1&3^0SZdB}wY~i0BBoDpO zN{%?ag&cY75c$!%_iB)C*r{#gsI5OENAIm5$1E);4_{YIj(u_tIj(HENs%pnTsm2A zol8y_eJT#e6HS@q5xvHf4PLe6q#h@k-l+JHjmJEAKKXK#U6C#2jVt7lN4&_Re%L@B zy}gB;TD+4yrlN^#ntHTGE>p%9{fay;VFx+QTtyz=_b24^_}wN+rlkL?gq)$PBxkg^ z#^Jd6`Xchg1M|p_HRX{rYZa!?+Lc0n{IEOEPqHkvE3!@Aw3Fv8<4%*GC|ymS67{JG z=cmq?PtNZ3Avrhc0#7~+^Zo0|`Kx|UF1YmudHTE>@{HG?=JkaOOUW}=XzhPi z_Dr&MuGao%Cz{AlPSo1}oRC73BHQ;OTWaoW2_N9;e06V0RX6+qLnPU2;U>{@2|dYN z(=V$r9k;K>g^z@$ix&|iQ`RvuJsiUqm7jH08-fp1mf8tgnII!*`J7m!A)8yc-2go6>ermz-(4{{m4|wTAa@aHb zO*lSq`n%*onVZRjM{XhyiF$<`9%LgA?KPV`w9QJ6_7AXikk4mqW8YnNWXv$qznSvXZ7Za4NTMe9o}tXtMK{Y~C3B|_Ijimp<>T|mHp^jU z($)s@%k6FCDbF0$f-cB~6r5^BWA)C@v&xWx(11f0 z$S8l$X3FP3vB2c3>JC8|T|sI*Fpxx0(1mX{(={FAwBoC7lR?3a5`ULePHnIK2Ie+x}i2S`C^5rnx@BD8Y;Nsd0F&Wg6O-$e0V-2}?>2hA^b+EDw2fV-k8q=qw&E$h3^8br=`naj8flYonJIR&R zXp6Shu-hAJ@OE-J!md3-q8^9~WJt^-tq3!}*gHlRdj9eI?x1b>V#CPd0hb^YjoRhN zVqtU1`>3S-ZfR!9fzLDUd2Ge#dhos3qlrfF$saE;WA&fB{z6rs6&;s-9yDf25rXZq z7j2WhbY%;yT8qK*MYEL6EWvj(g73~;%J@y{N*#ISIy_sFxlC?~yg%>;gmn8+)za-t z^_HQMi^Y_&N@YkbT@e>qN)t)c!sd=}xx8yO-dXRo?EbKcf=<`X3|6N3M8Uar}hNo5$5Vrh?kan3`F4Z@Ri6z$vJLBnXlO^|6JRjssSnWe{j*@))8 zq^P6ElbIp(yUDV$G}(CzOOXxeBR!VqM%PoWaclEHAZQoe+F7m08Jt@Lf~LYy%g$*b zT!;5U>+sseC{cfj^Q^wCndP*IYm!?*uIQZ)Eq}mSXnVH@DSFAE~NV=?Y~pT_G#d6BQ}r1HCr!C%0LG;!dkjsIn-fHVtbt z_*Wo@XU4Xc$uXs;&(R{vw=YuW_*|eY4A^a7kGa91EVzD@l zDItBTFD5fZiMjurvxIP`jMe4AF`2^Xh;2H_kwYhs_AJ&$RoC7;v=CYMzMu`PeRB5O z!M=yJfweBF#RS$@XR=i#1dK0fA6rPn5(|vRgmHwFv`YuZj>h!xXl17y`JF%hEb7;0 zFkD6Rj;=&i+5EU)-R>{bXBX{Djd7OK?#L5W&X%P8Z#W~yj}zOas>|f|scHqoORLHh zP37W@m~W3W(y|&;jZSKW4*SmASNU~~CLGoCzsC3<;8x?Ne??C`ST2D@q9 z+!kv+{w?1fL6MNUF>ePz_E}sW2HAJOI9I~IfnA|Tpo!swI)q1207&N<@vi?PK zplcdAaC<&E=r7^q;EIvtkUDpAXi*4xz?RG8und(vu>2T#P<(KJBHQ5NOJR^heD9LO zA3I78zt&8S7`B5P(O4ItEfm3Gk^lZE%Kln3(PAU^+F)XV6+VFsBOHD%nk$xvzzsm7 zm8ln}Jq`8U=+l{_&(1c%GObp8z6H|Y?~NC6qn&(c73VGC&jpV$LQ%sk)B&-n*i}q2JH+lBW z68t3z$#VFpsg<}R;&^q{YO9B%${&tgM9BR^uY@7BydPrDYRK;%^|;J>_ Date: Fri, 18 Jul 2025 12:17:28 +0200 Subject: [PATCH 939/999] Fix typo in a comment --- src/datetime/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 80b040d081..c51896cdb8 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1930,10 +1930,10 @@ where } } -/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0. +/// Number of days between January 1, 1970 and December 31, 1 BCE which we define to be day 0. /// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388 /// 1 day until January 1, 1601 1 -/// 369 years until Januari 1, 1970 369 * 365 = 134685 +/// 369 years until January 1, 1970 369 * 365 = 134685 /// of which floor(369 / 4) are leap years floor(369 / 4) = 92 /// except for 1700, 1800 and 1900 -3 + /// -------- From d43108cbfc884b0864d1cf2db7719aedf4adbf23 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 18 Jul 2025 12:18:02 +0200 Subject: [PATCH 940/999] Convert NaiveDate to/from days since unix epoch --- src/datetime/mod.rs | 2 +- src/naive/date/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++-- src/naive/date/tests.rs | 45 +++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index c51896cdb8..ffb0773f58 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1938,4 +1938,4 @@ where /// except for 1700, 1800 and 1900 -3 + /// -------- /// 719163 -const UNIX_EPOCH_DAY: i64 = 719_163; +pub(crate) const UNIX_EPOCH_DAY: i64 = 719_163; diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 78d507b4dc..e6af254304 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -27,6 +27,8 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] use pure_rust_locales::Locale; +use super::internals::{Mdf, YearFlags}; +use crate::datetime::UNIX_EPOCH_DAY; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ @@ -38,8 +40,6 @@ use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; use crate::{Datelike, TimeDelta, Weekday}; use crate::{expect, try_opt}; -use super::internals::{Mdf, YearFlags}; - #[cfg(test)] mod tests; @@ -384,6 +384,35 @@ impl NaiveDate { NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1970 being day 0. + /// + /// # Errors + /// + /// Returns `None` if the date is out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ndays_opt = NaiveDate::from_epoch_days; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ndays_opt(-719_162), Some(from_ymd(1, 1, 1))); + /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1970, 1, 2))); + /// assert_eq!(from_ndays_opt(0), Some(from_ymd(1970, 1, 1))); + /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(1969, 12, 31))); + /// assert_eq!(from_ndays_opt(13036), Some(from_ymd(2005, 9, 10))); + /// assert_eq!(from_ndays_opt(100_000_000), None); + /// assert_eq!(from_ndays_opt(-100_000_000), None); + /// ``` + #[must_use] + pub const fn from_epoch_days(days: i32) -> Option { + let ce_days = try_opt!(days.checked_add(UNIX_EPOCH_DAY as i32)); + NaiveDate::from_num_days_from_ce_opt(ce_days) + } + /// 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)`. @@ -1407,6 +1436,23 @@ impl NaiveDate { ndays + self.ordinal() as i32 } + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1970 as day 0. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ymd(1, 1, 1).to_epoch_days(), -719162); + /// assert_eq!(from_ymd(1970, 1, 1).to_epoch_days(), 0); + /// assert_eq!(from_ymd(2005, 9, 10).to_epoch_days(), 13036); + /// ``` + pub const fn to_epoch_days(&self) -> i32 { + self.num_days_from_ce() - UNIX_EPOCH_DAY as i32 + } + /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. /// /// In a valid value an ordinal is never `0`, and neither are the year flags. This method diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 516e4cc8b1..0b47ca439e 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -301,6 +301,39 @@ fn test_date_from_num_days_from_ce() { assert_eq!(from_ndays_from_ce(i32::MAX), None); } +#[test] +fn test_date_from_epoch_days() { + let from_epoch_days = NaiveDate::from_epoch_days; + assert_eq!(from_epoch_days(-719_162), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_epoch_days(0), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_epoch_days(1), Some(NaiveDate::from_ymd_opt(1970, 1, 2).unwrap())); + assert_eq!(from_epoch_days(2), Some(NaiveDate::from_ymd_opt(1970, 1, 3).unwrap())); + assert_eq!(from_epoch_days(30), Some(NaiveDate::from_ymd_opt(1970, 1, 31).unwrap())); + assert_eq!(from_epoch_days(31), Some(NaiveDate::from_ymd_opt(1970, 2, 1).unwrap())); + assert_eq!(from_epoch_days(58), Some(NaiveDate::from_ymd_opt(1970, 2, 28).unwrap())); + assert_eq!(from_epoch_days(59), Some(NaiveDate::from_ymd_opt(1970, 3, 1).unwrap())); + assert_eq!(from_epoch_days(364), Some(NaiveDate::from_ymd_opt(1970, 12, 31).unwrap())); + assert_eq!(from_epoch_days(365), Some(NaiveDate::from_ymd_opt(1971, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 2), Some(NaiveDate::from_ymd_opt(1972, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(1973, 1, 1).unwrap())); + assert_eq!(from_epoch_days(365 * 4 + 1), Some(NaiveDate::from_ymd_opt(1974, 1, 1).unwrap())); + assert_eq!(from_epoch_days(13036), Some(NaiveDate::from_ymd_opt(2005, 9, 10).unwrap())); + assert_eq!(from_epoch_days(-365), Some(NaiveDate::from_ymd_opt(1969, 1, 1).unwrap())); + assert_eq!(from_epoch_days(-366), Some(NaiveDate::from_ymd_opt(1968, 12, 31).unwrap())); + + for days in (-9999..10001).map(|x| x * 100) { + assert_eq!(from_epoch_days(days).map(|d| d.to_epoch_days()), Some(days)); + } + + assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days()), Some(NaiveDate::MIN)); + assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days() - 1), None); + assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days()), Some(NaiveDate::MAX)); + assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days() + 1), None); + + assert_eq!(from_epoch_days(i32::MIN), None); + assert_eq!(from_epoch_days(i32::MAX), None); +} + #[test] fn test_date_from_weekday_of_month_opt() { let ymwd = NaiveDate::from_weekday_of_month_opt; @@ -423,6 +456,18 @@ fn test_date_num_days_from_ce() { } } +#[test] +fn test_date_to_epoch_days() { + assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().to_epoch_days(), 0); + + for year in -9999..10001 { + assert_eq!( + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().to_epoch_days(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().to_epoch_days() + 1 + ); + } +} + #[test] fn test_date_succ() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); From b4b9aa580cc61354d5a1a44d7eb881f29ef39fc3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 8 Aug 2025 16:40:33 +0200 Subject: [PATCH 941/999] Apply suggestions from clippy 1.89 --- src/offset/local/mod.rs | 2 +- src/offset/local/tz_info/timezone.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 2b49e692ac..ee685715f5 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -219,7 +219,7 @@ impl Transition { #[cfg(windows)] impl PartialOrd for Transition { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.transition_utc.cmp(&other.transition_utc)) + Some(self.cmp(other)) } } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 6fa4ce1f21..77497994ae 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -134,7 +134,7 @@ impl TimeZone { } /// Returns a reference to the time zone - fn as_ref(&self) -> TimeZoneRef { + fn as_ref(&self) -> TimeZoneRef<'_> { TimeZoneRef { transitions: &self.transitions, local_time_types: &self.local_time_types, From 59d5f46f17f257a583d5fd95ca1a9e541945c201 Mon Sep 17 00:00:00 2001 From: Huliiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:09:33 +0800 Subject: [PATCH 942/999] Add `?Sized` bound to related methods of `DelayedFormat::write_to` --- src/format/formatting.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 3b37a15f73..a86d321535 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -109,7 +109,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// let mut buffer = String::new(); /// let _ = df.write_to(&mut buffer); /// ``` - pub fn write_to(&self, w: &mut impl Write) -> fmt::Result { + pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> fmt::Result { for item in self.items.clone() { match *item.borrow() { Item::Literal(s) | Item::Space(s) => w.write_str(s), @@ -124,14 +124,19 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[cfg(feature = "alloc")] - fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { + fn format_numeric( + &self, + w: &mut (impl Write + ?Sized), + spec: &Numeric, + pad: Pad, + ) -> fmt::Result { use self::Numeric::*; - fn write_one(w: &mut impl Write, v: u8) -> fmt::Result { + fn write_one(w: &mut (impl Write + ?Sized), v: u8) -> fmt::Result { w.write_char((b'0' + v) as char) } - fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result { + fn write_two(w: &mut (impl Write + ?Sized), v: u8, pad: Pad) -> fmt::Result { let ones = b'0' + v % 10; match (v / 10, pad) { (0, Pad::None) => {} @@ -142,7 +147,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[inline] - fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result { + fn write_year(w: &mut (impl Write + ?Sized), year: i32, pad: Pad) -> fmt::Result { if (1000..=9999).contains(&year) { // fast path write_hundreds(w, (year / 100) as u8)?; @@ -153,7 +158,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } fn write_n( - w: &mut impl Write, + w: &mut (impl Write + ?Sized), n: usize, v: i64, pad: Pad, @@ -214,7 +219,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } #[cfg(feature = "alloc")] - fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { + fn format_fixed(&self, w: &mut (impl Write + ?Sized), spec: &Fixed) -> fmt::Result { use Fixed::*; use InternalInternal::*; @@ -387,7 +392,7 @@ pub fn format_item( #[cfg(any(feature = "alloc", feature = "serde"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. - fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { + fn format(&self, w: &mut (impl Write + ?Sized), off: FixedOffset) -> fmt::Result { let off = off.local_minus_utc(); if self.allow_zulu && off == 0 { w.write_char('Z')?; @@ -496,7 +501,7 @@ pub enum SecondsFormat { #[inline] #[cfg(any(feature = "alloc", feature = "serde"))] pub(crate) fn write_rfc3339( - w: &mut impl Write, + w: &mut (impl Write + ?Sized), dt: NaiveDateTime, off: FixedOffset, secform: SecondsFormat, @@ -560,7 +565,7 @@ pub(crate) fn write_rfc3339( #[cfg(feature = "alloc")] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( - w: &mut impl Write, + w: &mut (impl Write + ?Sized), dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { @@ -605,7 +610,7 @@ pub(crate) fn write_rfc2822( } /// Equivalent to `{:02}` formatting for n < 100. -pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { +pub(crate) fn write_hundreds(w: &mut (impl Write + ?Sized), n: u8) -> fmt::Result { if n >= 100 { return Err(fmt::Error); } From 6aeea3ff5cb5eaac4d1df4f43caaae56ded2f8c5 Mon Sep 17 00:00:00 2001 From: Jason Owen Date: Fri, 8 Aug 2025 16:51:43 -0700 Subject: [PATCH 943/999] Add `from_timestamp_secs` method to `DateTime` Add a new single-argument function for creating a `DateTime` from a Unix timestamp, to match `from_timestamp_micros`, `from_timestamp_millis`, and `from_timestamp_nanos`. Refactor existing code and examples to use `DateTime::from_timestamp_secs` and `DateTime::from_timestamp_millis` where possible. Issue #1716 Suggestion: add DateTime::from_timestamp_secs function --- src/datetime/mod.rs | 55 +++++++++++++++++++++++++++++++++++++ src/datetime/serde.rs | 16 +++-------- src/datetime/tests.rs | 35 +++++++++++++++-------- src/format/parsed.rs | 2 +- src/lib.rs | 2 +- src/naive/datetime/serde.rs | 8 +++--- 6 files changed, 89 insertions(+), 29 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ffb0773f58..cbfa921979 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -713,6 +713,61 @@ impl DateTime { } impl DateTime { + /// Makes a new `DateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). + /// + /// This is a convenience wrapper around [`DateTime::from_timestamp`], + /// which is useful in functions like [`Iterator::map`] to avoid a closure. + /// + /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]; if you need to create a + /// `DateTime` with more precision, use [`DateTime::from_timestamp_micros`], + /// [`DateTime::from_timestamp_millis`], or [`DateTime::from_timestamp_nanos`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds, + /// otherwise returns `Some(DateTime {...})`. + /// + /// # Examples + /// + /// Using [`Option::and_then`]: + /// + /// ``` + /// # use chrono::DateTime; + /// let maybe_timestamp: Option = Some(1431648000); + /// let maybe_dt = maybe_timestamp.and_then(DateTime::from_timestamp_secs); + /// + /// assert!(maybe_dt.is_some()); + /// assert_eq!(maybe_dt.unwrap().to_string(), "2015-05-15 00:00:00 UTC"); + /// ``` + /// + /// Using [`Iterator::map`]: + /// + /// ``` + /// # use chrono::{DateTime, Utc}; + /// let v = vec![i64::MIN, 1_000_000_000, 1_234_567_890, i64::MAX]; + /// let timestamps: Vec>> = v + /// .into_iter() + /// .map(DateTime::from_timestamp_secs) + /// .collect(); + /// + /// assert_eq!(vec![ + /// None, + /// Some(DateTime::parse_from_rfc3339("2001-09-09 01:46:40Z").unwrap().to_utc()), + /// Some(DateTime::parse_from_rfc3339("2009-02-13 23:31:30Z").unwrap().to_utc()), + /// None, + /// ], timestamps); + /// ``` + /// + #[inline] + #[must_use] + pub const fn from_timestamp_secs(secs: i64) -> Option { + Self::from_timestamp(secs, 0) + } + /// Makes a new `DateTime` from the number of non-leap seconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 1492fe3f0b..bc00d674d0 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -242,11 +242,7 @@ pub mod ts_nanoseconds { where E: de::Error, { - DateTime::from_timestamp( - value.div_euclid(1_000_000_000), - (value.rem_euclid(1_000_000_000)) as u32, - ) - .ok_or_else(|| invalid_ts(value)) + Ok(DateTime::from_timestamp_nanos(value)) } /// Deserialize a timestamp in nanoseconds since the epoch @@ -524,11 +520,7 @@ pub mod ts_microseconds { where E: de::Error, { - DateTime::from_timestamp( - value.div_euclid(1_000_000), - (value.rem_euclid(1_000_000) * 1000) as u32, - ) - .ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in milliseconds since the epoch @@ -1064,7 +1056,7 @@ pub mod ts_seconds { where E: de::Error, { - DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp_secs(value).ok_or_else(|| invalid_ts(value)) } /// Deserialize a timestamp in seconds since the epoch @@ -1075,7 +1067,7 @@ pub mod ts_seconds { if value > i64::MAX as u64 { Err(invalid_ts(value)) } else { - DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value)) + DateTime::from_timestamp_secs(value as i64).ok_or_else(|| invalid_ts(value)) } } } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b443ec028e..67bee1049e 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -154,7 +154,10 @@ fn test_datetime_from_timestamp_millis() { // that of `from_timestamp_opt`. let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; for secs in secs_test.iter().cloned() { - assert_eq!(DateTime::from_timestamp_millis(secs * 1000), DateTime::from_timestamp(secs, 0)); + assert_eq!( + DateTime::from_timestamp_millis(secs * 1000), + DateTime::from_timestamp_secs(secs) + ); } } @@ -191,7 +194,7 @@ fn test_datetime_from_timestamp_micros() { for secs in secs_test.iter().copied() { assert_eq!( DateTime::from_timestamp_micros(secs * 1_000_000), - DateTime::from_timestamp(secs, 0) + DateTime::from_timestamp_secs(secs) ); } } @@ -242,24 +245,34 @@ fn test_datetime_from_timestamp_nanos() { for secs in secs_test.iter().copied() { assert_eq!( Some(DateTime::from_timestamp_nanos(secs * 1_000_000_000)), - DateTime::from_timestamp(secs, 0) + DateTime::from_timestamp_secs(secs) ); } } +#[test] +fn test_datetime_from_timestamp_secs() { + let valid = [-2208936075, 0, 119731017, 1234567890, 2034061609]; + + for timestamp_secs in valid.iter().copied() { + let datetime = DateTime::from_timestamp_secs(timestamp_secs).unwrap(); + assert_eq!(timestamp_secs, datetime.timestamp()); + assert_eq!(DateTime::from_timestamp(timestamp_secs, 0).unwrap(), datetime); + } +} + #[test] fn test_datetime_from_timestamp() { - let from_timestamp = |secs| DateTime::from_timestamp(secs, 0); let ymdhms = |y, m, d, h, n, s| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().and_utc() }; - assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); - assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); - assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); - assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); - assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); - assert_eq!(from_timestamp(i64::MIN), None); - assert_eq!(from_timestamp(i64::MAX), None); + assert_eq!(DateTime::from_timestamp_secs(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(DateTime::from_timestamp_secs(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(DateTime::from_timestamp_secs(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(DateTime::from_timestamp_secs(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(DateTime::from_timestamp_secs(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(DateTime::from_timestamp_secs(i64::MIN), None); + assert_eq!(DateTime::from_timestamp_secs(i64::MAX), None); } #[test] diff --git a/src/format/parsed.rs b/src/format/parsed.rs index fd5008f159..8524daaa7d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -832,7 +832,7 @@ impl Parsed { // reconstruct date and time fields from timestamp let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; - let mut datetime = DateTime::from_timestamp(ts, 0).ok_or(OUT_OF_RANGE)?.naive_utc(); + let mut datetime = DateTime::from_timestamp_secs(ts).ok_or(OUT_OF_RANGE)?.naive_utc(); // fill year, ordinal, hour, minute and second fields from timestamp. // if existing fields are consistent, this will allow the full date/time reconstruction. diff --git a/src/lib.rs b/src/lib.rs index 2beeb40a14..301d467241 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -380,7 +380,7 @@ //! use chrono::{DateTime, Utc}; //! //! // Construct a datetime from epoch: -//! let dt: DateTime = DateTime::from_timestamp(1_500_000_000, 0).unwrap(); +//! let dt: DateTime = DateTime::from_timestamp_secs(1_500_000_000).unwrap(); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 85fcb94f29..6ebdefaee5 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -955,7 +955,7 @@ pub mod ts_seconds { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc(); /// assert_eq!(my_s, S { time: expected }); /// # Ok::<(), serde_json::Error>(()) /// ``` @@ -979,7 +979,7 @@ pub mod ts_seconds { where E: de::Error, { - DateTime::from_timestamp(value, 0) + DateTime::from_timestamp_secs(value) .map(|dt| dt.naive_utc()) .ok_or_else(|| invalid_ts(value)) } @@ -991,7 +991,7 @@ pub mod ts_seconds { if value > i64::MAX as u64 { Err(invalid_ts(value)) } else { - DateTime::from_timestamp(value as i64, 0) + DateTime::from_timestamp_secs(value as i64) .map(|dt| dt.naive_utc()) .ok_or_else(|| invalid_ts(value)) } @@ -1080,7 +1080,7 @@ pub mod ts_seconds_option { /// } /// /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc(); /// assert_eq!(my_s, S { time: Some(expected) }); /// # Ok::<(), serde_json::Error>(()) /// ``` From 0f22fbda3c08f815cbbf69ceda98cbfb81df30f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 06:28:45 +0000 Subject: [PATCH 944/999] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 10 +++++----- .github/workflows/test.yml | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index b6a98e4a12..eb6d0938d7 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,7 +17,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # nightly is required for --doctests, see cargo-llvm-cov#2 - name: Install Rust (nightly) run: rustup update nightly diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f6f9a62719..05299e6703 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc @@ -44,20 +44,20 @@ jobs: container: image: tamasfe/taplo:0.8.0 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: taplo lint - run: taplo fmt --check --diff cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: EmbarkStudios/cargo-deny-action@v2 check-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} @@ -68,7 +68,7 @@ jobs: cffconvert: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - uses: citation-file-format/cffconvert-github-action@2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 88ec503cf7..55b6573a2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always @@ -31,7 +31,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: "1.62.0" @@ -48,7 +48,7 @@ jobs: rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} @@ -66,7 +66,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 @@ -93,7 +93,7 @@ jobs: target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -113,7 +113,7 @@ jobs: ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -126,7 +126,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown @@ -144,7 +144,7 @@ jobs: target: [wasm32-wasip1] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -159,7 +159,7 @@ jobs: - x86_64-unknown-illumos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} @@ -170,7 +170,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always @@ -181,7 +181,7 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: From 0813b2c994a103a5c6ac7600b028ac815540575d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 04:05:16 +0000 Subject: [PATCH 945/999] Bump actions/setup-node from 4 to 5 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55b6573a2c..aba4c032be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,7 +131,7 @@ jobs: with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 - uses: jetli/wasm-pack-action@v0.4.0 # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment # with the host system. From 78c0fbcf181d3256c2c240a465feb8fd6c950451 Mon Sep 17 00:00:00 2001 From: Benjamin Brittain Date: Mon, 19 May 2025 22:51:42 -0400 Subject: [PATCH 946/999] Migrate to core::error::Error Stabilized in rust 1.81, behind a feature flag so as to not bump the current MSRV of 1.61. --- Cargo.toml | 1 + src/format/mod.rs | 4 +++- src/lib.rs | 5 ++++- src/month.rs | 3 +++ src/round.rs | 14 +++++++++++++- src/time_delta.rs | 4 +++- src/weekday.rs | 3 +++ 7 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0fb5306d16..7147d38e66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ winapi = ["windows-link"] std = ["alloc"] clock = ["winapi", "iana-time-zone", "now"] now = ["std"] +core-error = [] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] diff --git a/src/format/mod.rs b/src/format/mod.rs index 241be7a1d9..a0aa94b78b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -33,6 +33,8 @@ #[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::boxed::Box; +#[cfg(all(feature = "core-error", not(feature = "std")))] +use core::error::Error; use core::fmt; use core::str::FromStr; #[cfg(feature = "std")] @@ -450,7 +452,7 @@ impl fmt::Display for ParseError { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "core-error", feature = "std"))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/lib.rs b/src/lib.rs index 301d467241..32a1b82f8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -512,8 +512,8 @@ extern crate alloc; mod time_delta; -#[cfg(feature = "std")] #[doc(no_inline)] +#[cfg(any(feature = "std", feature = "core-error"))] pub use time_delta::OutOfRangeError; pub use time_delta::TimeDelta; @@ -690,6 +690,9 @@ impl fmt::Debug for OutOfRange { #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for OutOfRange {} + /// Workaround because `?` is not (yet) available in const context. #[macro_export] #[doc(hidden)] diff --git a/src/month.rs b/src/month.rs index 1b4dbfd7b8..3ba9b6b2dd 100644 --- a/src/month.rs +++ b/src/month.rs @@ -272,6 +272,9 @@ pub struct ParseMonthError { #[cfg(feature = "std")] impl std::error::Error for ParseMonthError {} +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for ParseMonthError {} + impl fmt::Display for ParseMonthError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ParseMonthError {{ .. }}") diff --git a/src/round.rs b/src/round.rs index f5ca2d0634..9f575a6896 100644 --- a/src/round.rs +++ b/src/round.rs @@ -109,7 +109,11 @@ pub trait DurationRound: Sized { type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(feature = "std"))] + #[cfg(all(not(feature = "std"), feature = "core-error"))] + type Err: core::error::Error; + + /// Error that can occur in rounding or truncating + #[cfg(all(not(feature = "std"), not(feature = "core-error")))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by TimeDelta. @@ -362,6 +366,14 @@ impl std::error::Error for RoundingError { } } +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for RoundingError { + #[allow(deprecated)] + fn description(&self) -> &str { + "error from rounding or truncating with DurationRound" + } +} + #[cfg(test)] mod tests { use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; diff --git a/src/time_delta.rs b/src/time_delta.rs index 34147707f3..3ed81e5eed 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -10,6 +10,8 @@ //! Temporal quantification +#[cfg(all(not(feature = "std"), feature = "core-error"))] +use core::error::Error; use core::fmt; use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration; @@ -630,7 +632,7 @@ impl fmt::Display for OutOfRangeError { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "core-error"))] impl Error for OutOfRangeError { #[allow(deprecated)] fn description(&self) -> &str { diff --git a/src/weekday.rs b/src/weekday.rs index 1554ca78b8..2a4b2ea2bd 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -238,6 +238,9 @@ pub struct ParseWeekdayError { pub(crate) _dummy: (), } +#[cfg(all(not(feature = "std"), feature = "core-error"))] +impl core::error::Error for ParseWeekdayError {} + #[cfg(feature = "std")] impl std::error::Error for ParseWeekdayError {} From 757a8b0226e9c0a23b5334c7dc0986f1e487b8ac Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:27:52 +0200 Subject: [PATCH 947/999] Upgrade to windows-bindgen 0.63 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7147d38e66..4a093cf58e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,10 +50,10 @@ wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] -windows-link = { version = "0.1", optional = true } +windows-link = { version = "0.2", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.62" } # MSRV is 1.74 +windows-bindgen = { version = "0.63" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } From 0ee2172fb92df4e0be4cf7a733c860226eec37a0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:46:39 +0200 Subject: [PATCH 948/999] strftime: re-order items to keep impls together --- src/format/strftime.rs | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 1449eee5a3..8483e4f11e 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -427,33 +427,6 @@ impl<'a> StrftimeItems<'a> { } } -const HAVE_ALTERNATES: &str = "z"; - -impl<'a> Iterator for StrftimeItems<'a> { - type Item = Item<'a>; - - fn next(&mut self) -> Option> { - // We have items queued to return from a specifier composed of multiple formatting items. - if let Some((item, remainder)) = self.queue.split_first() { - self.queue = remainder; - return Some(item.clone()); - } - - // We are in the middle of parsing the localized formatting string of a specifier. - #[cfg(feature = "unstable-locales")] - if !self.locale_str.is_empty() { - let (remainder, item) = self.parse_next_item(self.locale_str)?; - self.locale_str = remainder; - return Some(item); - } - - // Normal: we are parsing the formatting string. - let (remainder, item) = self.parse_next_item(self.remainder)?; - self.remainder = remainder; - Some(item) - } -} - impl<'a> StrftimeItems<'a> { fn error<'b>( &mut self, @@ -779,6 +752,33 @@ impl<'a> StrftimeItems<'a> { } } +impl<'a> Iterator for StrftimeItems<'a> { + type Item = Item<'a>; + + fn next(&mut self) -> Option> { + // We have items queued to return from a specifier composed of multiple formatting items. + if let Some((item, remainder)) = self.queue.split_first() { + self.queue = remainder; + return Some(item.clone()); + } + + // We are in the middle of parsing the localized formatting string of a specifier. + #[cfg(feature = "unstable-locales")] + if !self.locale_str.is_empty() { + let (remainder, item) = self.parse_next_item(self.locale_str)?; + self.locale_str = remainder; + return Some(item); + } + + // Normal: we are parsing the formatting string. + let (remainder, item) = self.parse_next_item(self.remainder)?; + self.remainder = remainder; + Some(item) + } +} + +const HAVE_ALTERNATES: &str = "z"; + #[cfg(test)] mod tests { use super::StrftimeItems; From d516c2764d74e467fa37e25e8e88d5633478bafa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:47:36 +0200 Subject: [PATCH 949/999] strftime: merge impl blocks --- src/format/strftime.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 8483e4f11e..1f2e3ee338 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -425,9 +425,7 @@ impl<'a> StrftimeItems<'a> { }) .collect() } -} -impl<'a> StrftimeItems<'a> { fn error<'b>( &mut self, original: &'b str, From e5f6be7db4a0c2756cad0a7c8186a5a180873f7a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:48:01 +0200 Subject: [PATCH 950/999] strftime: move error() method below caller --- src/format/strftime.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 1f2e3ee338..660abb6f1b 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -426,22 +426,6 @@ impl<'a> StrftimeItems<'a> { .collect() } - fn error<'b>( - &mut self, - original: &'b str, - error_len: &mut usize, - ch: Option, - ) -> (&'b str, Item<'b>) { - if !self.lenient { - return (&original[*error_len..], Item::Error); - } - - if let Some(c) = ch { - *error_len -= c.len_utf8(); - } - (&original[*error_len..], Item::Literal(&original[..*error_len])) - } - fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { use InternalInternal::*; use Item::{Literal, Space}; @@ -732,6 +716,22 @@ impl<'a> StrftimeItems<'a> { } } + fn error<'b>( + &mut self, + original: &'b str, + error_len: &mut usize, + ch: Option, + ) -> (&'b str, Item<'b>) { + if !self.lenient { + return (&original[*error_len..], Item::Error); + } + + if let Some(c) = ch { + *error_len -= c.len_utf8(); + } + (&original[*error_len..], Item::Literal(&original[..*error_len])) + } + #[cfg(feature = "unstable-locales")] fn switch_to_locale_str( &mut self, From 9d5dfe164098186bfff8af7d5eecbb05d7712d4d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:51:07 +0200 Subject: [PATCH 951/999] strftime: outline constants --- src/format/strftime.rs | 68 ++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 660abb6f1b..ab863bd861 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -431,35 +431,6 @@ impl<'a> StrftimeItems<'a> { use Item::{Literal, Space}; use Numeric::*; - static D_FMT: &[Item<'static>] = - &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]; - static D_T_FMT: &[Item<'static>] = &[ - fixed(Fixed::ShortWeekdayName), - Space(" "), - fixed(Fixed::ShortMonthName), - Space(" "), - nums(Day), - Space(" "), - num0(Hour), - Literal(":"), - num0(Minute), - Literal(":"), - num0(Second), - Space(" "), - num0(Year), - ]; - static T_FMT: &[Item<'static>] = - &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; - static T_FMT_AMPM: &[Item<'static>] = &[ - num0(Hour12), - Literal(":"), - num0(Minute), - Literal(":"), - num0(Second), - Space(" "), - fixed(Fixed::UpperAmPm), - ]; - match remainder.chars().next() { // we are done None => None, @@ -775,6 +746,45 @@ impl<'a> Iterator for StrftimeItems<'a> { } } +static D_FMT: &[Item<'static>] = &[ + num0(Numeric::Month), + Item::Literal("/"), + num0(Numeric::Day), + Item::Literal("/"), + num0(Numeric::YearMod100), +]; +static D_T_FMT: &[Item<'static>] = &[ + fixed(Fixed::ShortWeekdayName), + Item::Space(" "), + fixed(Fixed::ShortMonthName), + Item::Space(" "), + nums(Numeric::Day), + Item::Space(" "), + num0(Numeric::Hour), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), + Item::Space(" "), + num0(Numeric::Year), +]; +static T_FMT: &[Item<'static>] = &[ + num0(Numeric::Hour), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), +]; +static T_FMT_AMPM: &[Item<'static>] = &[ + num0(Numeric::Hour12), + Item::Literal(":"), + num0(Numeric::Minute), + Item::Literal(":"), + num0(Numeric::Second), + Item::Space(" "), + fixed(Fixed::UpperAmPm), +]; + const HAVE_ALTERNATES: &str = "z"; #[cfg(test)] From 7f413c363b74e293d495dc6109335c02dda9d629 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:52:31 +0200 Subject: [PATCH 952/999] strftime: yield None early --- src/format/strftime.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index ab863bd861..9643d70bc5 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -431,12 +431,9 @@ impl<'a> StrftimeItems<'a> { use Item::{Literal, Space}; use Numeric::*; - match remainder.chars().next() { - // we are done - None => None, - + match remainder.chars().next()? { // the next item is a specifier - Some('%') => { + '%' => { let original = remainder; remainder = &remainder[1..]; let mut error_len = 0; @@ -664,7 +661,7 @@ impl<'a> StrftimeItems<'a> { } // the next item is space - Some(c) if c.is_whitespace() => { + c if c.is_whitespace() => { // `%` is not a whitespace, so `c != '%'` is redundant let nextspec = remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); From 36fbfb1221da05b1482eb9dd18254e6bd736cb31 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 09:58:28 +0200 Subject: [PATCH 953/999] strftime: move specifier handling out of match to reduce rightward drift --- src/format/strftime.rs | 456 ++++++++++++++++++++--------------------- 1 file changed, 227 insertions(+), 229 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 9643d70bc5..0692fe0166 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -431,234 +431,9 @@ impl<'a> StrftimeItems<'a> { use Item::{Literal, Space}; use Numeric::*; - match remainder.chars().next()? { + let (original, mut remainder) = match remainder.chars().next()? { // the next item is a specifier - '%' => { - let original = remainder; - remainder = &remainder[1..]; - let mut error_len = 0; - if self.lenient { - error_len += 1; - } - - macro_rules! next { - () => { - match remainder.chars().next() { - Some(x) => { - remainder = &remainder[x.len_utf8()..]; - if self.lenient { - error_len += x.len_utf8(); - } - x - } - None => return Some(self.error(original, &mut error_len, None)), // premature end of string - } - }; - } - - let spec = next!(); - let pad_override = match spec { - '-' => Some(Pad::None), - '0' => Some(Pad::Zero), - '_' => Some(Pad::Space), - _ => None, - }; - let is_alternate = spec == '#'; - let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; - if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(self.error(original, &mut error_len, Some(spec))); - } - - macro_rules! queue { - [$head:expr, $($tail:expr),+ $(,)*] => ({ - const QUEUE: &'static [Item<'static>] = &[$($tail),+]; - self.queue = QUEUE; - $head - }) - } - #[cfg(not(feature = "unstable-locales"))] - macro_rules! queue_from_slice { - ($slice:expr) => {{ - self.queue = &$slice[1..]; - $slice[0].clone() - }}; - } - - let item = match spec { - 'A' => fixed(Fixed::LongWeekdayName), - 'B' => fixed(Fixed::LongMonthName), - 'C' => num0(YearDiv100), - 'D' => { - queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] - } - 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], - 'G' => num0(IsoYear), - 'H' => num0(Hour), - 'I' => num0(Hour12), - 'M' => num0(Minute), - 'P' => fixed(Fixed::LowerAmPm), - 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], - 'S' => num0(Second), - 'T' => { - queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] - } - 'U' => num0(WeekFromSun), - 'V' => num0(IsoWeek), - 'W' => num0(WeekFromMon), - #[cfg(not(feature = "unstable-locales"))] - 'X' => queue_from_slice!(T_FMT), - #[cfg(feature = "unstable-locales")] - 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), - 'Y' => num0(Year), - 'Z' => fixed(Fixed::TimezoneName), - 'a' => fixed(Fixed::ShortWeekdayName), - 'b' | 'h' => fixed(Fixed::ShortMonthName), - #[cfg(not(feature = "unstable-locales"))] - 'c' => queue_from_slice!(D_T_FMT), - #[cfg(feature = "unstable-locales")] - 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), - 'd' => num0(Day), - 'e' => nums(Day), - 'f' => num0(Nanosecond), - 'g' => num0(IsoYearMod100), - 'j' => num0(Ordinal), - 'k' => nums(Hour), - 'l' => nums(Hour12), - 'm' => num0(Month), - 'n' => Space("\n"), - 'p' => fixed(Fixed::UpperAmPm), - 'q' => num(Quarter), - #[cfg(not(feature = "unstable-locales"))] - 'r' => queue_from_slice!(T_FMT_AMPM), - #[cfg(feature = "unstable-locales")] - 'r' => { - if self.locale.is_some() - && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() - { - // 12-hour clock not supported by this locale. Switch to 24-hour format. - self.switch_to_locale_str(locales::t_fmt, T_FMT) - } else { - self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) - } - } - 's' => num(Timestamp), - 't' => Space("\t"), - 'u' => num(WeekdayFromMon), - 'v' => { - queue![ - nums(Day), - Literal("-"), - fixed(Fixed::ShortMonthName), - Literal("-"), - num0(Year) - ] - } - 'w' => num(NumDaysFromSun), - #[cfg(not(feature = "unstable-locales"))] - 'x' => queue_from_slice!(D_FMT), - #[cfg(feature = "unstable-locales")] - 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), - 'y' => num0(YearMod100), - 'z' => { - if is_alternate { - internal_fixed(TimezoneOffsetPermissive) - } else { - fixed(Fixed::TimezoneOffset) - } - } - '+' => fixed(Fixed::RFC3339), - ':' => { - if remainder.starts_with("::z") { - remainder = &remainder[3..]; - fixed(Fixed::TimezoneOffsetTripleColon) - } else if remainder.starts_with(":z") { - remainder = &remainder[2..]; - fixed(Fixed::TimezoneOffsetDoubleColon) - } else if remainder.starts_with('z') { - remainder = &remainder[1..]; - fixed(Fixed::TimezoneOffsetColon) - } else { - self.error(original, &mut error_len, None).1 - } - } - '.' => match next!() { - '3' => match next!() { - 'f' => fixed(Fixed::Nanosecond3), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '6' => match next!() { - 'f' => fixed(Fixed::Nanosecond6), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '9' => match next!() { - 'f' => fixed(Fixed::Nanosecond9), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - 'f' => fixed(Fixed::Nanosecond), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '3' => match next!() { - 'f' => internal_fixed(Nanosecond3NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '6' => match next!() { - 'f' => internal_fixed(Nanosecond6NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '9' => match next!() { - 'f' => internal_fixed(Nanosecond9NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }, - '%' => Literal("%"), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } - }; - - // Adjust `item` if we have any padding modifier. - // Not allowed on non-numeric items or on specifiers composed out of multiple - // formatting items. - if let Some(new_pad) = pad_override { - match item { - Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { - Some((remainder, Item::Numeric(kind.clone(), new_pad))) - } - _ => Some(self.error(original, &mut error_len, None)), - } - } else { - Some((remainder, item)) - } - } + '%' => (remainder, &remainder[1..]), // the next item is space c if c.is_whitespace() => { @@ -668,7 +443,7 @@ impl<'a> StrftimeItems<'a> { assert!(nextspec > 0); let item = Space(&remainder[..nextspec]); remainder = &remainder[nextspec..]; - Some((remainder, item)) + return Some((remainder, item)); } // the next item is literal @@ -679,8 +454,231 @@ impl<'a> StrftimeItems<'a> { assert!(nextspec > 0); let item = Literal(&remainder[..nextspec]); remainder = &remainder[nextspec..]; - Some((remainder, item)) + return Some((remainder, item)); } + }; + + let mut error_len = 0; + if self.lenient { + error_len += 1; + } + + macro_rules! next { + () => { + match remainder.chars().next() { + Some(x) => { + remainder = &remainder[x.len_utf8()..]; + if self.lenient { + error_len += x.len_utf8(); + } + x + } + None => return Some(self.error(original, &mut error_len, None)), // premature end of string + } + }; + } + + let spec = next!(); + let pad_override = match spec { + '-' => Some(Pad::None), + '0' => Some(Pad::Zero), + '_' => Some(Pad::Space), + _ => None, + }; + + let is_alternate = spec == '#'; + let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; + if is_alternate && !HAVE_ALTERNATES.contains(spec) { + return Some(self.error(original, &mut error_len, Some(spec))); + } + + macro_rules! queue { + [$head:expr, $($tail:expr),+ $(,)*] => ({ + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; + $head + }) + } + + #[cfg(not(feature = "unstable-locales"))] + macro_rules! queue_from_slice { + ($slice:expr) => {{ + self.queue = &$slice[1..]; + $slice[0].clone() + }}; + } + + let item = match spec { + 'A' => fixed(Fixed::LongWeekdayName), + 'B' => fixed(Fixed::LongMonthName), + 'C' => num0(YearDiv100), + 'D' => { + queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] + } + 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], + 'G' => num0(IsoYear), + 'H' => num0(Hour), + 'I' => num0(Hour12), + 'M' => num0(Minute), + 'P' => fixed(Fixed::LowerAmPm), + 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], + 'S' => num0(Second), + 'T' => { + queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] + } + 'U' => num0(WeekFromSun), + 'V' => num0(IsoWeek), + 'W' => num0(WeekFromMon), + #[cfg(not(feature = "unstable-locales"))] + 'X' => queue_from_slice!(T_FMT), + #[cfg(feature = "unstable-locales")] + 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), + 'Y' => num0(Year), + 'Z' => fixed(Fixed::TimezoneName), + 'a' => fixed(Fixed::ShortWeekdayName), + 'b' | 'h' => fixed(Fixed::ShortMonthName), + #[cfg(not(feature = "unstable-locales"))] + 'c' => queue_from_slice!(D_T_FMT), + #[cfg(feature = "unstable-locales")] + 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), + 'd' => num0(Day), + 'e' => nums(Day), + 'f' => num0(Nanosecond), + 'g' => num0(IsoYearMod100), + 'j' => num0(Ordinal), + 'k' => nums(Hour), + 'l' => nums(Hour12), + 'm' => num0(Month), + 'n' => Space("\n"), + 'p' => fixed(Fixed::UpperAmPm), + 'q' => num(Quarter), + #[cfg(not(feature = "unstable-locales"))] + 'r' => queue_from_slice!(T_FMT_AMPM), + #[cfg(feature = "unstable-locales")] + 'r' => { + if self.locale.is_some() && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() { + // 12-hour clock not supported by this locale. Switch to 24-hour format. + self.switch_to_locale_str(locales::t_fmt, T_FMT) + } else { + self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) + } + } + 's' => num(Timestamp), + 't' => Space("\t"), + 'u' => num(WeekdayFromMon), + 'v' => { + queue![ + nums(Day), + Literal("-"), + fixed(Fixed::ShortMonthName), + Literal("-"), + num0(Year) + ] + } + 'w' => num(NumDaysFromSun), + #[cfg(not(feature = "unstable-locales"))] + 'x' => queue_from_slice!(D_FMT), + #[cfg(feature = "unstable-locales")] + 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), + 'y' => num0(YearMod100), + 'z' => { + if is_alternate { + internal_fixed(TimezoneOffsetPermissive) + } else { + fixed(Fixed::TimezoneOffset) + } + } + '+' => fixed(Fixed::RFC3339), + ':' => { + if remainder.starts_with("::z") { + remainder = &remainder[3..]; + fixed(Fixed::TimezoneOffsetTripleColon) + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; + fixed(Fixed::TimezoneOffsetDoubleColon) + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; + fixed(Fixed::TimezoneOffsetColon) + } else { + self.error(original, &mut error_len, None).1 + } + } + '.' => match next!() { + '3' => match next!() { + 'f' => fixed(Fixed::Nanosecond3), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '6' => match next!() { + 'f' => fixed(Fixed::Nanosecond6), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '9' => match next!() { + 'f' => fixed(Fixed::Nanosecond9), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + 'f' => fixed(Fixed::Nanosecond), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '3' => match next!() { + 'f' => internal_fixed(Nanosecond3NoDot), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '6' => match next!() { + 'f' => internal_fixed(Nanosecond6NoDot), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '9' => match next!() { + 'f' => internal_fixed(Nanosecond9NoDot), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }, + '%' => Literal("%"), + c => { + let res = self.error(original, &mut error_len, Some(c)); + remainder = res.0; + res.1 + } + }; + + // Adjust `item` if we have any padding modifier. + // Not allowed on non-numeric items or on specifiers composed out of multiple + // formatting items. + if let Some(new_pad) = pad_override { + match item { + Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { + Some((remainder, Item::Numeric(kind.clone(), new_pad))) + } + _ => Some(self.error(original, &mut error_len, None)), + } + } else { + Some((remainder, item)) } } From a6231701ee934cbbeb595fe71328fcbbf1b584d5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 10:42:52 +0200 Subject: [PATCH 954/999] strftime: simplify error handling --- src/format/strftime.rs | 84 +++++++++--------------------------------- 1 file changed, 17 insertions(+), 67 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 0692fe0166..a4cfef8734 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -253,8 +253,7 @@ impl<'a> StrftimeItems<'a> { /// const ITEMS: &[Item<'static>] = &[ /// Item::Numeric(Numeric::Year, Pad::Zero), /// Item::Literal("-"), - /// Item::Literal("%"), - /// Item::Literal("Q"), + /// Item::Literal("%Q"), /// ]; /// println!("{:?}", strftime_parser.clone().collect::>()); /// assert!(strftime_parser.eq(ITEMS.iter().cloned())); @@ -458,22 +457,14 @@ impl<'a> StrftimeItems<'a> { } }; - let mut error_len = 0; - if self.lenient { - error_len += 1; - } - macro_rules! next { () => { match remainder.chars().next() { Some(x) => { remainder = &remainder[x.len_utf8()..]; - if self.lenient { - error_len += x.len_utf8(); - } x } - None => return Some(self.error(original, &mut error_len, None)), // premature end of string + None => return Some((remainder, self.error(original, remainder))), // premature end of string } }; } @@ -489,7 +480,7 @@ impl<'a> StrftimeItems<'a> { let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(self.error(original, &mut error_len, Some(spec))); + return Some((remainder, self.error(original, remainder))); } macro_rules! queue { @@ -600,71 +591,39 @@ impl<'a> StrftimeItems<'a> { remainder = &remainder[1..]; fixed(Fixed::TimezoneOffsetColon) } else { - self.error(original, &mut error_len, None).1 + self.error(original, remainder) } } '.' => match next!() { '3' => match next!() { 'f' => fixed(Fixed::Nanosecond3), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '6' => match next!() { 'f' => fixed(Fixed::Nanosecond6), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '9' => match next!() { 'f' => fixed(Fixed::Nanosecond9), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, 'f' => fixed(Fixed::Nanosecond), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '3' => match next!() { 'f' => internal_fixed(Nanosecond3NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '6' => match next!() { 'f' => internal_fixed(Nanosecond6NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '9' => match next!() { 'f' => internal_fixed(Nanosecond9NoDot), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }, '%' => Literal("%"), - c => { - let res = self.error(original, &mut error_len, Some(c)); - remainder = res.0; - res.1 - } + _ => self.error(original, remainder), }; // Adjust `item` if we have any padding modifier. @@ -675,27 +634,18 @@ impl<'a> StrftimeItems<'a> { Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { Some((remainder, Item::Numeric(kind.clone(), new_pad))) } - _ => Some(self.error(original, &mut error_len, None)), + _ => Some((remainder, self.error(original, remainder))), } } else { Some((remainder, item)) } } - fn error<'b>( - &mut self, - original: &'b str, - error_len: &mut usize, - ch: Option, - ) -> (&'b str, Item<'b>) { - if !self.lenient { - return (&original[*error_len..], Item::Error); - } - - if let Some(c) = ch { - *error_len -= c.len_utf8(); + fn error<'b>(&mut self, original: &'b str, remainder: &'b str) -> Item<'b> { + match self.lenient { + false => Item::Error, + true => Item::Literal(&original[..original.len() - remainder.len()]), } - (&original[*error_len..], Item::Literal(&original[..*error_len])) } #[cfg(feature = "unstable-locales")] From 5cf56035007f291b1b8f457fa3bfc79fb367419a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 10:03:38 +0200 Subject: [PATCH 955/999] strftime: add regression test case --- src/format/strftime.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index a4cfef8734..5dc1180cb9 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -1199,4 +1199,18 @@ mod tests { "2014-05-07T12:34:56+0000%Q%.2f%%" ); } + + /// Regression test for https://github.com/chronotope/chrono/issues/1725 + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_finite() { + let mut i = 0; + for item in StrftimeItems::new("%2f") { + println!("{:?}", item); + i += 1; + if i > 10 { + panic!("infinite loop"); + } + } + } } From f3fd15f976c8c8e56d97eda6237af4d485ba2297 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 8 Sep 2025 10:44:36 +0200 Subject: [PATCH 956/999] Bump version to 0.4.42 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a093cf58e..6c6d5c269f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.41" +version = "0.4.42" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 0f016846f11e7be830352606932b435f1899c7bd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 26 Sep 2025 09:07:59 +0200 Subject: [PATCH 957/999] Install extra components for lint workflow Install rustfmt for workflows that call `cargo test`, to make sure rustfmt is available for the `win_bindings` codegen test. --- .github/workflows/lint.yml | 1 + .github/workflows/test.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 05299e6703..93a359636e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,6 +20,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc + components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo fmt --check -- --color=always - run: cargo fmt --check --manifest-path fuzz/Cargo.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aba4c032be..3909f7b43a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,8 @@ jobs: steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo test ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always @@ -52,6 +54,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} + components: rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo check --manifest-path bench/Cargo.toml --benches - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets @@ -68,6 +71,8 @@ jobs: steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - run: | @@ -130,6 +135,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown + components: rustfmt - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v5 - uses: jetli/wasm-pack-action@v0.4.0 From 113777a22f4c4f8ab4d80e96f70c9af545660a0c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 26 Sep 2025 10:50:49 +0200 Subject: [PATCH 958/999] Upgrade windows-bindgen to 0.64 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c6d5c269f..f693d6b277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-link = { version = "0.2", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.63" } # MSRV is 1.74 +windows-bindgen = { version = "0.64" } # MSRV is 1.74 [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } From 58f46672068943e237c948e19db86a88e8f4e29d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 1 Oct 2025 15:16:31 +0200 Subject: [PATCH 959/999] Allow windows-bindgen codegen on any host target --- .github/workflows/codecov.yml | 6 ++++-- Cargo.toml | 4 +--- tests/win_bindings.rs | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index eb6d0938d7..a5d7c9ed9d 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -19,8 +19,10 @@ jobs: steps: - uses: actions/checkout@v5 # nightly is required for --doctests, see cargo-llvm-cov#2 - - name: Install Rust (nightly) - run: rustup update nightly + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: rustfmt - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage diff --git a/Cargo.toml b/Cargo.toml index f693d6b277..1cdb9e9e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,9 +52,6 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for [target.'cfg(windows)'.dependencies] windows-link = { version = "0.2", optional = true } -[target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.64" } # MSRV is 1.74 - [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } @@ -63,6 +60,7 @@ serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } similar-asserts = { version = "1.6.1" } bincode = { version = "1.3.0" } +windows-bindgen = { version = "0.64" } # MSRV is 1.74 [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index 2e281571b9..c50d1c8bac 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -1,5 +1,3 @@ -#![cfg(all(windows, feature = "clock", feature = "std"))] - use std::fs; use windows_bindgen::bindgen; From 09ab2089fbd53e2b4ce2c93457e0458c37bd6d1b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 1 Oct 2025 15:20:58 +0200 Subject: [PATCH 960/999] Inline bindings specification --- src/offset/local/win_bindings.txt | 7 ------- tests/win_bindings.rs | 25 +++++++++++++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) delete mode 100644 src/offset/local/win_bindings.txt diff --git a/src/offset/local/win_bindings.txt b/src/offset/local/win_bindings.txt deleted file mode 100644 index 2efa4314fc..0000000000 --- a/src/offset/local/win_bindings.txt +++ /dev/null @@ -1,7 +0,0 @@ ---out src/offset/local/win_bindings.rs ---flat --sys --no-comment ---filter - GetTimeZoneInformationForYear - SystemTimeToFileTime - SystemTimeToTzSpecificLocalTime - TzSpecificLocalTimeToSystemTime diff --git a/tests/win_bindings.rs b/tests/win_bindings.rs index c50d1c8bac..6a38dcecba 100644 --- a/tests/win_bindings.rs +++ b/tests/win_bindings.rs @@ -3,16 +3,27 @@ use windows_bindgen::bindgen; #[test] fn gen_bindings() { - let input = "src/offset/local/win_bindings.txt"; - let output = "src/offset/local/win_bindings.rs"; - let existing = fs::read_to_string(output).unwrap(); + let existing = fs::read_to_string(BINDINGS).unwrap(); - bindgen(["--no-deps", "--etc", input]).unwrap(); + bindgen([ + "--out", + BINDINGS, + "--flat", + "--no-comment", + "--no-deps", + "--sys", + "--filter", + "GetTimeZoneInformationForYear", + "SystemTimeToFileTime", + "SystemTimeToTzSpecificLocalTime", + "TzSpecificLocalTimeToSystemTime", + ]) + .unwrap(); // Check the output is the same as before. // Depending on the git configuration the file may have been checked out with `\r\n` newlines or // with `\n`. Compare line-by-line to ignore this difference. - let mut new = fs::read_to_string(output).unwrap(); + let mut new = fs::read_to_string(BINDINGS).unwrap(); if existing.contains("\r\n") && !new.contains("\r\n") { new = new.replace("\n", "\r\n"); } else if !existing.contains("\r\n") && new.contains("\r\n") { @@ -21,6 +32,8 @@ fn gen_bindings() { similar_asserts::assert_eq!(existing, new); if !new.lines().eq(existing.lines()) { - panic!("generated file `{output}` is changed."); + panic!("generated file `{BINDINGS}` is changed."); } } + +const BINDINGS: &str = "src/offset/local/win_bindings.rs"; From 0061d86cb531c469272c22727c414a0eb9e55816 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 1 Oct 2025 15:29:57 +0200 Subject: [PATCH 961/999] Drop stabilized feature doc_auto_cfg --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 32a1b82f8e..0b102c0745 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,7 +506,7 @@ #![warn(unreachable_pub)] #![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "alloc")] extern crate alloc; From 728822e2a9a366781f83cf9336ae06274d273634 Mon Sep 17 00:00:00 2001 From: SomeBodY1CE Date: Sat, 27 Sep 2025 12:59:09 +0000 Subject: [PATCH 962/999] test: a couple more tests for strict rfc3339 parsing --- src/format/parse.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 2e3b29849d..906834b5cc 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1830,29 +1830,34 @@ mod tests { "2015-01-20T17:35:20.000000000452−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // too small with MINUS SIGN (U+2212) + ("2023-11-05T01:30:00-04:00", Ok(ymd_hmsn(2023, 11, 5, 1, 30, 0, 0, -4))), // ambiguous timestamp ("2015-01-20 17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T' - ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD - ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS - ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year - ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-01-20_17:35:20-08:00", Err(INVALID)), // wrong date time separator + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YM + ("2015-01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char MD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HM + ("2015-01-20T17-35:20.001-08:00", Err(INVALID)), // wrong separator char MS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-00-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad month value ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value - ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format - ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator From e0dd9c5f16a496482ce6c828bbd596f9ccdddf87 Mon Sep 17 00:00:00 2001 From: SomeBodY1CE Date: Sat, 27 Sep 2025 13:54:17 +0000 Subject: [PATCH 963/999] refactor: scan::nanosecond to return u32 --- src/format/parse.rs | 4 ++-- src/format/scan.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 906834b5cc..773b2151af 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -208,7 +208,7 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; if s.starts_with('.') { let nanosecond = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nanosecond)?; + parsed.set_nanosecond(nanosecond as i64)?; } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); @@ -420,7 +420,7 @@ where &Nanosecond => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nano)?; + parsed.set_nanosecond(nano as i64)?; } } diff --git a/src/format/scan.rs b/src/format/scan.rs index 49a4f123a5..30dbc6746b 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -47,14 +47,15 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) /// Tries to consume at least one digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, u32)> { // record the number of digits consumed for later scaling. let origlen = s.len(); let (s, v) = number(s, 1, 9)?; + let v = u32::try_from(v).expect("999,999,999 should fit u32"); let consumed = origlen - s.len(); // scale the number accordingly. - static SCALE: [i64; 10] = + const SCALE: [u32; 10] = [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1]; let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; From ebc808e8fac3481d6e683b7b4a0c2cca411f56b0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 3 Oct 2025 11:19:22 +0200 Subject: [PATCH 964/999] feat: faster RFC 3339 parsing without Parsed Co-authored-by: SomeBodY1CE --- src/datetime/mod.rs | 11 ++--- src/format/parse.rs | 99 ++++++++++++++++++++++++++++++++------------- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index cbfa921979..ebab3fb34c 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -21,8 +21,8 @@ use crate::format::Locale; #[cfg(feature = "alloc")] use crate::format::{DelayedFormat, SecondsFormat, write_rfc2822, write_rfc3339}; use crate::format::{ - Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, TOO_LONG, parse, - parse_and_remainder, parse_rfc3339, + Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder, + parse_rfc3339, }; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -1068,12 +1068,7 @@ impl DateTime { /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - let (s, _) = parse_rfc3339(&mut parsed, s)?; - if !s.is_empty() { - return Err(TOO_LONG); - } - parsed.to_datetime() + parse_rfc3339(s) } /// Parses a string from a user-specified format into a `DateTime` value. diff --git a/src/format/parse.rs b/src/format/parse.rs index 773b2151af..18857a286a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -11,7 +11,7 @@ use super::scan; use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; use super::{ParseError, ParseResult}; -use crate::{DateTime, FixedOffset, Weekday}; +use crate::{DateTime, FixedOffset, MappedLocalTime, NaiveDate, NaiveTime, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -151,7 +151,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st Ok((s, ())) } -pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { +pub(crate) fn parse_rfc3339(mut s: &str) -> ParseResult> { macro_rules! try_consume { ($e:expr) => {{ let (s_, v) = $e?; @@ -189,40 +189,81 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes // // - For readability a full-date and a full-time may be separated by a space character. - parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; - s = scan::char(s, b'-')?; - parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b'-')?; - parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; + let bytes = s.as_bytes(); + if bytes.len() < 19 { + return Err(TOO_SHORT); + } - s = match s.as_bytes().first() { - Some(&b't' | &b'T' | &b' ') => &s[1..], - Some(_) => return Err(INVALID), - None => return Err(TOO_SHORT), - }; + let fixed = <&[u8; 19]>::try_from(&bytes[..19]).unwrap(); // we just checked the length + let year = digit(fixed, 0)? as u16 * 1000 + + digit(fixed, 1)? as u16 * 100 + + digit(fixed, 2)? as u16 * 10 + + digit(fixed, 3)? as u16; + if bytes.get(4) != Some(&b'-') { + return Err(INVALID); + } - parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b':')?; - parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s, b':')?; - parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; - if s.starts_with('.') { - let nanosecond = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nanosecond as i64)?; + let month = digit(fixed, 5)? * 10 + digit(fixed, 6)?; + if bytes.get(7) != Some(&b'-') { + return Err(INVALID); } - let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); - // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant. - // But it is possible to read the offset directly from `Parsed`. We want to only successfully - // populate `Parsed` if the input is fully valid RFC 3339. + let day = digit(fixed, 8)? * 10 + digit(fixed, 9)?; + let date = + NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).ok_or(OUT_OF_RANGE)?; + + if !matches!(bytes.get(10), Some(&b't' | &b'T' | &b' ')) { + return Err(INVALID); + } + + let hour = digit(fixed, 11)? * 10 + digit(fixed, 12)?; + if bytes.get(13) != Some(&b':') { + return Err(INVALID); + } + + let min = digit(fixed, 14)? * 10 + digit(fixed, 15)?; + if bytes.get(16) != Some(&b':') { + return Err(INVALID); + } + + let sec = digit(fixed, 17)? * 10 + digit(fixed, 18)?; + let (sec, extra_nanos) = match sec { + 60 => (59, 1_000_000_000), // rfc3339 allows leap seconds + _ => (sec, 0), + }; + + let nano = if bytes.get(19) == Some(&b'.') { + let nanosecond = try_consume!(scan::nanosecond(&s[20..])); + extra_nanos + nanosecond + } else { + s = &s[19..]; + extra_nanos + }; + + let time = NaiveTime::from_hms_nano_opt(hour as u32, min as u32, sec as u32, nano) + .ok_or(OUT_OF_RANGE)?; + // Max for the hours field is `23`, and for the minutes field `59`. - const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60; - if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { - return Err(OUT_OF_RANGE); + let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); + if !s.is_empty() { + return Err(TOO_LONG); } - parsed.set_offset(i64::from(offset))?; - Ok((s, ())) + let tz = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + Ok(match date.and_time(time).and_local_timezone(tz) { + MappedLocalTime::Single(dt) => dt, + // `FixedOffset::with_ymd_and_hms` doesn't return `MappedLocalTime::Ambiguous` + // and returns `MappedLocalTime::None` on invalid data + MappedLocalTime::Ambiguous(_, _) | MappedLocalTime::None => unreachable!(), + }) +} + +#[inline] +fn digit(bytes: &[u8; 19], index: usize) -> ParseResult { + match bytes[index].is_ascii_digit() { + true => Ok(bytes[index] - b'0'), + false => Err(INVALID), + } } /// Tries to parse given string into `parsed` with given formatting items. From e632ffd3b89d3cfaa96776f2368ee4c21a972766 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 04:04:53 +0000 Subject: [PATCH 965/999] Update windows-bindgen requirement from 0.64 to 0.65 --- updated-dependencies: - dependency-name: windows-bindgen dependency-version: 0.65.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1cdb9e9e0c..d238518c98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } similar-asserts = { version = "1.6.1" } bincode = { version = "1.3.0" } -windows-bindgen = { version = "0.64" } # MSRV is 1.74 +windows-bindgen = { version = "0.65" } # MSRV is 1.74 [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" From c34e4c51082fab9c0a7a7561f35e6ef0df8bdfdc Mon Sep 17 00:00:00 2001 From: Ruslan Fadeev Date: Mon, 13 Oct 2025 17:30:11 +0300 Subject: [PATCH 966/999] add Datelike::days_between --- src/traits.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index 450f0a2259..78e930a735 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{IsoWeek, Month, Weekday}; +use crate::{Days, IsoWeek, Month, Weekday}; /// The common set of methods for date component. /// @@ -277,6 +277,12 @@ pub trait Datelike: Sized { // know it is in range, and the result will never be `None`. month.num_days(self.year()).unwrap() } + + /// Get the difference in days between the two days, + /// ignoring the time component + fn days_between(&self, other: impl Datelike) -> Days { + Days::new(i32::abs_diff(self.num_days_from_ce(), other.num_days_from_ce()).into()) + } } /// The common set of methods for time component. From 334565c5f4e8f2eb092e8bc3517fff590c7b757c Mon Sep 17 00:00:00 2001 From: Ruslan Fadeev Date: Tue, 14 Oct 2025 00:18:29 +0300 Subject: [PATCH 967/999] replace Datelike::days_between with NaiveDate::abs_diff --- src/naive/date/mod.rs | 20 +++++++++++++++++++- src/traits.rs | 8 +------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index e6af254304..d38ab74c28 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1148,7 +1148,7 @@ impl NaiveDate { /// ); /// ``` #[must_use] - pub const fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { + pub const fn signed_duration_since(self, rhs: Self) -> TimeDelta { let year1 = self.year(); let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); @@ -1161,6 +1161,24 @@ impl NaiveDate { expect(TimeDelta::try_days(days), "always in range") } + /// Returns the absolute difference between two `NaiveDate`s measured as the number of days. + /// + /// This is always an integer, non-negative number, similar to `abs_diff` in `std`. + /// + /// # Example + /// + /// ``` + /// # use chrono::{Days, NaiveDate}; + /// # + /// let date1: NaiveDate = "2020-01-01".parse().unwrap(); + /// let date2: NaiveDate = "2020-01-31".parse().unwrap(); + /// assert_eq!(date2.abs_diff(date1), Days::new(30)); + /// assert_eq!(date1.abs_diff(date2), Days::new(30)); + /// ``` + pub const fn abs_diff(self, rhs: Self) -> Days { + Days::new(i32::abs_diff(self.num_days_from_ce(), rhs.num_days_from_ce()) as u64) + } + /// Returns the number of whole years from the given `base` until `self`. /// /// # Errors diff --git a/src/traits.rs b/src/traits.rs index 78e930a735..450f0a2259 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{Days, IsoWeek, Month, Weekday}; +use crate::{IsoWeek, Month, Weekday}; /// The common set of methods for date component. /// @@ -277,12 +277,6 @@ pub trait Datelike: Sized { // know it is in range, and the result will never be `None`. month.num_days(self.year()).unwrap() } - - /// Get the difference in days between the two days, - /// ignoring the time component - fn days_between(&self, other: impl Datelike) -> Days { - Days::new(i32::abs_diff(self.num_days_from_ce(), other.num_days_from_ce()).into()) - } } /// The common set of methods for time component. From 35f9f2daef4c3132546d0004fdf37284df023fd3 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Thu, 2 Oct 2025 14:34:59 -0700 Subject: [PATCH 968/999] Add feature gated defmt support. --- Cargo.toml | 4 +++- src/date.rs | 10 ++++++++++ src/datetime/mod.rs | 10 ++++++++++ src/format/formatting.rs | 1 + src/format/mod.rs | 34 ++++++++++++++++++++++++++++++++++ src/format/parsed.rs | 1 + src/format/strftime.rs | 1 + src/lib.rs | 7 +++++++ src/month.rs | 9 +++++++++ src/naive/date/mod.rs | 17 +++++++++++++++++ src/naive/datetime/mod.rs | 7 +++++++ src/naive/isoweek.rs | 15 +++++++++++++++ src/naive/mod.rs | 2 ++ src/naive/time/mod.rs | 25 +++++++++++++++++++++++++ src/offset/fixed.rs | 17 +++++++++++++++++ src/offset/local/mod.rs | 1 + src/offset/mod.rs | 2 +- src/offset/utc.rs | 7 +++++++ src/round.rs | 1 + src/time_delta.rs | 2 ++ src/weekday.rs | 8 ++++++++ src/weekday_set.rs | 17 +++++++++++++++++ 22 files changed, 196 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d238518c98..dfda5d284f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ name = "chrono" # Don't forget to adjust `ALL_NON_EXCLUSIVE_FEATURES` in CI scripts when adding a feature or an optional dependency. default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] +defmt = ["dep:defmt", "pure-rust-locales?/defmt"] libc = [] winapi = ["windows-link"] std = ["alloc"] @@ -41,9 +42,10 @@ __internal_bench = [] [dependencies] num-traits = { version = "0.2", default-features = false } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.8", optional = true } +pure-rust-locales = { version = "0.8.2", optional = true } rkyv = { version = "0.7.43", optional = true, default-features = false } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } +defmt = { version = "1.0.1", optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } diff --git a/src/date.rs b/src/date.rs index a66882cecc..59cef0ef43 100644 --- a/src/date.rs +++ b/src/date.rs @@ -551,6 +551,16 @@ where } } +#[cfg(feature = "defmt")] +impl defmt::Format for Date +where + Tz::Offset: defmt::Format, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}{}", self.naive_local(), self.offset); + } +} + // Note that implementation of Arbitrary cannot be automatically derived for Date, due to // the nontrivial bound ::Offset: Arbitrary. #[cfg(all(feature = "arbitrary", feature = "std"))] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ebab3fb34c..3f68421890 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1812,6 +1812,16 @@ impl fmt::Debug for DateTime { } } +#[cfg(feature = "defmt")] +impl defmt::Format for DateTime +where + Tz::Offset: defmt::Format, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}{}", self.overflowing_naive_local(), self.offset); + } +} + // `fmt::Debug` is hand implemented for the `rkyv::Archive` variant of `DateTime` because // deriving a trait recursively does not propagate trait defined associated types with their own // constraints: diff --git a/src/format/formatting.rs b/src/format/formatting.rs index a86d321535..d95c0acbef 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -474,6 +474,7 @@ impl OffsetFormat { /// /// See the `TimeZone::to_rfc3339_opts` function for usage. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(clippy::manual_non_exhaustive)] pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. diff --git a/src/format/mod.rs b/src/format/mod.rs index a0aa94b78b..ec4beae453 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -78,6 +78,7 @@ enum Void {} /// Padding characters for numeric items. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Pad { /// No padding. None, @@ -102,6 +103,7 @@ pub enum Pad { /// parsed with the same formatting items. #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). @@ -170,12 +172,20 @@ impl fmt::Debug for InternalNumeric { } } +#[cfg(feature = "defmt")] +impl defmt::Format for InternalNumeric { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "") + } +} + /// Fixed-format item types. /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Fixed { /// Abbreviated month names. /// @@ -260,11 +270,13 @@ pub enum Fixed { /// An opaque type representing fixed-format item types for internal uses only. #[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct InternalFixed { val: InternalInternal, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -285,6 +297,7 @@ enum InternalInternal { /// Type for specifying the format of UTC offsets. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct OffsetFormat { /// See `OffsetPrecision`. pub precision: OffsetPrecision, @@ -298,6 +311,7 @@ pub struct OffsetFormat { /// The precision of an offset from UTC formatting item. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OffsetPrecision { /// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to /// have an offset of 30 minutes, 15 minutes, etc. @@ -319,6 +333,7 @@ pub enum OffsetPrecision { /// The separator between hours and minutes in an offset. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Colons { /// No separator None, @@ -350,6 +365,23 @@ pub enum Item<'a> { Error, } +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Item<'a> { + fn format(&self, f: defmt::Formatter) { + match self { + Item::Literal(v) => defmt::write!(f, "Literal {{ {} }}", v), + #[cfg(feature = "alloc")] + Item::OwnedLiteral(_) => {} + Item::Space(v) => defmt::write!(f, "Space {{ {} }}", v), + #[cfg(feature = "alloc")] + Item::OwnedSpace(_) => {} + Item::Numeric(u, v) => defmt::write!(f, "Numeric {{ {}, {} }}", u, v), + Item::Fixed(v) => defmt::write!(f, "Fixed {{ {} }}", v), + Item::Error => defmt::write!(f, "Error"), + } + } +} + const fn num(numeric: Numeric) -> Item<'static> { Item::Numeric(numeric, Pad::None) } @@ -388,6 +420,7 @@ impl Item<'_> { /// An error from the `parse` function. #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ParseError(ParseErrorKind); impl ParseError { @@ -400,6 +433,7 @@ impl ParseError { /// The category of parse error #[allow(clippy::manual_non_exhaustive)] #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 8524daaa7d..3746ba4d97 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -126,6 +126,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// ``` #[allow(clippy::manual_non_exhaustive)] #[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Parsed { #[doc(hidden)] pub year: Option, diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 5dc1180cb9..d93ef814c8 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -189,6 +189,7 @@ use alloc::vec::Vec; /// [`DateTime`]: crate::DateTime /// [`format::parse()`]: crate::format::parse() #[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct StrftimeItems<'a> { /// Remaining portion of the string. remainder: &'a str, diff --git a/src/lib.rs b/src/lib.rs index 0b102c0745..c25e4c6dbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -687,6 +687,13 @@ impl fmt::Debug for OutOfRange { } } +#[cfg(feature = "defmt")] +impl defmt::Format for OutOfRange { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "out of range"); + } +} + #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} diff --git a/src/month.rs b/src/month.rs index 3ba9b6b2dd..5e51d7f73c 100644 --- a/src/month.rs +++ b/src/month.rs @@ -38,6 +38,7 @@ use crate::naive::NaiveDate; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Month { /// January January = 0, @@ -248,6 +249,7 @@ impl num_traits::FromPrimitive for Month { /// A duration in calendar months #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Months(pub(crate) u32); impl Months { @@ -287,6 +289,13 @@ impl fmt::Debug for ParseMonthError { } } +#[cfg(feature = "defmt")] +impl defmt::Format for ParseMonthError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "ParseMonthError {{ .. }}") + } +} + #[cfg(feature = "serde")] mod month_serde { use super::Month; diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index d38ab74c28..0b80da93f0 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -2302,6 +2302,23 @@ impl fmt::Debug for NaiveDate { } } +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveDate { + fn format(&self, fmt: defmt::Formatter) { + let year = self.year(); + let mdf = self.mdf(); + if (0..=9999).contains(&year) { + defmt::write!(fmt, "{:02}{:02}", year / 100, year % 100); + } else { + // ISO 8601 requires the explicit sign for out-of-range years + let sign = ['+', '-'][(year < 0) as usize]; + defmt::write!(fmt, "{}{:05}", sign, year.abs()); + } + + defmt::write!(fmt, "-{:02}-{:02}", mdf.month(), mdf.day()); + } +} + /// The `Display` output of the naive date `d` is the same as /// [`d.format("%Y-%m-%d")`](crate::format::strftime). /// diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e5c871148a..604e6654c5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -2057,6 +2057,13 @@ impl fmt::Debug for NaiveDateTime { } } +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveDateTime { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}T{}", self.date, self.time); + } +} + /// The `Display` output of the naive date and time `dt` is the same as /// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](crate::format::strftime). /// diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index f2296a23a9..25fcbc138e 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -160,6 +160,21 @@ impl fmt::Debug for IsoWeek { } } +#[cfg(feature = "defmt")] +impl defmt::Format for IsoWeek { + fn format(&self, fmt: defmt::Formatter) { + let year = self.year(); + let week = self.week(); + if (0..=9999).contains(&year) { + defmt::write!(fmt, "{:04}-W{:02}", year, week) + } else { + // ISO 8601 requires the explicit sign for out-of-range years + let sign = ['+', '-'][(year < 0) as usize]; + defmt::write!(fmt, "{}{:05}-W{:02}", sign, year.abs(), week) + } + } +} + #[cfg(test)] mod tests { #[cfg(feature = "rkyv-validation")] diff --git a/src/naive/mod.rs b/src/naive/mod.rs index efb9822859..ff94584c6e 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -31,6 +31,7 @@ pub use self::internals::YearFlags as __BenchYearFlags; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. #[derive(Clone, Copy, Debug, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct NaiveWeek { date: NaiveDate, start: Weekday, @@ -226,6 +227,7 @@ impl Hash for NaiveWeek { /// 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, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Days(pub(crate) u64); impl Days { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8891587cfb..5a35aa3844 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1529,6 +1529,31 @@ impl fmt::Debug for NaiveTime { } } +#[cfg(feature = "defmt")] +impl defmt::Format for NaiveTime { + fn format(&self, fmt: defmt::Formatter) { + let (hour, min, sec) = self.hms(); + let (sec, nano) = if self.frac >= 1_000_000_000 { + (sec + 1, self.frac - 1_000_000_000) + } else { + (sec, self.frac) + }; + + let (hour, min, sec) = (hour as u8, min as u8, sec as u8); + defmt::write!(fmt, "{:02}:{:02}:{:02}", hour, min, sec); + + if nano == 0 { + return; + } else if nano % 1_000_000 == 0 { + defmt::write!(fmt, ".{:03}", nano / 1_000_000); + } else if nano % 1_000 == 0 { + defmt::write!(fmt, ".{:06}", nano / 1_000); + } else { + defmt::write!(fmt, ".{:09}", nano); + } + } +} + /// The `Display` output of the naive time `t` is the same as /// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). /// diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index da6d4f2a6e..e810c854fd 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -174,6 +174,23 @@ impl fmt::Display for FixedOffset { } } +#[cfg(feature = "defmt")] +impl defmt::Format for FixedOffset { + fn format(&self, f: defmt::Formatter) { + let offset = self.local_minus_utc; + let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; + let sec = offset.rem_euclid(60); + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); + if sec == 0 { + defmt::write!(f, "{}{:02}:{:02}", sign, hour, min) + } else { + defmt::write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + } + } +} + #[cfg(all(feature = "arbitrary", feature = "std"))] impl arbitrary::Arbitrary<'_> for FixedOffset { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index ee685715f5..5741d24103 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -122,6 +122,7 @@ mod tz_info; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Local; impl Local { diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 38380c131c..295700b4f7 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -76,7 +76,7 @@ pub use self::utc::Utc; /// The type of `T` is usually a [`DateTime`] but may also be only an offset. pub type MappedLocalTime = LocalResult; #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] - +#[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Old name of [`MappedLocalTime`]. See that type for more documentation. pub enum LocalResult { /// The local time maps to a single unique result. diff --git a/src/offset/utc.rs b/src/offset/utc.rs index ab925fbf16..c1a27c8598 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -150,3 +150,10 @@ impl fmt::Display for Utc { write!(f, "UTC") } } + +#[cfg(feature = "defmt")] +impl defmt::Format for Utc { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "Z"); + } +} diff --git a/src/round.rs b/src/round.rs index 9f575a6896..34c4fe442c 100644 --- a/src/round.rs +++ b/src/round.rs @@ -305,6 +305,7 @@ where /// /// See: [`DurationRound`] #[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RoundingError { /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch. /// diff --git a/src/time_delta.rs b/src/time_delta.rs index 3ed81e5eed..123a781485 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -59,6 +59,7 @@ const SECS_PER_WEEK: i64 = 604_800; archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TimeDelta { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC @@ -624,6 +625,7 @@ impl fmt::Display for TimeDelta { /// *seconds*, while this module supports signed range of up to /// `i64::MAX` of *milliseconds*. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct OutOfRangeError(()); impl fmt::Display for OutOfRangeError { diff --git a/src/weekday.rs b/src/weekday.rs index 2a4b2ea2bd..da4746b8ae 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -38,6 +38,7 @@ use crate::OutOfRange; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Weekday { /// Monday. Mon = 0, @@ -256,6 +257,13 @@ impl fmt::Debug for ParseWeekdayError { } } +#[cfg(feature = "defmt")] +impl defmt::Format for ParseWeekdayError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "ParseWeekdayError {{ .. }}") + } +} + // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")] diff --git a/src/weekday_set.rs b/src/weekday_set.rs index ce60bf3ba4..989131c209 100644 --- a/src/weekday_set.rs +++ b/src/weekday_set.rs @@ -331,6 +331,23 @@ impl Debug for WeekdaySet { } } +#[cfg(feature = "defmt")] +impl defmt::Format for WeekdaySet { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!( + f, + "WeekdaySet({}{}{}{}{}{}{})", + 0x1 & (self.0 >> 6), + 0x1 & (self.0 >> 5), + 0x1 & (self.0 >> 4), + 0x1 & (self.0 >> 3), + 0x1 & (self.0 >> 2), + 0x1 & (self.0 >> 1), + 0x1 & (self.0 >> 0), + ) + } +} + /// An iterator over a collection of weekdays, starting from a given day. /// /// See [`WeekdaySet::iter()`]. From ea1f11b356fde5c2825679378e8ddf6e153942e5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 19 Oct 2025 10:03:51 +0200 Subject: [PATCH 969/999] Drop deny lints, eager Debug impls are a mixed blessing --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c25e4c6dbb..0e942e02d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,10 +501,8 @@ //! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 #![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] #![warn(unreachable_pub)] -#![deny(clippy::tests_outside_test_module)] +#![warn(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] From 8c827116b9d67fe926eceb553ce95656dd7627d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 04:05:13 +0000 Subject: [PATCH 970/999] Bump actions/setup-node from 5 to 6 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3909f7b43a..b3770c824a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,7 @@ jobs: targets: wasm32-unknown-unknown components: rustfmt - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 - uses: jetli/wasm-pack-action@v0.4.0 # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment # with the host system. From 34b5f49e9d7cdcabda6eea054609a2ce4ba947f0 Mon Sep 17 00:00:00 2001 From: spuradage Date: Wed, 22 Oct 2025 16:17:28 +0800 Subject: [PATCH 971/999] chore: minor improvement for docs Signed-off-by: spuradage --- src/datetime/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3f68421890..2bfdf28b3f 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -414,7 +414,7 @@ impl DateTime { } /// Fix the offset from UTC to its current value, dropping the associated timezone information. - /// This it useful for converting a generic `DateTime` to `DateTime`. + /// This is useful for converting a generic `DateTime` to `DateTime`. #[inline] #[must_use] pub fn fixed_offset(&self) -> DateTime { @@ -1518,7 +1518,7 @@ impl hash::Hash for DateTime { /// Add `TimeDelta` to `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// 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 @@ -1537,7 +1537,7 @@ impl Add for DateTime { /// Add `std::time::Duration` to `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// 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 @@ -1558,7 +1558,7 @@ impl Add for DateTime { /// Add-assign `chrono::Duration` to `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// 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 @@ -1578,7 +1578,7 @@ impl AddAssign for DateTime { /// Add-assign `std::time::Duration` to `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// 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 @@ -1678,7 +1678,7 @@ impl Sub for DateTime { /// This is the same as the addition with a negated `TimeDelta`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// second ever**, except when the `DateTime` itself represents a leap second in which case /// the assumption becomes that **there is exactly a single leap second ever**. /// /// # Panics @@ -1698,7 +1698,7 @@ impl SubAssign for DateTime { /// Subtract-assign `std::time::Duration` from `DateTime`. /// /// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap -/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// second ever**, except when the `DateTime` itself represents a leap second in which case /// the assumption becomes that **there is exactly a single leap second ever**. /// /// # Panics From db129730e8f21ca66b3808c133b7147cbc62fc41 Mon Sep 17 00:00:00 2001 From: LucasBou <29388787+LucasBou@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:36:22 +0100 Subject: [PATCH 972/999] Added doctest for the NaiveDate years_since function (#1755) --- src/naive/date/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 0b80da93f0..dfa2b181b5 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1184,6 +1184,17 @@ impl NaiveDate { /// # Errors /// /// Returns `None` if `base > self`. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate}; + /// # + /// let base: NaiveDate = "2025-01-01".parse().unwrap(); + /// let date: NaiveDate = "2030-01-01".parse().unwrap(); + /// + /// assert_eq!(date.years_since(base), Some(5)) + /// ``` #[must_use] pub const fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); From f4b7bbda679199e7509da813849a10242b2f7aa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 04:04:36 +0000 Subject: [PATCH 973/999] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 10 +++++----- .github/workflows/test.yml | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index a5d7c9ed9d..e972e08311 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,7 +17,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # nightly is required for --doctests, see cargo-llvm-cov#2 - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 93a359636e..c8ac89ffca 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc @@ -45,20 +45,20 @@ jobs: container: image: tamasfe/taplo:0.8.0 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: taplo lint - run: taplo fmt --check --diff cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: EmbarkStudios/cargo-deny-action@v2 check-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} @@ -69,7 +69,7 @@ jobs: cffconvert: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - uses: citation-file-format/cffconvert-github-action@2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3770c824a..5c16e2b1f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -33,7 +33,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: "1.62.0" @@ -50,7 +50,7 @@ jobs: rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} @@ -69,7 +69,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -98,7 +98,7 @@ jobs: target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -118,7 +118,7 @@ jobs: ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -131,7 +131,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown @@ -150,7 +150,7 @@ jobs: target: [wasm32-wasip1] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -165,7 +165,7 @@ jobs: - x86_64-unknown-illumos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} @@ -176,7 +176,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always @@ -187,7 +187,7 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: From 4fedaba2a214aa560e1c6a70d0a09e6955ed3ff4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 9 Jan 2026 09:55:22 +0100 Subject: [PATCH 974/999] Ignore bincode advisory --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index c9121c1f0e..2fe92c8a44 100644 --- a/deny.toml +++ b/deny.toml @@ -4,3 +4,5 @@ private = { ignore = true } [advisories] yanked = "deny" +# bincode is deprecated -- just a dev-dependency in this crate +ignore = ["RUSTSEC-2025-0141"] From a03e43b1c3ef3aea77f12200d84144f275560aa9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 9 Jan 2026 09:57:13 +0100 Subject: [PATCH 975/999] Upgrade windows-bindgen to 0.66 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dfda5d284f..49a1b9d304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } similar-asserts = { version = "1.6.1" } bincode = { version = "1.3.0" } -windows-bindgen = { version = "0.65" } # MSRV is 1.74 +windows-bindgen = { version = "0.66" } # MSRV is 1.74 [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" From 1c0b8f011ab2f2e53c195df1866a1fb4c7fd193a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 9 Jan 2026 09:55:35 +0100 Subject: [PATCH 976/999] Bump version to 0.4.43 --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 19ae4f2357..b1d4b976df 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2025-02-26 -version: 0.4.41 +date-released: 2026-01-09 +version: 0.4.43 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 49a1b9d304..a544fcbfe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.42" +version = "0.4.43" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 45caaa970ce443b11106a710ee24bd2480e5ff99 Mon Sep 17 00:00:00 2001 From: taozui472 Date: Tue, 13 Jan 2026 03:05:11 +0000 Subject: [PATCH 977/999] Update copyright year to 2026 in LICENSE.txt --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 0f9e3cfda8..480fdf7e47 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ Rust-chrono is dual-licensed under The MIT License [1] and -Apache 2.0 License [2]. Copyright (c) 2014--2025, Kang Seonghoon and +Apache 2.0 License [2]. Copyright (c) 2014--2026, Kang Seonghoon and contributors. Nota Bene: This is same as the Rust Project's own license. From f8900b5a44228a7f6282c65e8c407d3ecb6dcb7b Mon Sep 17 00:00:00 2001 From: Carlos O'Ryan Date: Sun, 8 Feb 2026 14:05:54 -0500 Subject: [PATCH 978/999] docs: match MSRV with `Cargo.toml` contents --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76cd26e288..58e16f3c7f 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. ## Rust version requirements -The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.62.0**. The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done lightly. From cfae889a3a23507acf49b605794abba17effd2d7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 12 Feb 2026 19:04:13 +0100 Subject: [PATCH 979/999] Fix panic message in to_rfc2822 --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2bfdf28b3f..dc24b1bbfa 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -623,7 +623,7 @@ impl DateTime { pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) - .expect("writing rfc2822 datetime to string should never fail"); + .expect("date cannot be represented by RFC 2822"); result } From ea832c5090369eefa2cb6a47d643e2f7ade7ffa7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 12 Feb 2026 19:07:05 +0100 Subject: [PATCH 980/999] Add track_caller to non-deprecated functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … that can panic because of invalid user input. --- src/date.rs | 4 ++++ src/datetime/mod.rs | 15 +++++++++++++++ src/naive/date/mod.rs | 8 ++++++++ src/naive/datetime/mod.rs | 14 ++++++++++++++ src/naive/mod.rs | 3 +++ src/time_delta.rs | 13 +++++++++++++ 6 files changed, 57 insertions(+) diff --git a/src/date.rs b/src/date.rs index 59cef0ef43..9356dd052b 100644 --- a/src/date.rs +++ b/src/date.rs @@ -497,6 +497,7 @@ impl Add for Date { type Output = Date; #[inline] + #[track_caller] fn add(self, rhs: TimeDelta) -> Date { self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed") } @@ -504,6 +505,7 @@ impl Add for Date { impl AddAssign for Date { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: TimeDelta) { self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed"); } @@ -513,6 +515,7 @@ impl Sub for Date { type Output = Date; #[inline] + #[track_caller] fn sub(self, rhs: TimeDelta) -> Date { self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed") } @@ -520,6 +523,7 @@ impl Sub for Date { impl SubAssign for Date { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: TimeDelta) { self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed"); } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index dc24b1bbfa..272976a015 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -620,6 +620,7 @@ impl DateTime { /// can not have more than 4 digits. #[cfg(feature = "alloc")] #[must_use] + #[track_caller] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) @@ -1529,6 +1530,7 @@ impl Add for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn add(self, rhs: TimeDelta) -> DateTime { self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") } @@ -1548,6 +1550,7 @@ impl Add for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn add(self, rhs: Duration) -> DateTime { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1567,6 +1570,7 @@ impl Add for DateTime { /// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: TimeDelta) { let datetime = self.datetime.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed"); @@ -1587,6 +1591,7 @@ impl AddAssign for DateTime { /// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: Duration) { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1603,6 +1608,7 @@ impl Add for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn add(mut self, rhs: FixedOffset) -> DateTime { self.datetime = self.naive_utc().checked_add_offset(rhs).expect("`DateTime + FixedOffset` overflowed"); @@ -1626,6 +1632,7 @@ impl Add for DateTime { impl Add for DateTime { type Output = DateTime; + #[track_caller] fn add(self, rhs: Months) -> Self::Output { self.checked_add_months(rhs).expect("`DateTime + Months` out of range") } @@ -1647,6 +1654,7 @@ impl Sub for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn sub(self, rhs: TimeDelta) -> DateTime { self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") } @@ -1666,6 +1674,7 @@ impl Sub for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn sub(self, rhs: Duration) -> DateTime { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1687,6 +1696,7 @@ impl Sub for DateTime { /// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: TimeDelta) { let datetime = self.datetime.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed"); @@ -1707,6 +1717,7 @@ impl SubAssign for DateTime { /// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: Duration) { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1723,6 +1734,7 @@ impl Sub for DateTime { type Output = DateTime; #[inline] + #[track_caller] fn sub(mut self, rhs: FixedOffset) -> DateTime { self.datetime = self.naive_utc().checked_sub_offset(rhs).expect("`DateTime - FixedOffset` overflowed"); @@ -1746,6 +1758,7 @@ impl Sub for DateTime { impl Sub for DateTime { type Output = DateTime; + #[track_caller] fn sub(self, rhs: Months) -> Self::Output { self.checked_sub_months(rhs).expect("`DateTime - Months` out of range") } @@ -1782,6 +1795,7 @@ impl Sub<&DateTime> for DateTime { impl Add for DateTime { type Output = DateTime; + #[track_caller] fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).expect("`DateTime + Days` out of range") } @@ -1800,6 +1814,7 @@ impl Add for DateTime { impl Sub for DateTime { type Output = DateTime; + #[track_caller] fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).expect("`DateTime - Days` out of range") } diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index dfa2b181b5..4c306b5e7f 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1982,6 +1982,7 @@ impl Add for NaiveDate { type Output = NaiveDate; #[inline] + #[track_caller] fn add(self, rhs: TimeDelta) -> NaiveDate { self.checked_add_signed(rhs).expect("`NaiveDate + TimeDelta` overflowed") } @@ -1998,6 +1999,7 @@ impl Add for NaiveDate { /// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDate { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: TimeDelta) { *self = self.add(rhs); } @@ -2030,6 +2032,7 @@ impl AddAssign for NaiveDate { impl Add for NaiveDate { type Output = NaiveDate; + #[track_caller] fn add(self, months: Months) -> Self::Output { self.checked_add_months(months).expect("`NaiveDate + Months` out of range") } @@ -2059,6 +2062,7 @@ impl Add for NaiveDate { impl Sub for NaiveDate { type Output = NaiveDate; + #[track_caller] fn sub(self, months: Months) -> Self::Output { self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") } @@ -2073,6 +2077,7 @@ impl Sub for NaiveDate { impl Add for NaiveDate { type Output = NaiveDate; + #[track_caller] fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).expect("`NaiveDate + Days` out of range") } @@ -2087,6 +2092,7 @@ impl Add for NaiveDate { impl Sub for NaiveDate { type Output = NaiveDate; + #[track_caller] fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).expect("`NaiveDate - Days` out of range") } @@ -2134,6 +2140,7 @@ impl Sub for NaiveDate { type Output = NaiveDate; #[inline] + #[track_caller] fn sub(self, rhs: TimeDelta) -> NaiveDate { self.checked_sub_signed(rhs).expect("`NaiveDate - TimeDelta` overflowed") } @@ -2151,6 +2158,7 @@ impl Sub for NaiveDate { /// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDate { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: TimeDelta) { *self = self.sub(rhs); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 604e6654c5..0723c4866f 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1631,6 +1631,7 @@ impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn add(self, rhs: TimeDelta) -> NaiveDateTime { self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") } @@ -1650,6 +1651,7 @@ impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn add(self, rhs: Duration) -> NaiveDateTime { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1669,6 +1671,7 @@ impl Add for NaiveDateTime { /// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: TimeDelta) { *self = self.add(rhs); } @@ -1686,6 +1689,7 @@ impl AddAssign for NaiveDateTime { /// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] + #[track_caller] fn add_assign(&mut self, rhs: Duration) { *self = self.add(rhs); } @@ -1701,6 +1705,7 @@ impl Add for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn add(self, rhs: FixedOffset) -> NaiveDateTime { self.checked_add_offset(rhs).expect("`NaiveDateTime + FixedOffset` out of range") } @@ -1754,6 +1759,7 @@ impl Add for NaiveDateTime { impl Add for NaiveDateTime { type Output = NaiveDateTime; + #[track_caller] fn add(self, rhs: Months) -> Self::Output { self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") } @@ -1819,6 +1825,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn sub(self, rhs: TimeDelta) -> NaiveDateTime { self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") } @@ -1838,6 +1845,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn sub(self, rhs: Duration) -> NaiveDateTime { let rhs = TimeDelta::from_std(rhs) .expect("overflow converting from core::time::Duration to TimeDelta"); @@ -1859,6 +1867,7 @@ impl Sub for NaiveDateTime { /// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: TimeDelta) { *self = self.sub(rhs); } @@ -1876,6 +1885,7 @@ impl SubAssign for NaiveDateTime { /// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] + #[track_caller] fn sub_assign(&mut self, rhs: Duration) { *self = self.sub(rhs); } @@ -1891,6 +1901,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; #[inline] + #[track_caller] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { self.checked_sub_offset(rhs).expect("`NaiveDateTime - FixedOffset` out of range") } @@ -1930,6 +1941,7 @@ impl Sub for NaiveDateTime { impl Sub for NaiveDateTime { type Output = NaiveDateTime; + #[track_caller] fn sub(self, rhs: Months) -> Self::Output { self.checked_sub_months(rhs).expect("`NaiveDateTime - Months` out of range") } @@ -2002,6 +2014,7 @@ impl Sub for NaiveDateTime { impl Add for NaiveDateTime { type Output = NaiveDateTime; + #[track_caller] fn add(self, days: Days) -> Self::Output { self.checked_add_days(days).expect("`NaiveDateTime + Days` out of range") } @@ -2016,6 +2029,7 @@ impl Add for NaiveDateTime { impl Sub for NaiveDateTime { type Output = NaiveDateTime; + #[track_caller] fn sub(self, days: Days) -> Self::Output { self.checked_sub_days(days).expect("`NaiveDateTime - Days` out of range") } diff --git a/src/naive/mod.rs b/src/naive/mod.rs index ff94584c6e..4cc205595c 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -61,6 +61,7 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] + #[track_caller] pub const fn first_day(&self) -> NaiveDate { expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`") } @@ -113,6 +114,7 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] + #[track_caller] pub const fn last_day(&self) -> NaiveDate { expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`") } @@ -167,6 +169,7 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] + #[track_caller] pub const fn days(&self) -> RangeInclusive { // `expect` doesn't work because `RangeInclusive` is not `Copy` match self.checked_days() { diff --git a/src/time_delta.rs b/src/time_delta.rs index 123a781485..a622ab818e 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -105,6 +105,7 @@ impl TimeDelta { /// Panics when the duration is out of bounds. #[inline] #[must_use] + #[track_caller] pub const fn weeks(weeks: i64) -> TimeDelta { expect(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") } @@ -132,6 +133,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[track_caller] pub const fn days(days: i64) -> TimeDelta { expect(TimeDelta::try_days(days), "TimeDelta::days out of bounds") } @@ -158,6 +160,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[track_caller] pub const fn hours(hours: i64) -> TimeDelta { expect(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") } @@ -183,6 +186,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds. #[inline] #[must_use] + #[track_caller] pub const fn minutes(minutes: i64) -> TimeDelta { expect(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") } @@ -207,6 +211,7 @@ impl TimeDelta { /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). #[inline] #[must_use] + #[track_caller] pub const fn seconds(seconds: i64) -> TimeDelta { expect(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") } @@ -230,6 +235,7 @@ impl TimeDelta { /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. #[inline] + #[track_caller] pub const fn milliseconds(milliseconds: i64) -> TimeDelta { expect(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") } @@ -514,6 +520,7 @@ impl Neg for TimeDelta { type Output = TimeDelta; #[inline] + #[track_caller] fn neg(self) -> TimeDelta { let (secs_diff, nanos) = match self.nanos { 0 => (0, 0), @@ -526,6 +533,7 @@ impl Neg for TimeDelta { impl Add for TimeDelta { type Output = TimeDelta; + #[track_caller] fn add(self, rhs: TimeDelta) -> TimeDelta { self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed") } @@ -534,12 +542,14 @@ impl Add for TimeDelta { impl Sub for TimeDelta { type Output = TimeDelta; + #[track_caller] fn sub(self, rhs: TimeDelta) -> TimeDelta { self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed") } } impl AddAssign for TimeDelta { + #[track_caller] fn add_assign(&mut self, rhs: TimeDelta) { let new = self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed"); *self = new; @@ -547,6 +557,7 @@ impl AddAssign for TimeDelta { } impl SubAssign for TimeDelta { + #[track_caller] fn sub_assign(&mut self, rhs: TimeDelta) { let new = self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed"); *self = new; @@ -556,6 +567,7 @@ impl SubAssign for TimeDelta { impl Mul for TimeDelta { type Output = TimeDelta; + #[track_caller] fn mul(self, rhs: i32) -> TimeDelta { self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed") } @@ -564,6 +576,7 @@ impl Mul for TimeDelta { impl Div for TimeDelta { type Output = TimeDelta; + #[track_caller] fn div(self, rhs: i32) -> TimeDelta { self.checked_div(rhs).expect("`i32` is zero") } From c14b4599d07ef36ffa1f8a531fb0bc7eb3b42464 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 13 Feb 2026 17:21:31 +0100 Subject: [PATCH 981/999] Bump version to 0.4.44 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a544fcbfe7..50b6fa7590 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.43" +version = "0.4.44" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 120686c82c5da90377e815edb82c9a80b6b4f2be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 04:54:14 +0000 Subject: [PATCH 982/999] Bump codecov/codecov-action from 5 to 6 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index e972e08311..baff89f2bf 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -28,7 +28,7 @@ jobs: - name: Generate code coverage run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: From c6063e6f5a03a48c6feeac3eb5b51ab4cb902759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:54:35 +0000 Subject: [PATCH 983/999] Update similar-asserts requirement from 1.6.1 to 2.0.0 Updates the requirements on [similar-asserts](https://github.com/mitsuhiko/similar-asserts) to permit the latest version. - [Changelog](https://github.com/mitsuhiko/similar-asserts/blob/main/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/similar-asserts/compare/1.6.1...2.0.0) --- updated-dependencies: - dependency-name: similar-asserts dependency-version: 2.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 50b6fa7590..c81647059b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } -similar-asserts = { version = "1.6.1" } +similar-asserts = { version = "2.0.0" } bincode = { version = "1.3.0" } windows-bindgen = { version = "0.66" } # MSRV is 1.74 From f14ead46c0feeed8d5b2471c7a55069fbc822d01 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Sun, 24 May 2026 15:22:26 -0700 Subject: [PATCH 984/999] fix(tz): reject TZ offset hour of 24 to avoid FixedOffset overflow --- src/offset/local/tz_info/rule.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index d428395db0..cd507d8c18 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -357,7 +357,8 @@ fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> { fn parse_offset(cursor: &mut Cursor) -> Result { let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; - if !(0..=24).contains(&hour) { + // The offset must fit in a `FixedOffset`, which is limited to ±23:59:59. + if !(0..=23).contains(&hour) { return Err(Error::InvalidTzString("invalid offset hour")); } if !(0..=59).contains(&minute) { @@ -846,6 +847,16 @@ mod tests { Ok(()) } + #[test] + fn test_invalid_offset_hour() { + // An offset of 24 hours does not fit in a `FixedOffset` and must be rejected. + assert!(TransitionRule::from_tz_string(b"FOO24", false).is_err()); + assert!(TransitionRule::from_tz_string(b"FOO+24", false).is_err()); + assert!(TransitionRule::from_tz_string(b"FOO-24", false).is_err()); + // The maximum valid offset is 23:59:59. + assert!(TransitionRule::from_tz_string(b"FOO23:59:59", false).is_ok()); + } + #[test] fn test_all_year_dst() -> Result<(), Error> { let tz_string = b"EST5EDT,0/0,J365/25"; From 881f9ab2f7068c98173cce86ce1a3642848ce98a Mon Sep 17 00:00:00 2001 From: Carus Cha Date: Thu, 4 Jun 2026 16:57:37 +0800 Subject: [PATCH 985/999] tz_data: fix tzdata locations on Android --- src/offset/local/tz_data.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/offset/local/tz_data.rs b/src/offset/local/tz_data.rs index 3533e56d56..920537a7fc 100644 --- a/src/offset/local/tz_data.rs +++ b/src/offset/local/tz_data.rs @@ -25,9 +25,14 @@ pub(crate) fn for_zone(tz_string: &str) -> Result>> { /// Open the `tzdata` file of Android from the environment variables. #[cfg(target_os = "android")] fn open_android_tz_data_file() -> Result { - for (env_var, path) in - [("ANDROID_DATA", "/misc/zoneinfo"), ("ANDROID_ROOT", "/usr/share/zoneinfo")] - { + for (env_var, path) in [ + // Default root path: /apex/com.android.tzdata + ("ANDROID_TZDATA_ROOT", "/etc/tz"), + // Default root path: /data + ("ANDROID_DATA", "/misc/zoneinfo/current"), + // Default root path: /system + ("ANDROID_ROOT", "/usr/share/zoneinfo"), + ] { if let Ok(env_value) = std::env::var(env_var) { if let Ok(file) = File::open(format!("{}{}/tzdata", env_value, path)) { return Ok(file); From 170338250e836976a211e64728ec956e45e78a39 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 4 Jun 2026 14:04:14 +0200 Subject: [PATCH 986/999] Prepare 0.4.45 release --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index b1d4b976df..b5bae89fbf 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2026-01-09 -version: 0.4.43 +date-released: 2026-06-04 +version: 0.4.45 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index c81647059b..5bad64d53b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.44" +version = "0.4.45" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 0cadfb4fd1816265a03d4509f5b2d750f9e1d6c6 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 12 Jun 2026 14:06:17 +0200 Subject: [PATCH 987/999] Ignore advisory for proc-macro-error2 --- deny.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deny.toml b/deny.toml index 2fe92c8a44..edca3624ba 100644 --- a/deny.toml +++ b/deny.toml @@ -4,5 +4,9 @@ private = { ignore = true } [advisories] yanked = "deny" -# bincode is deprecated -- just a dev-dependency in this crate -ignore = ["RUSTSEC-2025-0141"] +ignore = [ + # bincode is deprecated -- just a dev-dependency in this crate + "RUSTSEC-2025-0141", + # proc-macro-error2 is unmaintained, via defmt + "RUSTSEC-2026-0173", +] From bf76cb9639a4040c66d9d68251ea7a79d43a2d5d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 12 Jun 2026 14:09:46 +0200 Subject: [PATCH 988/999] ci: drop Taplo linting --- .github/workflows/lint.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c8ac89ffca..14236404fa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -40,15 +40,6 @@ jobs: env: RUSTFLAGS: "-Dwarnings" - toml: - runs-on: ubuntu-latest - container: - image: tamasfe/taplo:0.8.0 - steps: - - uses: actions/checkout@v6 - - run: taplo lint - - run: taplo fmt --check --diff - cargo-deny: runs-on: ubuntu-latest steps: From 2c8426a3bdb456e476932978b27480d68adc48b2 Mon Sep 17 00:00:00 2001 From: Dave Sims <87742609+W4HO-ham@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:39:49 -0703 Subject: [PATCH 989/999] Accept longer time zone designations and '_' in TZif files TimeZoneName::new rejected designations outside 3-7 characters or [0-9A-Za-z+-]. zic and glibc accept longer names and '_', so a valid /etc/localtime using such a designation failed to parse and chrono::Local silently fell back to UTC. Store the designation in a 32-byte length-prefixed buffer (TimeZoneName/LocalTimeType stay Copy, no API change), accept up to 31 characters, truncating longer names instead of failing the parse, and allow '_'. Adds regression tests. Fixes #1793 --- src/offset/local/tz_info/timezone.rs | 44 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 77497994ae..0bcad4a2ae 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -489,7 +489,7 @@ impl LeapSecond { #[derive(Copy, Clone, Eq, PartialEq)] struct TimeZoneName { /// Length-prefixed string buffer - bytes: [u8; 8], + bytes: [u8; 32], } impl TimeZoneName { @@ -499,23 +499,28 @@ impl TimeZoneName { /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII /// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with /// POSIX requirements for time zone abbreviations. + /// + /// That is a *should*, not a *must*: `zic` and glibc accept longer + /// designations and a slightly wider character set (notably `_`). Rejecting + /// an over-long designation would fail the whole TZif parse and make `Local` + /// fall back to UTC, so we keep the offset and simply truncate the name to + /// the 31-byte buffer, and additionally allow `_`. fn new(input: &[u8]) -> Result { - let len = input.len(); + // Truncate over-long designations rather than failing the parse. + let len = input.len().min(31); - if !(3..=7).contains(&len) { - return Err(Error::LocalTimeType( - "time zone name must have between 3 and 7 characters", - )); + if len < 3 { + return Err(Error::LocalTimeType("time zone name must have at least 3 characters")); } - let mut bytes = [0; 8]; - bytes[0] = input.len() as u8; + let mut bytes = [0; 32]; + bytes[0] = len as u8; let mut i = 0; while i < len { let b = input[i]; match b { - b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' | b'_' => {} _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } @@ -528,14 +533,8 @@ impl TimeZoneName { /// Returns time zone name as a byte slice fn as_bytes(&self) -> &[u8] { - match self.bytes[0] { - 3 => &self.bytes[1..4], - 4 => &self.bytes[1..5], - 5 => &self.bytes[1..6], - 6 => &self.bytes[1..7], - 7 => &self.bytes[1..8], - _ => unreachable!(), - } + let len = self.bytes[0] as usize; + &self.bytes[1..=len] } /// Check if two time zone names are equal @@ -797,7 +796,16 @@ mod tests { assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); + // Designations longer than the historical 6/7-char limit are accepted: + // `zic`/glibc allow them, and rejecting one fails the whole TZif file. + assert_eq!(TimeZoneName::new(b"12345678")?.as_bytes(), b"12345678"); + // Custom designations containing `_` parse too (regression: a name like + // this previously made the entire zone fail to load, so `Local` fell + // back to UTC instead of the file's real offset). + assert_eq!(TimeZoneName::new(b"Solar_Custom_Zone")?.as_bytes(), b"Solar_Custom_Zone"); + // Over-long designations are truncated to fit the buffer (keeping the + // offset) rather than failing the parse. + assert_eq!(TimeZoneName::new(&[b'A'; 64])?.as_bytes(), &[b'A'; 31]); assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_)))); Ok(()) From 75926fce51a0e2a7b731b2b907a1f34d34b7a282 Mon Sep 17 00:00:00 2001 From: nvxbug Date: Tue, 16 Jun 2026 15:01:44 +0530 Subject: [PATCH 990/999] fix(tz): avoid i64 overflow in find_local_time_type_from_local --- src/offset/local/tz_info/timezone.rs | 37 ++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 0bcad4a2ae..9dfe2d508a 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -237,8 +237,10 @@ impl<'a> TimeZoneRef<'a> { // the end and start here refers to where the time starts prior to the transition // and where it ends up after. not the temporal relationship. - let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset); - let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset); + let transition_end = + transition.unix_leap_time.saturating_add(i64::from(after_ltt.ut_offset)); + let transition_start = + transition.unix_leap_time.saturating_add(i64::from(prev.ut_offset)); match transition_start.cmp(&transition_end) { Ordering::Greater => { @@ -953,4 +955,35 @@ mod tests { Ok(()) } + + #[test] + fn test_find_local_time_type_from_local_overflow() -> Result<(), Error> { + use crate::MappedLocalTime; + + let local = + crate::NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + // A transition timestamp near `i64::MAX` plus a positive offset overflows when mapping a + // local time; near `i64::MIN` a negative offset underflows. + let near_max = TimeZone::new( + vec![Transition::new(i64::MAX, 1)], + vec![LocalTimeType::UTC, LocalTimeType::new(3600, false, Some(b"FOO"))?], + Vec::new(), + None, + )?; + assert_eq!( + near_max.find_local_time_type_from_local(local)?, + MappedLocalTime::Single(LocalTimeType::UTC) + ); + + let near_min = TimeZone::new( + vec![Transition::new(i64::MIN, 0)], + vec![LocalTimeType::new(-3600, false, Some(b"FOO"))?, LocalTimeType::UTC], + Vec::new(), + None, + )?; + assert!(near_min.find_local_time_type_from_local(local).is_ok()); + + Ok(()) + } } From a71c212cdc2204a8868b8cc74e4ee48da2a4b5dc Mon Sep 17 00:00:00 2001 From: nvxbug Date: Wed, 17 Jun 2026 12:55:23 +0530 Subject: [PATCH 991/999] fix(tz): avoid out-of-bounds slice on partial tzdata index entry --- src/offset/local/tz_data.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/offset/local/tz_data.rs b/src/offset/local/tz_data.rs index 920537a7fc..fd6d2e87b9 100644 --- a/src/offset/local/tz_data.rs +++ b/src/offset/local/tz_data.rs @@ -102,10 +102,12 @@ impl TzDataIndexes { fn new(mut reader: impl Read, header: &TzDataHeader) -> Result { let mut buf = vec![0; header.data_offset.saturating_sub(header.index_offset) as usize]; reader.read_exact(&mut buf)?; - // replace chunks with array_chunks when it's stable + // replace chunks_exact with array_chunks when it's stable. A trailing partial chunk + // (when the index region size is not a multiple of `ENTRY_LEN`) is not a valid entry + // and must be skipped, otherwise the slicing below reads out of bounds. Ok(TzDataIndexes { indexes: buf - .chunks(ENTRY_LEN) + .chunks_exact(ENTRY_LEN) .filter_map(|chunk| { from_bytes_until_nul(&chunk[..TZ_NAME_LEN]).map(|name| { let name = name.to_bytes().to_vec().into_boxed_slice(); @@ -174,6 +176,23 @@ const TZDATA_VERSION_LEN: usize = 12; #[cfg(test)] mod tests { use super::*; + use std::io::Cursor; + + #[test] + fn test_tzdata_index_partial_entry() { + // An index region whose size is not a multiple of `ENTRY_LEN` leaves a trailing + // partial chunk that is too short to slice as an entry. + let mut data = Vec::new(); + data.extend_from_slice(b"tzdata2024a\0"); + data.extend_from_slice(&24u32.to_be_bytes()); // index_offset + data.extend_from_slice(&44u32.to_be_bytes()); // data_offset, index region = 20 bytes + data.extend_from_slice(&100u32.to_be_bytes()); // zonetab_offset + data.extend_from_slice(&[0u8; 20]); + let header = TzDataHeader::new(&mut Cursor::new(&data[..])).unwrap(); + let indexes = + TzDataIndexes::new::(&mut Cursor::new(&data[24..]), &header).unwrap(); + assert!(indexes.indexes.is_empty()); + } #[test] fn test_ohos_tzdata_header_and_index() { From bcfa377ec881d2b21ccab05de356adfb5958b21c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 04:42:20 +0000 Subject: [PATCH 992/999] Bump actions/checkout from 6 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- .github/workflows/lint.yml | 8 ++++---- .github/workflows/test.yml | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index baff89f2bf..9caae21012 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,7 +17,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 # nightly is required for --doctests, see cargo-llvm-cov#2 - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 14236404fa..57c3d228e3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc @@ -43,13 +43,13 @@ jobs: cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: EmbarkStudios/cargo-deny-action@v2 check-doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} @@ -60,7 +60,7 @@ jobs: cffconvert: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: persist-credentials: false - uses: citation-file-format/cffconvert-github-action@2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c16e2b1f0..279ef73c0c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -33,7 +33,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@master with: toolchain: "1.62.0" @@ -50,7 +50,7 @@ jobs: rust_version: ["stable", "beta", "nightly"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} @@ -69,7 +69,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -98,7 +98,7 @@ jobs: target: [thumbv6m-none-eabi, x86_64-fortanix-unknown-sgx] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -118,7 +118,7 @@ jobs: ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -131,7 +131,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown @@ -150,7 +150,7 @@ jobs: target: [wasm32-wasip1] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} @@ -165,7 +165,7 @@ jobs: - x86_64-unknown-illumos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} @@ -176,7 +176,7 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always @@ -187,7 +187,7 @@ jobs: check-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@nightly - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: From 3ffcd1b107fd3085bc184a1f5f7445b97a0d655f Mon Sep 17 00:00:00 2001 From: nvxbug Date: Thu, 18 Jun 2026 21:54:29 +0530 Subject: [PATCH 993/999] fix: clamp offset minute rounding to avoid invalid +24:00 --- src/format/formatting.rs | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index d95c0acbef..b5ef253a57 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -410,8 +410,10 @@ impl OffsetFormat { OffsetPrecision::Hours } OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { - // Round seconds to the nearest minute. - let minutes = (off + 30) / 60; + // Round seconds to the nearest minute, but never roll over to 24:00. A + // `FixedOffset` may be as large as 23:59:59, and rounding that up would emit + // an invalid "+24:00" that no offset parser (including ours) accepts. + let minutes = ((off + 30) / 60).min(24 * 60 - 1); mins = (minutes % 60) as u8; hours = (minutes / 60) as u8; if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 { @@ -948,4 +950,41 @@ mod tests { ], ); } + + #[cfg(feature = "alloc")] + #[test] + fn test_offset_minute_rounding_no_24h_rollover() { + // The largest representable `FixedOffset` is 23:59:59. Rounding it (or anything + // from 23:59:30 onwards) to whole minutes must not produce "+24:00", which is not + // a valid offset and does not round-trip through `parse_from_rfc3339`. + for (secs, colons, expected) in [ + (86_399, Colons::Colon, "+23:59"), // +23:59:59 + (86_399, Colons::None, "+2359"), + (86_370, Colons::Colon, "+23:59"), // +23:59:30, the bottom of the rounding window + (-86_399, Colons::Colon, "-23:59"), + (-86_370, Colons::None, "-2359"), + // Regular rounding is unaffected: 23:59:29 still rounds down to 23:59. + (86_369, Colons::Colon, "+23:59"), + ] { + let off = FixedOffset::east_opt(secs).unwrap(); + let mut s = String::new(); + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons, + allow_zulu: false, + padding: Pad::Zero, + } + .format(&mut s, off) + .unwrap(); + assert_eq!(s, expected, "offset {secs}s formatted incorrectly"); + } + + // The bug was reachable through the public `to_rfc3339`, and the result must parse + // back rather than fail with `OutOfRange`. + let dt = + FixedOffset::east_opt(86_399).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + let rfc = dt.to_rfc3339(); + assert_eq!(rfc, "2020-01-01T00:00:00+23:59"); + assert!(crate::DateTime::parse_from_rfc3339(&rfc).is_ok()); + } } From b04c42fda83ff5fbbad0503ae4325c92edc3c01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Sun, 28 Jun 2026 20:51:25 +0200 Subject: [PATCH 994/999] Replace `target_arch = "wasm32"` with `target_family = "wasm"` Web-target cfg gates (the wasm-bindgen / js-sys code paths and the `wasm-pack test --node` harness) currently key on `target_arch = "wasm32"`. As a result they silently disappear when building for `wasm64-unknown-unknown`: the `wasm-bindgen` and `js-sys` optional dependencies are not pulled in, `Utc::now()`, `Local`'s offset lookup, and the `js_sys::Date` conversions fall back to the non-web default (which calls `SystemTime::now()` and panics on wasm), and the `tests/wasm.rs` harness is compiled out. Switching the gates to `target_family = "wasm"` makes the same web backend apply to any WebAssembly target, including `wasm64`. The existing `not(any(target_os = "emscripten", target_os = "wasi"[, "linux"]))` exclusions are preserved, so behavior on `wasm32-unknown-emscripten`, `wasm32-wasip*` and emscripten/WASI variants is unchanged. `target_family = "wasm"` has been stable since Rust 1.54, well below chrono's MSRV (1.62). `wasm64-unknown-unknown` is a Tier 3 target, so it cannot be added to CI, but the same code paths are still exercised by the existing `wasm-pack test --node --features wasmbind` job on `wasm32`. --- Cargo.toml | 4 ++-- src/datetime/mod.rs | 6 +++--- src/datetime/tests.rs | 2 +- src/offset/local/mod.rs | 4 ++-- src/offset/utc.rs | 6 +++--- tests/wasm.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5bad64d53b..49604ae34d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ rkyv = { version = "0.7.43", optional = true, default-features = false } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } defmt = { version = "1.0.1", optional = true } -[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] +[target.'cfg(all(target_family = "wasm", 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 @@ -64,7 +64,7 @@ similar-asserts = { version = "2.0.0" } bincode = { version = "1.3.0" } windows-bindgen = { version = "0.66" } # MSRV is 1.74 -[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] +[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" [package.metadata.docs.rs] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 272976a015..2e76c1f92b 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1954,7 +1954,7 @@ impl From> for SystemTime { } #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] @@ -1965,7 +1965,7 @@ impl From for DateTime { } #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] @@ -1976,7 +1976,7 @@ impl From<&js_sys::Date> for DateTime { } #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 67bee1049e..1da69d9c7c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1387,7 +1387,7 @@ fn test_subsecond_part() { // `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`). // See https://github.com/rust-lang/rust/issues/44394. #[test] -#[cfg(all(feature = "std", not(all(target_arch = "wasm32", target_os = "wasi"))))] +#[cfg(all(feature = "std", not(all(target_family = "wasm", target_os = "wasi"))))] fn test_from_system_time() { use std::time::{Duration, SystemTime, UNIX_EPOCH}; diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 5741d24103..e3e7b0f959 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -35,7 +35,7 @@ mod tz_data; not(unix), not(windows), not(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )) @@ -57,7 +57,7 @@ mod inner { } #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] diff --git a/src/offset/utc.rs b/src/offset/utc.rs index c1a27c8598..29e3090721 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -7,7 +7,7 @@ use core::fmt; #[cfg(all( feature = "now", not(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) )) @@ -87,7 +87,7 @@ impl Utc { /// let now_with_offset = Utc::now().with_timezone(&offset); /// ``` #[cfg(not(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) )))] @@ -100,7 +100,7 @@ impl Utc { /// Returns a `DateTime` which corresponds to the current date and time. #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux")) ))] diff --git a/tests/wasm.rs b/tests/wasm.rs index ceb9b3de97..c175efdf66 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -6,7 +6,7 @@ //! The check will fail if the local timezone does not match one of the timezones defined below. #![cfg(all( - target_arch = "wasm32", + target_family = "wasm", feature = "wasmbind", feature = "clock", not(any(target_os = "emscripten", target_os = "wasi")) From 453792130cc3b4420a090348854e3ce2f170168c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 10 Jul 2026 12:06:23 +0200 Subject: [PATCH 995/999] Apply suggestions from Clippy 1.97 --- src/datetime/serde.rs | 2 +- src/offset/local/tz_info/rule.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index bc00d674d0..cde0f6238d 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1339,7 +1339,7 @@ mod tests { } let tz = TestTimeZone; - assert_eq!(format!("{:?}", &tz), "TEST"); + assert_eq!(format!("{:?}", tz), "TEST"); let dt = tz.with_ymd_and_hms(2023, 4, 24, 21, 10, 33).unwrap(); let encoded = serde_json::to_string(&dt).unwrap(); diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index cd507d8c18..19159f62da 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -149,9 +149,9 @@ impl AlternateTime { let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; - let current_year = match UtcDateTime::from_timespec(unix_time) { - Ok(dt) => dt.year, - Err(error) => return Err(error), + let current_year = { + let dt = UtcDateTime::from_timespec(unix_time)?; + dt.year }; // Check if the current year is valid for the following computations From 64428325ad7b1ef9e8b09079ad5a57681475473c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 10 Jul 2026 12:06:46 +0200 Subject: [PATCH 996/999] Drop scheduled CI runs --- .github/workflows/lint.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 57c3d228e3..bc6bf4a813 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,8 +8,6 @@ on: push: branches: [main, 0.5.x] pull_request: - schedule: - - cron: "38 6 * * 5" jobs: lint: From 7fa24eac9514db65eb8bddf7fadeca71b68e414a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:42:30 +0000 Subject: [PATCH 997/999] Bump actions/setup-node from 6 to 7 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 279ef73c0c..06bddd1210 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,7 +137,7 @@ jobs: targets: wasm32-unknown-unknown components: rustfmt - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 - uses: jetli/wasm-pack-action@v0.4.0 # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment # with the host system. From 7b6436f65e0c7ec2f9a7cdea88af1f3851a01f6f Mon Sep 17 00:00:00 2001 From: n0liu Date: Wed, 29 Jul 2026 11:02:07 +0800 Subject: [PATCH 998/999] Fix reversed `NaiveDate` day and week iterators `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` implemented `DoubleEndedIterator` by walking their single `value` cursor backwards from the start, while `size_hint`/`len` reported the number of steps up to `NaiveDate::MAX`. As a result reversing them (e.g. `iter_days().take(n).rev()`) produced dates far outside the intended range. Track an exclusive upper bound `end` so each iterator models the half-open range `[value, end)`: `next` advances `value` from the front and `next_back` lowers `end` from the back. `nth_back` skips in O(1) so `take(n).rev()` stays cheap even though the range extends to `NaiveDate::MAX`. Forward iteration and `size_hint` are unchanged. Fixes #1757 --- src/naive/date/mod.rs | 86 ++++++++++++++++++++++++++++++++++------- src/naive/date/tests.rs | 24 +++++++++++- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 4c306b5e7f..0dd0408c02 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -1341,14 +1341,16 @@ impl NaiveDate { /// } /// assert_eq!(count, 4); /// - /// for d in NaiveDate::from_ymd_opt(2016, 3, 1).unwrap().iter_days().rev().take(4) { + /// // The iterator is double-ended: reversing a bounded range yields the same + /// // dates in reverse order. + /// for d in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).rev() { /// count -= 1; /// assert_eq!(d, expected[count]); /// } /// ``` #[inline] pub const fn iter_days(&self) -> NaiveDateDaysIterator { - NaiveDateDaysIterator { value: *self } + NaiveDateDaysIterator { value: *self, end: NaiveDate::MAX } } /// Returns an iterator that steps by weeks across all representable dates. @@ -1372,14 +1374,24 @@ impl NaiveDate { /// } /// assert_eq!(count, 4); /// - /// for d in NaiveDate::from_ymd_opt(2016, 3, 19).unwrap().iter_weeks().rev().take(4) { + /// // The iterator is double-ended: reversing a bounded range yields the same + /// // dates in reverse order. + /// for d in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).rev() { /// count -= 1; /// assert_eq!(d, expected[count]); /// } /// ``` #[inline] pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator { - NaiveDateWeeksIterator { value: *self } + // Align the exclusive upper bound to the weekly grid starting at `*self`, so + // that `next_back` yields the same weeks as `next`, only in reverse order. + let weeks = NaiveDate::MAX.signed_duration_since(*self).num_weeks(); + // `weeks * 7` days is at most `NaiveDate::MAX - *self`, so this never overflows. + let end = match self.checked_add_days(Days::new((weeks * 7) as u64)) { + Some(end) => end, + None => *self, + }; + NaiveDateWeeksIterator { value: *self, end } } /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] @@ -2213,21 +2225,26 @@ impl From for NaiveDate { #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateDaysIterator { value: NaiveDate, + // Exclusive upper bound. The iterator yields the half-open range `[value, end)`; + // `next` advances `value` from the front and `next_back` lowers `end` from the back. + end: NaiveDate, } impl Iterator for NaiveDateDaysIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - // We return the current value, and have no way to return `NaiveDate::MAX`. + if self.value >= self.end { + return None; + } let current = self.value; - // This can't panic because current is < NaiveDate::MAX: + // `succ_opt()` can't return `None` because `current < end <= NaiveDate::MAX`. self.value = current.succ_opt()?; Some(current) } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_days(); + let exact_size = self.end.signed_duration_since(self.value).num_days(); (exact_size as usize, Some(exact_size as usize)) } } @@ -2236,10 +2253,25 @@ impl ExactSizeIterator for NaiveDateDaysIterator {} impl DoubleEndedIterator for NaiveDateDaysIterator { fn next_back(&mut self) -> Option { - // We return the current value, and have no way to return `NaiveDate::MIN`. - let current = self.value; - self.value = current.pred_opt()?; - Some(current) + if self.value >= self.end { + return None; + } + // `pred_opt()` can't return `None` because `end > value >= NaiveDate::MIN`. + self.end = self.end.pred_opt()?; + Some(self.end) + } + + fn nth_back(&mut self, n: usize) -> Option { + // Skipping from the back in O(1) keeps `take(k).rev()` cheap despite the + // iterator spanning up to `NaiveDate::MAX`. + if n >= self.len() { + self.end = self.value; + return None; + } + // The `n`-th element from the back is `end - (n + 1)` days, which stays + // within `[value, end)` and so can't underflow past `NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new(n as u64 + 1))?; + Some(self.end) } } @@ -2249,19 +2281,27 @@ impl FusedIterator for NaiveDateDaysIterator {} #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateWeeksIterator { value: NaiveDate, + // Exclusive upper bound, aligned to the weekly grid starting at `value`. The + // iterator yields `[value, end)` in weekly steps; `next` advances `value` from + // the front and `next_back` lowers `end` from the back. + end: NaiveDate, } impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { + if self.value >= self.end { + return None; + } let current = self.value; + // Can't overflow because `current + 7 days <= end <= NaiveDate::MAX`. self.value = current.checked_add_days(Days::new(7))?; Some(current) } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_weeks(); + let exact_size = self.end.signed_duration_since(self.value).num_weeks(); (exact_size as usize, Some(exact_size as usize)) } } @@ -2270,9 +2310,25 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - let current = self.value; - self.value = current.checked_sub_days(Days::new(7))?; - Some(current) + if self.value >= self.end { + return None; + } + // Can't underflow because `end - 7 days >= value >= NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new(7))?; + Some(self.end) + } + + fn nth_back(&mut self, n: usize) -> Option { + // Skipping from the back in O(1) keeps `take(k).rev()` cheap despite the + // iterator spanning up to `NaiveDate::MAX`. + if n >= self.len() { + self.end = self.value; + return None; + } + // The `n`-th element from the back is `end - 7 * (n + 1)` days, which stays + // within `[value, end)` and so can't underflow past `NaiveDate::MIN`. + self.end = self.end.checked_sub_days(Days::new((n as u64 + 1) * 7))?; + Some(self.end) } } diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index 0b47ca439e..aba1f8fc29 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -730,9 +730,12 @@ fn test_date_parse_from_str() { #[test] fn test_day_iterator_limit() { + // Forward iteration stops at `NaiveDate::MAX`. assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2); + // Reversing that bounded range yields the same dates, so it is likewise + // limited to 2 elements (previously `rev` walked below the start instead). assert_eq!( - NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).rev().count(), 2 ); } @@ -741,11 +744,28 @@ fn test_day_iterator_limit() { fn test_week_iterator_limit() { assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2); assert_eq!( - NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).rev().count(), 2 ); } +#[test] +fn test_iterator_reverse_matches_forward() { + // Regression test for #1757: reversing a bounded window of the day and week + // iterators must yield the same dates as forward iteration, in reverse order. + let start = NaiveDate::from_ymd_opt(2025, 10, 10).unwrap(); + + let mut days_forward: Vec<_> = start.iter_days().take(10).collect(); + let days_reversed: Vec<_> = start.iter_days().take(10).rev().collect(); + days_forward.reverse(); + assert_eq!(days_reversed, days_forward); + + let mut weeks_forward: Vec<_> = start.iter_weeks().take(10).collect(); + let weeks_reversed: Vec<_> = start.iter_weeks().take(10).rev().collect(); + weeks_forward.reverse(); + assert_eq!(weeks_reversed, weeks_forward); +} + #[test] fn test_weeks_from() { // tests per: https://github.com/chronotope/chrono/issues/961 From 6adaa5240c26fecb7bd9077334a91f8f67f4f3fe Mon Sep 17 00:00:00 2001 From: n0liu Date: Sun, 2 Aug 2026 10:34:06 +0800 Subject: [PATCH 999/999] Return None from from_isoywd_opt for out-of-range years `from_isoywd_opt` is a fallible `_opt` constructor documented to return `None` for out-of-range years, but it panicked for `i32::MIN` / `i32::MAX` because the internal `year - 1` / `year + 1` step overflowed. Use checked arithmetic and return `None` there; such years are far outside the range of `NaiveDate` anyway. Fixes #1808 --- src/naive/date/mod.rs | 20 ++++++++++++++++---- src/naive/date/tests.rs | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/naive/date/mod.rs b/src/naive/date/mod.rs index 0dd0408c02..e1eb371bb4 100644 --- a/src/naive/date/mod.rs +++ b/src/naive/date/mod.rs @@ -322,8 +322,14 @@ impl NaiveDate { let delta = flags.isoweek_delta(); let (year, ordinal, flags) = if weekord <= delta { // ordinal < 1, previous year - let prevflags = YearFlags::from_year(year - 1); - (year - 1, weekord + prevflags.ndays() - delta, prevflags) + // `year - 1` would overflow for `year == i32::MIN`; such a year is + // well out of range for `NaiveDate`, so return `None` as documented. + let year = match year.checked_sub(1) { + Some(year) => year, + None => return None, + }; + let prevflags = YearFlags::from_year(year); + (year, weekord + prevflags.ndays() - delta, prevflags) } else { let ordinal = weekord - delta; let ndays = flags.ndays(); @@ -332,8 +338,14 @@ impl NaiveDate { (year, ordinal, flags) } else { // ordinal > ndays, next year - let nextflags = YearFlags::from_year(year + 1); - (year + 1, ordinal - ndays, nextflags) + // `year + 1` would overflow for `year == i32::MAX`; such a year is + // well out of range for `NaiveDate`, so return `None` as documented. + let year = match year.checked_add(1) { + Some(year) => year, + None => return None, + }; + let nextflags = YearFlags::from_year(year); + (year, ordinal - ndays, nextflags) } }; NaiveDate::from_ordinal_and_flags(year, ordinal, flags) diff --git a/src/naive/date/tests.rs b/src/naive/date/tests.rs index aba1f8fc29..ba948b586c 100644 --- a/src/naive/date/tests.rs +++ b/src/naive/date/tests.rs @@ -222,6 +222,11 @@ fn test_date_from_isoywd() { assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None); + + // Extreme years are out of range for `NaiveDate` and must return `None`, not + // panic (the internal `year - 1` / `year + 1` step used to overflow here). + assert_eq!(from_isoywd(i32::MIN, 1, Weekday::Mon), None); + assert_eq!(from_isoywd(i32::MAX, 1, Weekday::Mon), None); } #[test]