diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 52596a46..81983948 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,7 +43,7 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.48.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,clock,serde,windows-sys --color=always -- --color=always diff --git a/Cargo.toml b/Cargo.toml index e4792d18..3433a0db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2018" +rust-version = "1.56.0" [lib] name = "chrono" @@ -38,7 +39,7 @@ 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-sys = { version = "0.48.0", features = ["Win32_System_Time", "Win32_Foundation"], optional = true } +windows-sys = { version = "0.48.0", features = ["Win32_System_Time", "Win32_System_SystemInformation", "Win32_Foundation"], optional = true } [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/benches/chrono.rs b/benches/chrono.rs index e825b5f4..0f692ff7 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)); } }) }); diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index f691ea3d..00000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.48" diff --git a/src/date.rs b/src/date.rs index ea9a9f4b..a394006c 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: TimeDelta) -> 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: TimeDelta) -> 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) -> TimeDelta { 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 abe623bb..2125efa5 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -105,6 +105,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 } } @@ -134,6 +135,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(); @@ -148,6 +150,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()) } @@ -165,6 +168,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() @@ -173,6 +177,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() } @@ -180,6 +185,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() } @@ -203,6 +209,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() } @@ -226,6 +233,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() } @@ -249,6 +257,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() } @@ -259,6 +268,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() } @@ -269,6 +279,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() } @@ -279,18 +290,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) } @@ -298,6 +312,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) } @@ -306,6 +321,7 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); @@ -318,6 +334,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)? @@ -329,6 +346,7 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[must_use] pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); @@ -341,6 +359,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)? @@ -351,6 +370,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)? @@ -361,6 +381,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)? @@ -371,23 +392,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) -> TimeDelta { 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 = @@ -648,6 +673,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()) @@ -658,6 +684,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()) @@ -691,6 +718,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; @@ -734,6 +762,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, @@ -758,6 +787,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)) } @@ -766,6 +796,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, @@ -793,6 +824,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 5a428119..372e54f0 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -158,6 +158,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, @@ -184,6 +185,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>, @@ -282,6 +284,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, @@ -311,6 +314,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>, @@ -411,6 +415,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, @@ -437,6 +442,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>, @@ -534,6 +540,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, @@ -563,6 +570,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>, @@ -663,6 +671,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, @@ -689,6 +698,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>, @@ -783,6 +793,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, @@ -824,6 +835,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>, @@ -925,6 +937,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, @@ -951,6 +964,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>, @@ -1042,6 +1056,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, @@ -1071,6 +1086,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 f81f8744..6ea2f113 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1873,3 +1873,38 @@ fn test_datetime_sub_assign_local() { assert_eq!(datetime_sub, datetime - TimeDelta::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/format/mod.rs b/src/format/mod.rs index 726ae2e9..1fc7bf2b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -863,6 +863,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, @@ -875,6 +876,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, @@ -898,6 +900,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, @@ -910,6 +913,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/parse.rs b/src/format/parse.rs index fa9b781e..23da524f 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1377,6 +1377,28 @@ 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 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 -0800", Err(INVALID)), // bad character! ]; @@ -1515,3 +1537,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))); +} diff --git a/src/format/parsed.rs b/src/format/parsed.rs index ad854e90..cd5d109b 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -122,6 +122,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() } @@ -1287,4 +1288,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()); + } } diff --git a/src/format/scan.rs b/src/format/scan.rs index e70d7b8c..50315fef 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()..]; } @@ -340,11 +340,15 @@ 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.as_bytes().iter().position(|c| !c.is_ascii_alphabetic()).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) = s.split_at(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") { offset_hours(0) @@ -358,8 +362,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[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))?; diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 5bfd1aa7..6d205653 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. @@ -227,6 +233,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 +241,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/lib.rs b/src/lib.rs index d108b73e..8077fa87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -519,15 +519,3 @@ impl fmt::Debug for OutOfRange { #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} - -/// 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/month.rs b/src/month.rs index 4ff42ba2..a127ca4e 100644 --- a/src/month.rs +++ b/src/month.rs @@ -29,7 +29,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)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { @@ -66,6 +66,7 @@ impl Month { /// ----------- | --------- | ---------- | --- | --------- /// `m.succ()`: | `February` | `March` | `...` | `January` #[inline] + #[must_use] pub const fn succ(&self) -> Month { match *self { Month::January => Month::February, @@ -89,6 +90,7 @@ impl Month { /// ----------- | --------- | ---------- | --- | --------- /// `m.pred()`: | `December` | `January` | `...` | `November` #[inline] + #[must_use] pub const fn pred(&self) -> Month { match *self { Month::January => Month::December, @@ -112,6 +114,7 @@ impl Month { /// -------------------------| --------- | ---------- | --- | ----- /// `m.number_from_month()`: | 1 | 2 | `...` | 12 #[inline] + #[must_use] pub const fn number_from_month(&self) -> u32 { match *self { Month::January => 1, @@ -136,6 +139,7 @@ impl Month { /// /// assert_eq!(Month::January.name(), "January") /// ``` + #[must_use] pub const fn name(&self) -> &'static str { match *self { Month::January => "January", @@ -334,4 +338,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); + } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 4271145e..14b6bc3f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -75,6 +75,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(); @@ -94,6 +95,7 @@ impl NaiveWeek { /// assert!(week.last_day() >= date); /// ``` #[inline] + #[must_use] pub fn last_day(&self) -> NaiveDate { self.first_day() + TimeDelta::days(6) } @@ -113,6 +115,7 @@ impl NaiveWeek { /// assert!(days.contains(&date)); /// ``` #[inline] + #[must_use] pub fn days(&self) -> RangeInclusive { self.first_day()..=self.last_day() } @@ -257,6 +260,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") } @@ -280,6 +284,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)?) @@ -290,6 +295,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") } @@ -314,6 +320,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)?) @@ -325,6 +332,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") } @@ -373,6 +381,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(); @@ -410,6 +419,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") } @@ -434,8 +444,9 @@ 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 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); @@ -453,6 +464,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") } @@ -469,6 +481,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, @@ -549,6 +562,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); @@ -579,6 +593,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_sub_months(self, months: Months) -> Option { if months.0 == 0 { return Some(self); @@ -652,6 +667,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_add_days(self, days: Days) -> Option { if days.0 == 0 { return Some(self); @@ -675,6 +691,7 @@ impl NaiveDate { /// None /// ); /// ``` + #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { if days.0 == 0 { return Some(self); @@ -706,6 +723,7 @@ impl NaiveDate { /// assert_eq!(dt.time(), t); /// ``` #[inline] + #[must_use] pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { NaiveDateTime::new(*self, time) } @@ -718,6 +736,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") } @@ -741,6 +760,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)) } @@ -753,6 +773,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") } @@ -778,6 +799,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, @@ -810,6 +832,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") } @@ -835,6 +858,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, @@ -853,6 +877,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") } @@ -878,6 +903,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, @@ -926,6 +952,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") } @@ -944,6 +971,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)) } @@ -953,6 +981,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") } @@ -971,6 +1000,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)) } @@ -993,6 +1023,7 @@ impl NaiveDate { /// 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 fn checked_add_signed(self, rhs: TimeDelta) -> Option { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); @@ -1024,6 +1055,7 @@ impl NaiveDate { /// 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 fn checked_sub_signed(self, rhs: TimeDelta) -> Option { let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); @@ -1059,6 +1091,7 @@ impl NaiveDate { /// 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 fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { let year1 = self.year(); let year2 = rhs.year(); @@ -1072,6 +1105,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()) { @@ -1114,6 +1148,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, @@ -1157,6 +1192,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)) } @@ -1165,6 +1201,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, @@ -1184,6 +1221,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, @@ -1519,7 +1557,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. @@ -1557,7 +1596,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. @@ -1605,7 +1645,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)?) } } @@ -1788,6 +1829,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 { @@ -2170,7 +2217,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.unsigned_abs() + 1)), None ); @@ -2421,6 +2468,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] @@ -2984,4 +3034,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()); + } } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index f324ed4a..58fc03f4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -145,6 +145,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") @@ -172,6 +173,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) } @@ -198,6 +200,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) } @@ -229,6 +232,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 = i32::try_from(days) @@ -312,7 +316,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))?; @@ -372,6 +376,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()); @@ -404,6 +409,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()) @@ -431,6 +437,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()) @@ -470,6 +477,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()) @@ -492,6 +500,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 } @@ -513,6 +522,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 } @@ -534,6 +544,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() } @@ -603,6 +614,7 @@ impl NaiveDateTime { /// 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 fn checked_add_signed(self, rhs: TimeDelta) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); @@ -639,6 +651,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 }) } @@ -704,6 +717,7 @@ impl NaiveDateTime { /// 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 fn checked_sub_signed(self, rhs: TimeDelta) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); @@ -740,6 +754,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 }) } @@ -747,6 +762,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 }) } @@ -754,6 +770,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 }) } @@ -796,6 +813,7 @@ impl NaiveDateTime { /// 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 fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) } @@ -830,6 +848,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, @@ -873,6 +892,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)) } @@ -894,6 +914,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 40695fa7..79509f02 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 b9e9ef2b..f2303e13 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -115,6 +115,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 863e21b1..8bb7738c 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -210,6 +210,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") } @@ -235,6 +236,7 @@ impl NaiveTime { /// assert!(from_hms_opt(23, 59, 60).is_none()); /// ``` #[inline] + #[must_use] pub const fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { NaiveTime::from_hms_nano_opt(hour, min, sec, 0) } @@ -247,6 +249,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") } @@ -274,6 +277,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) @@ -288,6 +292,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") } @@ -315,6 +320,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)) } @@ -327,6 +333,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") } @@ -354,6 +361,7 @@ impl NaiveTime { /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); /// ``` #[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 { return None; @@ -370,6 +378,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") } @@ -395,6 +404,7 @@ impl NaiveTime { /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); /// ``` #[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 { return None; @@ -488,6 +498,7 @@ impl NaiveTime { /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::hours(-7)), /// (from_hms(20, 4, 5), -86_400)); /// ``` + #[must_use] pub fn overflowing_add_signed(&self, mut rhs: TimeDelta) -> (NaiveTime, i64) { let mut secs = self.secs; let mut frac = self.frac; @@ -571,6 +582,7 @@ impl NaiveTime { /// (from_hms(1, 4, 5), -86_400)); /// ``` #[inline] + #[must_use] pub fn overflowing_sub_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { let (time, rhs) = self.overflowing_add_signed(-rhs); (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) @@ -630,6 +642,7 @@ impl NaiveTime { /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), /// TimeDelta::seconds(61)); /// ``` + #[must_use] pub fn signed_duration_since(self, rhs: NaiveTime) -> TimeDelta { // | | :leap| | | | | | | :leap| | // | | : | | | | | | | : | | @@ -692,6 +705,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, @@ -737,6 +751,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 be638ef5..2a7ca09e 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 const 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 const 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 8a872174..46e15337 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/local/tz_info/mod.rs b/src/offset/local/tz_info/mod.rs index 3143c30d..780e15ac 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] -const 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/parser.rs b/src/offset/local/tz_info/parser.rs index 78c667ac..47cc0377 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", diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 50e092c8..369e317a 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; @@ -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 956064ba..2fe24d2e 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -620,8 +620,8 @@ const 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; @@ -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> { diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 60ac56e3..ddf799a4 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -8,15 +8,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::io; -use std::mem; +use core::mem::MaybeUninit; +use std::io::Error; use std::ptr; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::result::Result; use windows_sys::Win32::Foundation::FILETIME; use windows_sys::Win32::Foundation::SYSTEMTIME; -use windows_sys::Win32::System::Time::FileTimeToSystemTime; -use windows_sys::Win32::System::Time::GetTimeZoneInformation; +use windows_sys::Win32::System::SystemInformation::GetLocalTime; use windows_sys::Win32::System::Time::SystemTimeToFileTime; use windows_sys::Win32::System::Time::SystemTimeToTzSpecificLocalTime; use windows_sys::Win32::System::Time::TzSpecificLocalTimeToSystemTime; @@ -24,268 +23,143 @@ use windows_sys::Win32::System::Time::TzSpecificLocalTimeToSystemTime; use super::{FixedOffset, Local}; use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; -pub(super) fn now() -> DateTime { - let datetime = tm_to_datetime(Timespec::now().local()); - datetime.single().expect("invalid time") -} - -/// 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: i32::from(local), - // 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) -> LocalResult> { - 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_opt( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - - match time { - Some(time) => { - let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); - let datetime = DateTime::from_utc(date.and_time(time) - offset, offset); - // #TODO - there should be ambiguous cases, investigate? - LocalResult::Single(datetime) - } - None => LocalResult::None, - } -} - -/// 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 } +/// # 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()); + } } - - /// 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; -fn time_to_file_time(sec: i64) -> FILETIME { - let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64; - FILETIME { dwLowDateTime: t as u32, dwHighDateTime: (t >> 32) as u32 } +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); + + let local_sys_time = match local { + false => LocalSysTime::from_utc_time(naive_sys_time), + true => LocalSysTime::from_local_time(naive_sys_time), + }; + + if let Ok(local) = local_sys_time { + return LocalResult::Single(local.datetime()); + } + LocalResult::None } -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 +struct LocalSysTime { + inner: SYSTEMTIME, + offset: i32, } -fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME { - unsafe { - let mut ft = mem::zeroed(); - SystemTimeToFileTime(sys, &mut ft); - ft +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)?; + 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(st.wHour as u32, st.wMinute as u32, st.wSecond as u32); + + let offset = FixedOffset::east_opt(self.offset).unwrap(); + DateTime::from_utc(date.and_time(time) - offset, offset) } } -fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME { - let mut sys: SYSTEMTIME = unsafe { mem::zeroed() }; - sys.wSecond = tm.tm_sec as u16; - sys.wMinute = tm.tm_min as u16; - sys.wHour = tm.tm_hour as u16; - sys.wDay = tm.tm_mday as u16; - sys.wDayOfWeek = tm.tm_wday as u16; - sys.wMonth = (tm.tm_mon + 1) as u16; - sys.wYear = (tm.tm_year + 1900) as u16; - sys +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 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); - - fn yday(year: i32, month: i32, day: i32) -> i32 { - let leap = if month > 2 { - if year % 4 == 0 { - 1 - } else { - 2 - } - } else { +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 - }; - let july = i32::from(month > 7); - - (month - 1) * 30 + month / 2 + (day - 1) - leap + july - } + ) + }; + // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can + // assume the value is initialized. + Ok(unsafe { sys_time.assume_init() }) } -macro_rules! call { - ($name:ident($($arg:expr),*)) => { - if $name($($arg),*) == 0 { - panic!(concat!(stringify!($name), " failed with: {}"), - io::Error::last_os_error()); - } - } -} - -fn time_to_local_tm(sec: i64, tm: &mut Tm) { - let ft = time_to_file_time(sec); +pub(crate) fn utc_to_local_time(utc_time: &SYSTEMTIME) -> Result { + let mut local = MaybeUninit::::uninit(); unsafe { - let mut utc = mem::zeroed(); - let mut local = mem::zeroed(); - call!(FileTimeToSystemTime(&ft, &mut utc)); - call!(SystemTimeToTzSpecificLocalTime(ptr::null(), &utc, &mut local)); - system_time_to_tm(&local, tm); - - let local = system_time_to_file_time(&local); - let local_sec = file_time_to_unix_seconds(&local); - - let mut tz = mem::zeroed(); - GetTimeZoneInformation(&mut tz); - - // 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 }; - } + 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() }) } -fn utc_tm_to_time(tm: &Tm) -> i64 { - 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) - } -} - -fn local_tm_to_time(tm: &Tm) -> i64 { - unsafe { - let mut ft = mem::zeroed(); - let mut utc = mem::zeroed(); - let sys_time = tm_to_system_time(tm); - call!(TzSpecificLocalTimeToSystemTime(ptr::null(), &sys_time, &mut utc)); - call!(SystemTimeToFileTime(&utc, &mut ft)); - file_time_to_unix_seconds(&ft) - } +/// 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) } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 09d0714e..ee1fe7e2 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,8 @@ 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"), diff --git a/src/offset/utc.rs b/src/offset/utc.rs index cfed754b..aeaeb672 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/time_delta.rs b/src/time_delta.rs index 538b123f..c9df0365 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -74,6 +74,7 @@ impl TimeDelta { /// 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) -> TimeDelta { let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); TimeDelta::seconds(secs) @@ -83,6 +84,7 @@ impl TimeDelta { /// 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) -> TimeDelta { let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); TimeDelta::seconds(secs) @@ -92,6 +94,7 @@ impl TimeDelta { /// 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) -> TimeDelta { let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); TimeDelta::seconds(secs) @@ -101,6 +104,7 @@ impl TimeDelta { /// 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) -> TimeDelta { let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); TimeDelta::seconds(secs) @@ -110,6 +114,7 @@ impl TimeDelta { /// Panics when the duration is more than `i64::MAX` seconds /// or less than `i64::MIN` seconds. #[inline] + #[must_use] pub fn seconds(seconds: i64) -> TimeDelta { let d = TimeDelta { secs: seconds, nanos: 0 }; if d < MIN || d > MAX { @@ -211,6 +216,7 @@ impl TimeDelta { } /// Add two durations, returning `None` if overflow occurred. + #[must_use] pub fn checked_add(&self, rhs: &TimeDelta) -> Option { let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); let mut nanos = self.nanos + rhs.nanos; @@ -229,6 +235,7 @@ impl TimeDelta { } /// Subtract two durations, returning `None` if overflow occurred. + #[must_use] pub fn checked_sub(&self, rhs: &TimeDelta) -> 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 5bfa4e2e..b8250379 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -10,6 +10,26 @@ use crate::OutOfRange; /// 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 std::convert::TryFrom; +/// +/// let monday = "Monday".parse::().unwrap(); +/// assert_eq!(monday, Weekday::Mon); +/// +/// 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 +/// 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 = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -37,6 +57,7 @@ impl Weekday { /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` #[inline] + #[must_use] pub const fn succ(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Tue, @@ -55,6 +76,7 @@ impl Weekday { /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` #[inline] + #[must_use] pub const fn pred(&self) -> Weekday { match *self { Weekday::Mon => Weekday::Sun, @@ -109,7 +131,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] diff --git a/tests/dateutils.rs b/tests/dateutils.rs index aabab29a..75b5b21b 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)] @@ -51,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