diff --git a/Cargo.toml b/Cargo.toml index 87eb8b27..c3de968d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,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/README.md b/README.md index c14fbcc9..5fe189e4 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 diff --git a/benches/chrono.rs b/benches/chrono.rs index c1f61849..e825b5f4 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| { @@ -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())); } @@ -56,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 @@ -109,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); diff --git a/ci/core-test/src/lib.rs b/ci/core-test/src/lib.rs index c4d7e9cc..4af7d2ec 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 24ce9c57..ea9a9f4b 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; @@ -53,6 +54,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 { @@ -288,15 +290,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`. @@ -332,15 +326,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] @@ -532,7 +517,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 +527,23 @@ 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) + } +} + +// 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)) } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8b5dc3fb..2c1aaf34 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")] @@ -31,7 +32,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::{Date, Datelike, Months, TimeDelta, Timelike, Weekday}; +#[allow(deprecated)] +use crate::Date; +use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; /// documented at re-export site #[cfg(feature = "serde")] @@ -145,18 +148,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()) } @@ -169,8 +163,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] @@ -203,13 +197,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] @@ -227,13 +220,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] @@ -251,13 +243,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] @@ -533,10 +524,10 @@ impl DateTime { /// 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> { @@ -579,11 +570,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(); @@ -656,16 +647,20 @@ 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`. #[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 @@ -678,8 +673,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), @@ -688,7 +683,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"); /// ``` @@ -757,7 +752,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"); /// ``` @@ -941,8 +936,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"); @@ -1047,7 +1042,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) } } @@ -1056,7 +1052,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) } } @@ -1207,49 +1205,44 @@ 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(); - 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); @@ -1263,30 +1256,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()) @@ -1311,22 +1294,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!( @@ -1334,9 +1312,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() )) ); @@ -1345,11 +1321,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()); diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 5b50e2fd..5a428119 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -106,7 +106,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)] @@ -115,7 +115,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(), /// }; @@ -142,7 +142,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)] @@ -152,7 +152,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}"#); @@ -231,7 +231,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)] @@ -240,7 +240,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(), /// }; @@ -266,7 +266,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)] @@ -276,7 +276,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}"#); @@ -360,7 +360,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)] @@ -369,7 +369,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(), /// }; @@ -395,7 +395,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)] @@ -405,7 +405,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}"#); @@ -484,7 +484,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)] @@ -493,7 +493,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(), /// }; @@ -518,7 +518,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)] @@ -528,7 +528,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}"#); @@ -612,7 +612,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)] @@ -621,7 +621,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(), /// }; @@ -647,7 +647,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)] @@ -657,7 +657,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}"#); @@ -733,7 +733,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)] @@ -742,7 +742,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(), /// }; @@ -767,7 +767,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)] @@ -777,7 +777,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}"#); @@ -883,7 +883,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(), /// }; @@ -919,7 +919,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}"#); @@ -1001,7 +1001,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(), /// }; @@ -1036,7 +1036,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}"#); @@ -1134,7 +1134,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 c27efabf..37f42c23 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 + TimeDelta::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()), TimeDelta::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"), @@ -296,13 +360,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()); @@ -352,7 +416,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()); @@ -364,31 +435,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) ); @@ -415,30 +507,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) ); @@ -460,7 +576,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("%%"); @@ -517,16 +633,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() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + 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::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + let two_year_ago = + Utc::now().date_naive() - 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)); + 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() + TimeDelta::weeks(12); - assert_eq!(Utc::today().years_since(future), None); + let future = Utc::now().date_naive() + TimeDelta::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 ed0a2a31..6b95e617 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"); @@ -40,6 +40,7 @@ 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; @@ -426,6 +427,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"))))] @@ -448,47 +506,10 @@ 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 core::fmt::Write; use num_integer::{div_floor, mod_floor}; match *item { @@ -564,77 +585,41 @@ 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| { - 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| { @@ -701,19 +686,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} ", - short_weekdays[d.weekday().num_days_from_sunday() as usize], - d.day(), - 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 } @@ -722,10 +695,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, "{:?}T{:?}", d, t)?; - Some(write_local_minus_utc(result, off, false, Colons::Single)) + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) } else { None } @@ -743,6 +713,110 @@ 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 { + 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))] diff --git a/src/format/parse.rs b/src/format/parse.rs index c6a46906..69204d2e 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 e7564e54..71569ae6 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -229,7 +229,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 @@ -1185,9 +1185,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()) }; @@ -1232,7 +1235,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; @@ -1252,16 +1262,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!( @@ -1272,9 +1285,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 6ba2617c..f57ca461 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 @@ -659,13 +663,12 @@ fn test_strftime_docs() { #[cfg(feature = "unstable-locales")] #[test] fn test_strftime_docs_localized() { - use crate::{FixedOffset, TimeZone}; + use crate::{FixedOffset, NaiveDate}; - let dt = FixedOffset::east_opt(34200) + let dt = NaiveDate::from_ymd_opt(2001, 7, 8) + .and_then(|d| d.and_hms_nano_opt(0, 34, 59, 1_026_490_708)) .unwrap() - .ymd_opt(2001, 7, 8) - .unwrap() - .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .and_local_timezone(FixedOffset::east_opt(34200).unwrap()) .unwrap(); // date specifiers diff --git a/src/lib.rs b/src/lib.rs index 63f986ac..44303bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,26 +112,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; //! ``` @@ -147,7 +147,7 @@ //! use chrono::TimeDelta; //! //! // 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)); @@ -161,7 +161,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 @@ -169,14 +169,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), TimeDelta::seconds(-2 * 3600 + 2)); //! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap() + TimeDelta::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() - TimeDelta::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() + 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 @@ -207,7 +207,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"); @@ -219,7 +219,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"))] @@ -259,7 +259,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 @@ -329,7 +329,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"); //! ``` //! @@ -409,6 +409,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 711d6524..5c66ce16 100644 --- a/src/month.rs +++ b/src/month.rs @@ -14,7 +14,7 @@ use crate::OutOfRange; /// ``` /// # use std::convert::TryFrom; /// 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::try_from(u8::try_from(date.month()).unwrap()).ok(); /// assert_eq!(month, Some(Month::October)) @@ -23,7 +23,7 @@ use crate::OutOfRange; /// ``` /// # 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. @@ -31,6 +31,7 @@ use crate::OutOfRange; // Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { /// January January = 0, @@ -177,6 +178,7 @@ impl TryFrom 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 { @@ -317,15 +319,11 @@ mod tests { assert_eq!(Month::try_from(12), Ok(Month::December)); assert_eq!(Month::try_from(13), Err(OutOfRange::new())); - let date = Utc.ymd_opt(2019, 10, 28).unwrap().and_hms_opt(9, 10, 11).unwrap(); - assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October)); + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::try_from(date.month() as u8).ok(), Some(Month::October)); let month = Month::January; - let dt = Utc - .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/date.rs b/src/naive/date.rs index 210bb19b..eb6287f9 100644 --- a/src/naive/date.rs +++ b/src/naive/date.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, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; @@ -191,6 +191,16 @@ 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 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) + } +} + // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, // we use a separate run-time test. #[test] @@ -265,7 +275,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) @@ -299,7 +309,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) @@ -368,18 +378,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 { @@ -422,7 +432,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 @@ -613,7 +623,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 @@ -974,7 +984,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 `TimeDelta` from the current date. @@ -1005,7 +1015,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. @@ -1042,6 +1052,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. /// @@ -1427,7 +1450,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. @@ -1446,7 +1469,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. @@ -1465,7 +1488,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. @@ -1484,7 +1507,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. @@ -1508,7 +1531,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. @@ -1532,7 +1555,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)?) } } @@ -1818,14 +1841,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/datetime/mod.rs b/src/naive/datetime/mod.rs index 363c5ace..a8a5bf3c 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}; @@ -74,6 +75,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, @@ -1623,7 +1625,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) } } @@ -1654,7 +1658,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) } } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 629f12f2..40695fa7 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 1629e8bb..773e1f38 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::time_delta::TimeDelta; +use crate::NaiveDate; use crate::{Datelike, FixedOffset, Utc}; use std::i64; diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3f72c2f6..7d642e91 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -272,20 +272,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; @@ -309,10 +302,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] @@ -377,30 +373,13 @@ pub(super) struct Mdf(pub(super) u32); impl Mdf { #[inline] - fn clamp_month(month: u32) -> u32 { - if month > 12 { - 0 - } else { - month + 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] - fn clamp_day(day: u32) -> u32 { - if day > 31 { - 0 - } else { - day - } - } - - #[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; @@ -427,10 +406,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] @@ -440,10 +422,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] @@ -525,7 +510,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 {:?}", @@ -557,7 +547,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 {:?}", @@ -642,7 +637,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); } @@ -653,11 +648,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); } @@ -676,25 +676,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; @@ -707,7 +707,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); @@ -720,11 +724,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); @@ -732,8 +740,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); @@ -763,7 +775,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); } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f49d2a82..21780ca3 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::{TimeDelta, Timelike}; @@ -76,7 +76,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); /// ``` /// @@ -157,9 +157,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"); /// ``` /// @@ -171,12 +171,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); /// ``` @@ -190,6 +190,17 @@ 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_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) + } +} + impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// @@ -637,13 +648,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 { @@ -1173,7 +1178,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 { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 40c66a2e..0e3b0007 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. @@ -231,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() - ); assert_eq!( format!( "{:?}", FixedOffset::west_opt(86399) .unwrap() - .ymd_opt(2012, 3, 4) + .with_ymd_and_hms(2012, 3, 4, 5, 6, 7) .unwrap() - .and_hms_opt(5, 6, 7) + ), + "2012-03-04T05:06:07-23:59:59".to_string() + ); + 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() diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 6d9cdbdc..8a872174 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 @@ -52,10 +53,13 @@ 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 { /// 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() } @@ -96,6 +100,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()) } @@ -104,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() } @@ -113,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 @@ -145,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()) @@ -179,7 +187,7 @@ impl TimeZone for Local { mod tests { use super::Local; use crate::offset::TimeZone; - use crate::{Datelike, TimeDelta}; + use crate::{Datelike, TimeDelta, Utc}; #[test] fn verify_correct_offsets() { @@ -227,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/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 085b25c1..ae6fb5f8 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 398f07ec..32aa3161 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}; @@ -32,68 +32,40 @@ thread_local! { } 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, + LocalTime { mtime: SystemTime }, + Environment { hash: u64 }, } -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 => match fs::symlink_metadata("/etc/localtime") { +impl Source { + fn new(env_tz: Option<&str>) -> Source { + match env_tz { + 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 // 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")] @@ -115,17 +87,63 @@ 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> { - 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 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 + (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, + }; + + if out_of_date { + self.zone = current_zone(env_ref); + } + + self.last_checked = now; + self.source = new_source; + } } if !local { diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 40d5a76f..09d0714e 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. @@ -206,6 +208,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. /// @@ -213,7 +236,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() } @@ -225,15 +249,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), @@ -248,7 +265,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() } @@ -260,14 +281,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), @@ -284,7 +302,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() } @@ -298,14 +320,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), @@ -431,6 +450,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 @@ -454,6 +475,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 173edd63..ed6d5e63 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,16 +38,22 @@ 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, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 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( + since = "0.4.23", + note = "use `Utc::now()` instead, potentially with `.date_naive()`" + )] + #[allow(deprecated)] pub fn today() -> Date { Utc::now().date() } diff --git a/src/round.rs b/src/round.rs index c1415fc9..0143fa18 100644 --- a/src/round.rs +++ b/src/round.rs @@ -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, TimeDelta, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, DurationRound, TimeDelta, 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(TimeDelta::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, TimeDelta, TimeZone, Utc}; - /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// # use chrono::{DateTime, DurationRound, TimeDelta, 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(TimeDelta::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, TimeDelta, 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(TimeDelta::days(365)), @@ -256,8 +256,8 @@ pub enum RoundingError { /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, TimeDelta, 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, TimeDelta, 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(TimeDelta::days(300 * 365)), @@ -270,7 +270,7 @@ pub enum RoundingError { /// /// ``` rust /// # use chrono::{DateTime, DurationRound, TimeDelta, 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(TimeDelta::days(1)), Err(RoundingError::TimestampExceedsLimit),); /// ``` @@ -306,12 +306,20 @@ impl std::error::Error for RoundingError { mod tests { use super::{DurationRound, SubsecRound, TimeDelta}; 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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::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(TimeDelta::minutes(10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" diff --git a/src/time_delta.rs b/src/time_delta.rs index 12b4df49..58f2ff5a 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -483,6 +483,24 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { (this / other, this % other) } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for TimeDelta { + 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 = TimeDelta { secs, nanos }; + + if duration < MIN || duration > MAX { + Err(arbitrary::Error::IncorrectFormat) + } else { + Ok(duration) + } + } +} + #[cfg(test)] mod tests { use super::{OutOfRangeError, TimeDelta, MAX, MIN}; diff --git a/src/weekday.rs b/src/weekday.rs index eda2737e..09edd408 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -12,6 +12,7 @@ use crate::OutOfRange; /// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. Mon = 0, diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 2a5acebb..ed40a8a7 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();