diff --git a/tests/compile-fail/invalid_utc_datetime.rs b/tests/compile-fail/invalid_utc_datetime.rs new file mode 100644 index 000000000..66df781e8 --- /dev/null +++ b/tests/compile-fail/invalid_utc_datetime.rs @@ -0,0 +1,9 @@ +use time::macros::utc_datetime; + +fn main() { + let _ = utc_datetime!(2021-000 0:00); + let _ = utc_datetime!(2021-001 24:00); + let _ = utc_datetime!(2021-001 0:00 0); + let _ = utc_datetime!(2021-001 0:00 UTC); + let _ = utc_datetime!(2021-001 0:00 UTC x); +} diff --git a/tests/compile-fail/invalid_utc_datetime.stderr b/tests/compile-fail/invalid_utc_datetime.stderr new file mode 100644 index 000000000..fb62ba8ba --- /dev/null +++ b/tests/compile-fail/invalid_utc_datetime.stderr @@ -0,0 +1,29 @@ +error: invalid component: ordinal was 0 + --> ../tests/compile-fail/invalid_utc_datetime.rs:4:32 + | +4 | let _ = utc_datetime!(2021-000 0:00); + | ^^^ + +error: invalid component: hour was 24 + --> ../tests/compile-fail/invalid_utc_datetime.rs:5:36 + | +5 | let _ = utc_datetime!(2021-001 24:00); + | ^^ + +error: unexpected token: 0 + --> ../tests/compile-fail/invalid_utc_datetime.rs:6:41 + | +6 | let _ = utc_datetime!(2021-001 0:00 0); + | ^ + +error: unexpected token: UTC + --> ../tests/compile-fail/invalid_utc_datetime.rs:7:41 + | +7 | let _ = utc_datetime!(2021-001 0:00 UTC); + | ^^^ + +error: unexpected token: UTC + --> ../tests/compile-fail/invalid_utc_datetime.rs:8:41 + | +8 | let _ = utc_datetime!(2021-001 0:00 UTC x); + | ^^^ diff --git a/tests/derives.rs b/tests/derives.rs index 2c76eaeea..6552f709e 100644 --- a/tests/derives.rs +++ b/tests/derives.rs @@ -5,12 +5,11 @@ use std::hash::Hash; use time::error::{self, ConversionRange, IndeterminateOffset, TryFromParsed}; use time::ext::NumericalDuration; use time::format_description::{self, modifier, well_known, Component, BorrowedFormatItem, OwnedFormatItem}; -use time::macros::{date, offset, time}; +use time::macros::{date, offset, time, utc_datetime, datetime}; use time::parsing::Parsed; use time::{Duration, Error, Month, Time, Weekday}; #[allow(deprecated)] use time::Instant; -use time_macros::datetime; macro_rules! assert_cloned_eq { ($x:expr) => { @@ -36,6 +35,7 @@ fn clone() { assert_cloned_eq!(offset!(UTC)); assert_cloned_eq!(datetime!(2021-001 0:00)); assert_cloned_eq!(datetime!(2021-001 0:00 UTC)); + assert_cloned_eq!(utc_datetime!(2021-001 0:00)); assert_cloned_eq!(Weekday::Monday); assert_cloned_eq!(Month::January); assert_cloned_eq!(Duration::ZERO); @@ -96,6 +96,7 @@ fn hash() { offset!(UTC).hash(&mut hasher); datetime!(2021-001 0:00).hash(&mut hasher); datetime!(2021-001 0:00 UTC).hash(&mut hasher); + utc_datetime!(2021-001 0:00).hash(&mut hasher); Weekday::Monday.hash(&mut hasher); Month::January.hash(&mut hasher); #[allow(deprecated)] @@ -146,6 +147,7 @@ fn debug() { } debug_all! { + utc_datetime!(2021-001 0:00); Duration::ZERO; IndeterminateOffset; ConversionRange; diff --git a/tests/formatting.rs b/tests/formatting.rs index 724379dbd..446d6f1a7 100644 --- a/tests/formatting.rs +++ b/tests/formatting.rs @@ -4,7 +4,7 @@ use std::num::NonZeroU8; use time::format_description::well_known::iso8601::{DateKind, OffsetPrecision, TimePrecision}; use time::format_description::well_known::{iso8601, Iso8601, Rfc2822, Rfc3339}; use time::format_description::{self, BorrowedFormatItem, OwnedFormatItem}; -use time::macros::{date, datetime, format_description as fd, offset, time}; +use time::macros::{date, datetime, format_description as fd, offset, time, utc_datetime}; use time::{OffsetDateTime, Time}; #[test] @@ -13,6 +13,10 @@ fn rfc_2822() -> time::Result<()> { datetime!(2021-01-02 03:04:05 UTC).format(&Rfc2822)?, "Sat, 02 Jan 2021 03:04:05 +0000" ); + assert_eq!( + utc_datetime!(2021-01-02 03:04:05).format(&Rfc2822)?, + "Sat, 02 Jan 2021 03:04:05 +0000" + ); assert_eq!( datetime!(2021-01-02 03:04:05 +06:07).format(&Rfc2822)?, "Sat, 02 Jan 2021 03:04:05 +0607" @@ -26,6 +30,10 @@ fn rfc_2822() -> time::Result<()> { datetime!(1885-01-01 01:01:01 UTC).format(&Rfc2822), Err(time::error::Format::InvalidComponent("year")) )); + assert!(matches!( + utc_datetime!(1885-01-01 01:01:01).format(&Rfc2822), + Err(time::error::Format::InvalidComponent("year")) + )); assert!(matches!( datetime!(2000-01-01 00:00:00 +00:00:01).format(&Rfc2822), Err(time::error::Format::InvalidComponent("offset_second")) @@ -562,6 +570,36 @@ fn display_odt() { ); } +#[test] +fn format_udt() -> time::Result<()> { + let format_description = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); + + assert_eq!( + utc_datetime!(1970-01-01 0:00).format(format_description)?, + "1970-01-01 00:00:00.0" + ); + assert!(utc_datetime!(1970-01-01 0:00) + .format_into(&mut io::sink(), format_description) + .is_ok()); + assert_eq!( + utc_datetime!(1970-01-01 0:00).format(&OwnedFormatItem::from(format_description))?, + "1970-01-01 00:00:00.0" + ); + assert!(utc_datetime!(1970-01-01 0:00) + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok()); + + Ok(()) +} + +#[test] +fn display_udt() { + assert_eq!( + utc_datetime!(1970-01-01 0:00).to_string(), + "1970-01-01 0:00:00.0 +00" + ); +} + #[test] fn insufficient_type_information() { let assert_insufficient_type_information = |res| { diff --git a/tests/main.rs b/tests/main.rs index 6e20098bc..a21d33cc4 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -133,6 +133,7 @@ require_all_features! { mod serde; mod serde_helpers; mod time; + mod utc_date_time; mod utc_offset; mod util; mod weekday; diff --git a/tests/meta.rs b/tests/meta.rs index 74beef1cc..0d138b378 100644 --- a/tests/meta.rs +++ b/tests/meta.rs @@ -20,8 +20,8 @@ use time::parsing::{Parsable, Parsed}; #[allow(deprecated)] use time::Instant; use time::{ - error, ext, Date, Duration, Error, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, - Weekday, + error, ext, Date, Duration, Error, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, + UtcOffset, Weekday, }; #[allow(clippy::cognitive_complexity)] // all test the same thing @@ -44,6 +44,7 @@ fn alignment() { assert_alignment!(Duration, 8); assert_alignment!(OffsetDateTime, 4); assert_alignment!(PrimitiveDateTime, 4); + assert_alignment!(UtcDateTime, 4); assert_alignment!(Time, 4); assert_alignment!(UtcOffset, 1); assert_alignment!(error::ComponentRange, 8); @@ -122,6 +123,7 @@ fn size() { assert_size!(Duration, 16, 16); assert_size!(OffsetDateTime, 16, 16); assert_size!(PrimitiveDateTime, 12, 12); + assert_size!(UtcDateTime, 12, 12); assert_size!(Time, 8, 8); assert_size!(UtcOffset, 3, 4); assert_size!(error::ComponentRange, 56, 56); @@ -382,6 +384,40 @@ assert_impl! { @'a; PrimitiveDateTime: Unpin, UnwindSafe, } +assert_impl! { @'a; UtcDateTime: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Deserialize<'a>, + Display, + Hash, + Ord, + PartialEq, + PartialEq, + PartialEq, + PartialOrd, + PartialOrd, + PartialOrd, + Serialize, + Sub, + Sub, + Sub, + Sub, + SubAssign, + SubAssign, + TryFrom, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} assert_impl! { @'a; Time: Add, Add, diff --git a/tests/offset_date_time.rs b/tests/offset_date_time.rs index c0c652fca..b36891b25 100644 --- a/tests/offset_date_time.rs +++ b/tests/offset_date_time.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use std::time::{Duration as StdDuration, SystemTime}; use time::ext::{NumericalDuration, NumericalStdDuration}; -use time::macros::{date, datetime, offset, time}; +use time::macros::{date, datetime, offset, time, utc_datetime}; use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Weekday}; #[test] @@ -96,6 +96,33 @@ fn checked_to_offset() { ); } +#[test] +fn to_utc() { + assert_eq!(datetime!(2000-01-01 0:00 +1).to_utc().year(), 1999); + assert_eq!( + datetime!(0000-001 0:00 UTC).to_utc(), + utc_datetime!(0000-001 0:00), + ); +} + +#[test] +fn to_utc_panic() { + assert_panic!(datetime!(+999999-12-31 23:59:59 -1).to_utc()); + assert_panic!(datetime!(-999999-01-01 00:00:00 +1).to_utc()); +} + +#[test] +fn checked_to_utc() { + assert_eq!( + datetime!(2000-01-01 0:00 +1) + .checked_to_utc() + .map(|udt| udt.year()), + Some(1999) + ); + assert_eq!(datetime!(+999999-12-31 23:59:59 -1).checked_to_utc(), None); + assert_eq!(datetime!(-999999-01-01 00:00:00 +1).checked_to_utc(), None); +} + #[test] fn from_unix_timestamp() { assert_eq!( diff --git a/tests/parsing.rs b/tests/parsing.rs index 01e9ba67f..25f69f696 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -3,11 +3,11 @@ use std::num::{NonZeroU16, NonZeroU8}; use time::format_description::modifier::Ignore; use time::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; use time::format_description::{modifier, BorrowedFormatItem, Component, OwnedFormatItem}; -use time::macros::{date, datetime, offset, time}; +use time::macros::{date, datetime, offset, time, utc_datetime}; use time::parsing::Parsed; use time::{ error, format_description as fd, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, - UtcOffset, Weekday, + UtcDateTime, UtcOffset, Weekday, }; macro_rules! invalid_literal { @@ -72,6 +72,51 @@ fn rfc_2822() -> time::Result<()> { datetime!(2022-01-01 06:06:59.999_999_999 +06:07), ); + assert_eq!( + UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 GMT", &Rfc2822)?, + utc_datetime!(2021-01-02 03:04:05), + ); + assert_eq!( + UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 UT", &Rfc2822)?, + utc_datetime!(2021-01-02 03:04:05), + ); + assert_eq!( + UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0000", &Rfc2822)?, + utc_datetime!(2021-01-02 03:04:05), + ); + assert_eq!( + UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822)?, + datetime!(2021-01-02 03:04:05 +06:07).to_utc(), + ); + assert_eq!( + UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822)?, + datetime!(2021-01-02 03:04:05 -06:07).to_utc(), + ); + assert_eq!( + UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 Z", &Rfc2822)?, + utc_datetime!(2021-12-31 23:59:59.999_999_999), + ); + assert_eq!( + UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 z", &Rfc2822)?, + utc_datetime!(2021-12-31 23:59:59.999_999_999), + ); + assert_eq!( + UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 a", &Rfc2822)?, + utc_datetime!(2021-12-31 23:59:59.999_999_999), + ); + assert_eq!( + UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 A", &Rfc2822)?, + utc_datetime!(2021-12-31 23:59:59.999_999_999), + ); + assert_eq!( + UtcDateTime::parse("Fri, 31 Dec 2021 17:52:60 -0607", &Rfc2822)?, + datetime!(2021-12-31 17:52:59.999_999_999 -06:07).to_utc(), + ); + assert_eq!( + UtcDateTime::parse("Sat, 01 Jan 2022 06:06:60 +0607", &Rfc2822)?, + datetime!(2022-01-01 06:06:59.999_999_999 +06:07).to_utc(), + ); + assert_eq!( Date::parse("Sat, 02 Jan 2021 03:04:05 GMT", &Rfc2822)?, date!(2021-01-02) @@ -569,6 +614,43 @@ fn iso_8601() { OffsetDateTime::parse("20210102T0304Z", &Iso8601::DEFAULT), Ok(datetime!(2021-01-02 03:04:00 UTC)) ); + + assert_eq!( + UtcDateTime::parse("2021-01-02T03:04:05Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-01-02 03:04:05)) + ); + assert_eq!( + UtcDateTime::parse("2021-002T03:04:05Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-002 03:04:05)) + ); + assert_eq!( + UtcDateTime::parse("2021-W01-2T03:04:05Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-W 01-2 03:04:05)) + ); + assert_eq!( + UtcDateTime::parse("-002021-01-02T03:04:05+01:00", &Iso8601::DEFAULT), + Ok(datetime!(-002021-01-02 03:04:05 +01:00).to_utc()) + ); + assert_eq!( + UtcDateTime::parse("20210102T03.1Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-01-02 03:06:00)) + ); + assert_eq!( + UtcDateTime::parse("2021002T0304.1Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-002 03:04:06)) + ); + assert_eq!( + UtcDateTime::parse("2021W012T030405.1-0100", &Iso8601::DEFAULT), + Ok(datetime!(2021-W 01-2 03:04:05.1 -01:00).to_utc()) + ); + assert_eq!( + UtcDateTime::parse("20210102T03Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-01-02 03:00:00)) + ); + assert_eq!( + UtcDateTime::parse("20210102T0304Z", &Iso8601::DEFAULT), + Ok(utc_datetime!(2021-01-02 03:04:00)) + ); assert_eq!(UtcOffset::parse("+07", &Iso8601::DEFAULT), Ok(offset!(+7))); assert_eq!( UtcOffset::parse("+0304", &Iso8601::DEFAULT), @@ -648,6 +730,73 @@ fn iso_8601_error() { error::TryFromParsed::InsufficientInformation { .. } )) )); + + assert!(matches!( + UtcDateTime::parse("20210102T03:04Z", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("20210102T03.", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-0102", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-01-x", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-Wx", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-W012", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-W01-x", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-01-02T03:x", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-01-02T03:04x", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("2021-01-02T03:04:", &Iso8601::DEFAULT), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("01:02", &Iso8601::DEFAULT), + Err(error::Parse::TryFromParsed( + error::TryFromParsed::InsufficientInformation { .. } + )) + )); } #[test] @@ -1121,6 +1270,61 @@ fn parse_offset_date_time_err() -> time::Result<()> { Ok(()) } +#[test] +fn parse_utc_date_time() -> time::Result<()> { + assert_eq!( + UtcDateTime::parse("2023-07-27 23", &fd::parse("[year]-[month]-[day] [hour]")?), + Ok(utc_datetime!(2023-07-27 23:00)) + ); + + Ok(()) +} + +#[test] +fn parse_utc_date_time_err() -> time::Result<()> { + assert!(matches!( + UtcDateTime::parse("", &fd::parse("")?), + Err(error::Parse::TryFromParsed( + error::TryFromParsed::InsufficientInformation { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse( + "2021-001 13 PM", + &fd::parse("[year]-[ordinal] [hour repr:12] [period]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("hour") + )) + )); + assert!(matches!( + UtcDateTime::parse( + "2023-07-27 23:30", + &fd::parse("[year]-[month]-[day] [hour]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::UnexpectedTrailingCharacters { .. } + )) + )); + assert!(matches!( + UtcDateTime::parse("x", &fd::parse("[year]")?), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("year") + )) + )); + assert!(matches!( + UtcDateTime::parse( + "2021-001 12 PM +25", + &fd::parse("[year]-[ordinal] [hour repr:12] [period] [offset_hour sign:mandatory]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("offset hour") + )) + )); + + Ok(()) +} + #[allow(clippy::cognitive_complexity)] // all test the same thing #[test] fn parse_components() -> time::Result<()> { diff --git a/tests/utc_date_time.rs b/tests/utc_date_time.rs new file mode 100644 index 000000000..fe458366f --- /dev/null +++ b/tests/utc_date_time.rs @@ -0,0 +1,1203 @@ +use std::cmp::Ordering; +use std::time::{Duration as StdDuration, SystemTime}; + +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::{date, datetime, offset, time, utc_datetime}; +use time::{Duration, Month, OffsetDateTime, UtcDateTime, Weekday}; + +#[test] +fn new() { + let dt = UtcDateTime::new(date!(2023-12-18), time!(10:13:44.250 AM)); + assert_eq!(dt.year(), 2023); + assert_eq!(dt.millisecond(), 250); +} + +#[test] +fn now() { + assert!(UtcDateTime::now().year() >= 2019); +} + +#[test] +fn to_offset() { + assert_eq!( + utc_datetime!(2000-01-01 0:00).to_offset(offset!(-1)), + datetime!(1999-12-31 23:00 -1), + ); + assert_eq!( + utc_datetime!(0000-001 0:00).to_offset(offset!(UTC)), + datetime!(0000-001 0:00 UTC), + ); +} + +#[test] +fn to_offset_panic() { + assert_panic!(UtcDateTime::MAX.to_offset(offset!(+1))); + assert_panic!(UtcDateTime::MIN.to_offset(offset!(-1))); +} + +#[test] +fn checked_to_offset() { + assert_eq!( + utc_datetime!(2000-01-01 0:00) + .checked_to_offset(offset!(-1)) + .map(|odt| odt.year()), + Some(1999), + ); + assert_eq!(UtcDateTime::MAX.checked_to_offset(offset!(+1)), None); + assert_eq!(UtcDateTime::MIN.checked_to_offset(offset!(-1)), None); +} + +#[test] +fn from_unix_timestamp() { + assert_eq!( + UtcDateTime::from_unix_timestamp(0), + Ok(UtcDateTime::UNIX_EPOCH), + ); + assert_eq!( + UtcDateTime::from_unix_timestamp(1_546_300_800), + Ok(utc_datetime!(2019-01-01 0:00)), + ); +} + +#[test] +fn from_unix_timestamp_nanos() { + assert_eq!( + UtcDateTime::from_unix_timestamp_nanos(0), + Ok(UtcDateTime::UNIX_EPOCH), + ); + assert_eq!( + UtcDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000), + Ok(utc_datetime!(2019-01-01 0:00)), + ); + assert!(UtcDateTime::from_unix_timestamp_nanos(i128::MAX).is_err()); +} + +#[test] +fn unix_timestamp() { + assert_eq!(UtcDateTime::UNIX_EPOCH.unix_timestamp(), 0); +} + +#[test] +fn unix_timestamp_nanos() { + assert_eq!(UtcDateTime::UNIX_EPOCH.unix_timestamp_nanos(), 0); +} + +#[test] +fn date() { + assert_eq!(utc_datetime!(2019-01-01 0:00).date(), date!(2019-01-01)); +} + +#[test] +fn time() { + assert_eq!(utc_datetime!(2019-01-01 0:00).time(), time!(0:00)); +} + +#[test] +fn year() { + assert_eq!(utc_datetime!(2019-01-01 0:00).year(), 2019); +} + +#[test] +fn month() { + assert_eq!(utc_datetime!(2019-01-01 0:00).month(), Month::January); +} + +#[test] +fn day() { + assert_eq!(utc_datetime!(2019-01-01 0:00).day(), 1); +} + +#[test] +fn ordinal() { + assert_eq!(utc_datetime!(2019-01-01 0:00).ordinal(), 1); +} + +#[test] +fn iso_week() { + assert_eq!(utc_datetime!(2019-01-01 0:00).iso_week(), 1); + assert_eq!(utc_datetime!(2020-01-01 0:00).iso_week(), 1); + assert_eq!(utc_datetime!(2020-12-31 0:00).iso_week(), 53); + assert_eq!(utc_datetime!(2021-01-01 0:00).iso_week(), 53); +} + +#[test] +fn sunday_based_week() { + assert_eq!(utc_datetime!(2019-01-01 0:00).sunday_based_week(), 0); + assert_eq!(utc_datetime!(2020-01-01 0:00).sunday_based_week(), 0); + assert_eq!(utc_datetime!(2020-12-31 0:00).sunday_based_week(), 52); + assert_eq!(utc_datetime!(2021-01-01 0:00).sunday_based_week(), 0); +} + +#[test] +fn monday_based_week() { + assert_eq!(utc_datetime!(2019-01-01 0:00).monday_based_week(), 0); + assert_eq!(utc_datetime!(2020-01-01 0:00).monday_based_week(), 0); + assert_eq!(utc_datetime!(2020-12-31 0:00).monday_based_week(), 52); + assert_eq!(utc_datetime!(2021-01-01 0:00).monday_based_week(), 0); +} + +#[test] +fn to_calendar_date() { + assert_eq!( + utc_datetime!(2019-01-02 0:00).to_calendar_date(), + (2019, Month::January, 2) + ); +} + +#[test] +fn to_ordinal_date() { + assert_eq!(utc_datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1)); +} + +#[test] +fn to_iso_week_date() { + use Weekday::*; + assert_eq!( + utc_datetime!(2019-01-01 0:00).to_iso_week_date(), + (2019, 1, Tuesday) + ); + assert_eq!( + utc_datetime!(2019-10-04 0:00).to_iso_week_date(), + (2019, 40, Friday) + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00).to_iso_week_date(), + (2020, 1, Wednesday) + ); + assert_eq!( + utc_datetime!(2020-12-31 0:00).to_iso_week_date(), + (2020, 53, Thursday) + ); + assert_eq!( + utc_datetime!(2021-01-01 0:00).to_iso_week_date(), + (2020, 53, Friday) + ); +} + +#[test] +fn weekday() { + use Weekday::*; + assert_eq!(utc_datetime!(2019-01-01 0:00).weekday(), Tuesday); + assert_eq!(utc_datetime!(2019-02-01 0:00).weekday(), Friday); + assert_eq!(utc_datetime!(2019-03-01 0:00).weekday(), Friday); +} + +#[test] +fn to_julian_day() { + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).to_julian_day(), + -363_521_074 + ); + assert_eq!(utc_datetime!(-4713-11-24 0:00).to_julian_day(), 0); + assert_eq!(utc_datetime!(2000-01-01 0:00).to_julian_day(), 2_451_545); + assert_eq!(utc_datetime!(2019-01-01 0:00).to_julian_day(), 2_458_485); + assert_eq!(utc_datetime!(2019-12-31 0:00).to_julian_day(), 2_458_849); +} + +#[test] +fn as_hms() { + assert_eq!(utc_datetime!(2020-01-01 1:02:03).as_hms(), (1, 2, 3)); +} + +#[test] +fn as_hms_milli() { + assert_eq!( + utc_datetime!(2020-01-01 1:02:03.004).as_hms_milli(), + (1, 2, 3, 4) + ); +} + +#[test] +fn as_hms_micro() { + assert_eq!( + utc_datetime!(2020-01-01 1:02:03.004_005).as_hms_micro(), + (1, 2, 3, 4_005) + ); +} + +#[test] +fn as_hms_nano() { + assert_eq!( + utc_datetime!(2020-01-01 1:02:03.004_005_006).as_hms_nano(), + (1, 2, 3, 4_005_006) + ); +} + +#[test] +fn hour() { + assert_eq!(utc_datetime!(2019-01-01 0:00).hour(), 0); +} + +#[test] +fn minute() { + assert_eq!(utc_datetime!(2019-01-01 0:00).minute(), 0); +} + +#[test] +fn second() { + assert_eq!(utc_datetime!(2019-01-01 0:00).second(), 0); +} + +#[test] +fn millisecond() { + assert_eq!(utc_datetime!(2019-01-01 0:00).millisecond(), 0); + assert_eq!(utc_datetime!(2019-01-01 23:59:59.999).millisecond(), 999); +} + +#[test] +fn microsecond() { + assert_eq!(utc_datetime!(2019-01-01 0:00).microsecond(), 0); + assert_eq!( + utc_datetime!(2019-01-01 23:59:59.999_999).microsecond(), + 999_999, + ); +} + +#[test] +fn nanosecond() { + assert_eq!(utc_datetime!(2019-01-01 0:00).nanosecond(), 0); + assert_eq!( + utc_datetime!(2019-01-01 23:59:59.999_999_999).nanosecond(), + 999_999_999, + ); +} + +#[test] +fn replace_time() { + assert_eq!( + utc_datetime!(2020-01-01 5:00).replace_time(time!(12:00)), + utc_datetime!(2020-01-01 12:00) + ); +} + +#[test] +fn replace_date() { + assert_eq!( + utc_datetime!(2020-01-01 12:00).replace_date(date!(2020-01-30)), + utc_datetime!(2020-01-30 12:00) + ); +} + +#[test] +fn replace_year() { + assert_eq!( + utc_datetime!(2022-02-18 12:00).replace_year(2019), + Ok(utc_datetime!(2019-02-18 12:00)) + ); + assert!(utc_datetime!(2022-02-18 12:00) + .replace_year(-1_000_000_000) + .is_err()); // -1_000_000_000 isn't a valid year + assert!(utc_datetime!(2022-02-18 12:00) + .replace_year(1_000_000_000) + .is_err()); // 1_000_000_000 isn't a valid year +} + +#[test] +fn replace_month() { + assert_eq!( + utc_datetime!(2022-02-18 12:00).replace_month(Month::January), + Ok(utc_datetime!(2022-01-18 12:00)) + ); + assert!(utc_datetime!(2022-01-30 12:00) + .replace_month(Month::February) + .is_err()); // 30 isn't a valid day in February +} + +#[test] +fn replace_day() { + assert_eq!( + utc_datetime!(2022-02-18 12:00).replace_day(1), + Ok(utc_datetime!(2022-02-01 12:00)) + ); + // 00 isn't a valid day + assert!(utc_datetime!(2022-02-18 12:00).replace_day(0).is_err()); + // 30 isn't a valid day in February + assert!(utc_datetime!(2022-02-18 12:00).replace_day(30).is_err()); +} + +#[test] +fn replace_ordinal() { + assert_eq!( + utc_datetime!(2022-02-18 12:00).replace_ordinal(1), + Ok(utc_datetime!(2022-001 12:00)) + ); + assert_eq!( + utc_datetime!(2024-02-29 12:00).replace_ordinal(366), + Ok(utc_datetime!(2024-366 12:00)) + ); + assert!(utc_datetime!(2022-049 12:00).replace_ordinal(0).is_err()); // 0 isn't a valid day + assert!(utc_datetime!(2022-049 12:00).replace_ordinal(366).is_err()); // 2022 isn't a leap year + assert!(utc_datetime!(2022-049 12:00).replace_ordinal(367).is_err()); // 367 isn't a valid day +} + +#[test] +fn replace_hour() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_hour(7), + Ok(utc_datetime!(2022-02-18 07:02:03.004_005_006)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_hour(24) + .is_err()); // 24 isn't a valid hour +} + +#[test] +fn replace_minute() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_minute(7), + Ok(utc_datetime!(2022-02-18 01:07:03.004_005_006)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_minute(60) + .is_err()); // 60 isn't a valid minute +} + +#[test] +fn replace_second() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_second(7), + Ok(utc_datetime!(2022-02-18 01:02:07.004_005_006)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_second(60) + .is_err()); // 60 isn't a valid second +} + +#[test] +fn replace_millisecond() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_millisecond(7), + Ok(utc_datetime!(2022-02-18 01:02:03.007)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_millisecond(1_000) + .is_err()); // 1_000 isn't a valid millisecond +} + +#[test] +fn replace_microsecond() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_microsecond(7_008), + Ok(utc_datetime!(2022-02-18 01:02:03.007_008)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_microsecond(1_000_000) + .is_err()); // 1_000_000 isn't a valid microsecond +} + +#[test] +fn replace_nanosecond() { + assert_eq!( + utc_datetime!(2022-02-18 01:02:03.004_005_006).replace_nanosecond(7_008_009), + Ok(utc_datetime!(2022-02-18 01:02:03.007_008_009)) + ); + assert!(utc_datetime!(2022-02-18 01:02:03.004_005_006) + .replace_nanosecond(1_000_000_000) + .is_err()); // 1_000_000_000 isn't a valid nanosecond +} + +#[test] +fn partial_eq() { + assert_eq!( + utc_datetime!(2000-01-01 0:00), + utc_datetime!(2000-01-01 0:00), + ); +} + +#[test] +fn partial_ord() { + let t1 = utc_datetime!(2019-01-01 0:00); + let t2 = utc_datetime!(2019-01-01 0:00); + assert_eq!(t1.partial_cmp(&t2), Some(Ordering::Equal)); +} + +#[test] +fn ord() { + let t1 = utc_datetime!(2019-01-01 0:00); + let t2 = utc_datetime!(2019-01-01 0:00); + assert_eq!(t1, t2); + + let t1 = utc_datetime!(2019-01-01 0:00); + let t2 = utc_datetime!(2019-01-01 0:00:00.000_000_001); + assert!(t2 > t1); +} + +#[test] +fn hash() { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + assert_eq!( + { + let mut hasher = DefaultHasher::new(); + utc_datetime!(2019-01-01 0:00).hash(&mut hasher); + hasher.finish() + }, + { + let mut hasher = DefaultHasher::new(); + utc_datetime!(2019-01-01 0:00).hash(&mut hasher); + hasher.finish() + } + ); +} + +#[test] +fn add_duration() { + assert_eq!( + utc_datetime!(2019-01-01 0:00) + 5.days(), + utc_datetime!(2019-01-06 0:00), + ); + assert_eq!( + utc_datetime!(2019-12-31 0:00) + 1.days(), + utc_datetime!(2020-01-01 0:00), + ); + assert_eq!( + utc_datetime!(2019-12-31 23:59:59) + 2.seconds(), + utc_datetime!(2020-01-01 0:00:01), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00:01) + (-2).seconds(), + utc_datetime!(2019-12-31 23:59:59), + ); + assert_eq!( + utc_datetime!(1999-12-31 23:00) + 1.hours(), + utc_datetime!(2000-01-01 0:00), + ); +} + +#[test] +fn add_std_duration() { + assert_eq!( + utc_datetime!(2019-01-01 0:00) + 5.std_days(), + utc_datetime!(2019-01-06 0:00), + ); + assert_eq!( + utc_datetime!(2019-12-31 0:00) + 1.std_days(), + utc_datetime!(2020-01-01 0:00), + ); + assert_eq!( + utc_datetime!(2019-12-31 23:59:59) + 2.std_seconds(), + utc_datetime!(2020-01-01 0:00:01), + ); +} + +#[test] +fn add_assign_duration() { + let mut new_years_day_2019 = utc_datetime!(2019-01-01 0:00); + new_years_day_2019 += 5.days(); + assert_eq!(new_years_day_2019, utc_datetime!(2019-01-06 0:00)); + + let mut new_years_eve_2020_days = utc_datetime!(2019-12-31 0:00); + new_years_eve_2020_days += 1.days(); + assert_eq!(new_years_eve_2020_days, utc_datetime!(2020-01-01 0:00)); + + let mut new_years_eve_2020_seconds = utc_datetime!(2019-12-31 23:59:59); + new_years_eve_2020_seconds += 2.seconds(); + assert_eq!( + new_years_eve_2020_seconds, + utc_datetime!(2020-01-01 0:00:01) + ); + + let mut new_years_day_2020_seconds = utc_datetime!(2020-01-01 0:00:01); + new_years_day_2020_seconds += (-2).seconds(); + assert_eq!( + new_years_day_2020_seconds, + utc_datetime!(2019-12-31 23:59:59) + ); +} + +#[test] +fn add_assign_std_duration() { + let mut new_years_day_2019 = utc_datetime!(2019-01-01 0:00); + new_years_day_2019 += 5.std_days(); + assert_eq!(new_years_day_2019, utc_datetime!(2019-01-06 0:00)); + + let mut new_years_eve_2020_days = utc_datetime!(2019-12-31 0:00); + new_years_eve_2020_days += 1.std_days(); + assert_eq!(new_years_eve_2020_days, utc_datetime!(2020-01-01 0:00)); + + let mut new_years_eve_2020_seconds = utc_datetime!(2019-12-31 23:59:59); + new_years_eve_2020_seconds += 2.std_seconds(); + assert_eq!( + new_years_eve_2020_seconds, + utc_datetime!(2020-01-01 0:00:01) + ); +} + +#[test] +fn sub_duration() { + assert_eq!( + utc_datetime!(2019-01-06 0:00) - 5.days(), + utc_datetime!(2019-01-01 0:00), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00) - 1.days(), + utc_datetime!(2019-12-31 0:00), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00:01) - 2.seconds(), + utc_datetime!(2019-12-31 23:59:59), + ); + assert_eq!( + utc_datetime!(2019-12-31 23:59:59) - (-2).seconds(), + utc_datetime!(2020-01-01 0:00:01), + ); + assert_eq!( + utc_datetime!(1999-12-31 23:00) - (-1).hours(), + utc_datetime!(2000-01-01 0:00), + ); +} + +#[test] +fn sub_std_duration() { + assert_eq!( + utc_datetime!(2019-01-06 0:00) - 5.std_days(), + utc_datetime!(2019-01-01 0:00), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00) - 1.std_days(), + utc_datetime!(2019-12-31 0:00), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00:01) - 2.std_seconds(), + utc_datetime!(2019-12-31 23:59:59), + ); +} + +#[test] +fn sub_assign_duration() { + let mut new_years_day_2019 = utc_datetime!(2019-01-06 0:00); + new_years_day_2019 -= 5.days(); + assert_eq!(new_years_day_2019, utc_datetime!(2019-01-01 0:00)); + + let mut new_years_day_2020_days = utc_datetime!(2020-01-01 0:00); + new_years_day_2020_days -= 1.days(); + assert_eq!(new_years_day_2020_days, utc_datetime!(2019-12-31 0:00)); + + let mut new_years_day_2020_seconds = utc_datetime!(2020-01-01 0:00:01); + new_years_day_2020_seconds -= 2.seconds(); + assert_eq!( + new_years_day_2020_seconds, + utc_datetime!(2019-12-31 23:59:59) + ); + + let mut new_years_eve_2020_seconds = utc_datetime!(2019-12-31 23:59:59); + new_years_eve_2020_seconds -= (-2).seconds(); + assert_eq!( + new_years_eve_2020_seconds, + utc_datetime!(2020-01-01 0:00:01) + ); +} + +#[test] +fn sub_assign_std_duration() { + let mut ny19 = utc_datetime!(2019-01-06 0:00); + ny19 -= 5.std_days(); + assert_eq!(ny19, utc_datetime!(2019-01-01 0:00)); + + let mut ny20 = utc_datetime!(2020-01-01 0:00); + ny20 -= 1.std_days(); + assert_eq!(ny20, utc_datetime!(2019-12-31 0:00)); + + let mut ny20t = utc_datetime!(2020-01-01 0:00:01); + ny20t -= 2.std_seconds(); + assert_eq!(ny20t, utc_datetime!(2019-12-31 23:59:59)); +} + +#[test] +fn std_add_duration() { + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-01 0:00)) + 0.seconds(), + SystemTime::from(utc_datetime!(2019-01-01 0:00)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-01 0:00)) + 5.days(), + SystemTime::from(utc_datetime!(2019-01-06 0:00)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-12-31 0:00)) + 1.days(), + SystemTime::from(utc_datetime!(2020-01-01 0:00)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-12-31 23:59:59)) + 2.seconds(), + SystemTime::from(utc_datetime!(2020-01-01 0:00:01)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2020-01-01 0:00:01)) + (-2).seconds(), + SystemTime::from(utc_datetime!(2019-12-31 23:59:59)), + ); +} + +#[test] +fn std_add_assign_duration() { + let mut new_years_day_2019 = SystemTime::from(utc_datetime!(2019-01-01 0:00)); + new_years_day_2019 += 5.days(); + assert_eq!(new_years_day_2019, utc_datetime!(2019-01-06 0:00)); + + let mut new_years_eve_2020_days = SystemTime::from(utc_datetime!(2019-12-31 0:00)); + new_years_eve_2020_days += 1.days(); + assert_eq!(new_years_eve_2020_days, utc_datetime!(2020-01-01 0:00)); + + let mut new_years_eve_2020_seconds = SystemTime::from(utc_datetime!(2019-12-31 23:59:59)); + new_years_eve_2020_seconds += 2.seconds(); + assert_eq!( + new_years_eve_2020_seconds, + utc_datetime!(2020-01-01 0:00:01) + ); + + let mut new_years_day_2020_seconds = SystemTime::from(utc_datetime!(2020-01-01 0:00:01)); + new_years_day_2020_seconds += (-2).seconds(); + assert_eq!( + new_years_day_2020_seconds, + utc_datetime!(2019-12-31 23:59:59) + ); +} + +#[test] +fn std_sub_duration() { + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-06 0:00)) - 5.days(), + SystemTime::from(utc_datetime!(2019-01-01 0:00)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2020-01-01 0:00)) - 1.days(), + SystemTime::from(utc_datetime!(2019-12-31 0:00)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2020-01-01 0:00:01)) - 2.seconds(), + SystemTime::from(utc_datetime!(2019-12-31 23:59:59)), + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-12-31 23:59:59)) - (-2).seconds(), + SystemTime::from(utc_datetime!(2020-01-01 0:00:01)), + ); +} + +#[test] +fn std_sub_assign_duration() { + let mut new_years_day_2019 = SystemTime::from(utc_datetime!(2019-01-06 0:00)); + new_years_day_2019 -= 5.days(); + assert_eq!(new_years_day_2019, utc_datetime!(2019-01-01 0:00)); + + let mut new_years_day_2020 = SystemTime::from(utc_datetime!(2020-01-01 0:00)); + new_years_day_2020 -= 1.days(); + assert_eq!(new_years_day_2020, utc_datetime!(2019-12-31 0:00)); + + let mut new_years_day_2020_seconds = SystemTime::from(utc_datetime!(2020-01-01 0:00:01)); + new_years_day_2020_seconds -= 2.seconds(); + assert_eq!( + new_years_day_2020_seconds, + utc_datetime!(2019-12-31 23:59:59) + ); + + let mut new_years_eve_2020_seconds = SystemTime::from(utc_datetime!(2019-12-31 23:59:59)); + new_years_eve_2020_seconds -= (-2).seconds(); + assert_eq!( + new_years_eve_2020_seconds, + utc_datetime!(2020-01-01 0:00:01) + ); +} + +#[test] +fn sub_self() { + assert_eq!( + utc_datetime!(2019-01-02 0:00) - utc_datetime!(2019-01-01 0:00), + 1.days(), + ); + assert_eq!( + utc_datetime!(2019-01-01 0:00) - utc_datetime!(2019-01-02 0:00), + (-1).days(), + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00) - utc_datetime!(2019-12-31 0:00), + 1.days(), + ); + assert_eq!( + utc_datetime!(2019-12-31 0:00) - utc_datetime!(2020-01-01 0:00), + (-1).days(), + ); +} + +#[test] +fn std_sub() { + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-02 0:00)) - utc_datetime!(2019-01-01 0:00), + 1.days() + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-01 0:00)) - utc_datetime!(2019-01-02 0:00), + (-1).days() + ); + assert_eq!( + SystemTime::from(utc_datetime!(2020-01-01 0:00)) - utc_datetime!(2019-12-31 0:00), + 1.days() + ); + assert_eq!( + SystemTime::from(utc_datetime!(2019-12-31 0:00)) - utc_datetime!(2020-01-01 0:00), + (-1).days() + ); +} + +#[test] +fn sub_std() { + assert_eq!( + utc_datetime!(2019-01-02 0:00) - SystemTime::from(utc_datetime!(2019-01-01 0:00)), + 1.days() + ); + assert_eq!( + utc_datetime!(2019-01-01 0:00) - SystemTime::from(utc_datetime!(2019-01-02 0:00)), + (-1).days() + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00) - SystemTime::from(utc_datetime!(2019-12-31 0:00)), + 1.days() + ); + assert_eq!( + utc_datetime!(2019-12-31 0:00) - SystemTime::from(utc_datetime!(2020-01-01 0:00)), + (-1).days() + ); +} + +#[test] +fn odt_sub() { + assert_eq!( + datetime!(2019-01-02 0:00 UTC) - utc_datetime!(2019-01-01 0:00), + 1.days() + ); + assert_eq!( + datetime!(2019-01-01 0:00 UTC) - utc_datetime!(2019-01-02 0:00), + (-1).days() + ); + assert_eq!( + datetime!(2020-01-01 0:00 UTC) - utc_datetime!(2019-12-31 0:00), + 1.days() + ); + assert_eq!( + datetime!(2019-12-31 0:00 UTC) - utc_datetime!(2020-01-01 0:00), + (-1).days() + ); +} + +#[test] +fn sub_odt() { + assert_eq!( + utc_datetime!(2019-01-02 0:00) - datetime!(2019-01-01 0:00 UTC), + 1.days() + ); + assert_eq!( + utc_datetime!(2019-01-01 0:00) - datetime!(2019-01-02 0:00 UTC), + (-1).days() + ); + assert_eq!( + utc_datetime!(2020-01-01 0:00) - datetime!(2019-12-31 0:00 UTC), + 1.days() + ); + assert_eq!( + utc_datetime!(2019-12-31 0:00) - datetime!(2020-01-01 0:00 UTC), + (-1).days() + ); +} + +#[test] +fn eq_std() { + let now_datetime = UtcDateTime::now(); + let now_systemtime = SystemTime::from(now_datetime); + assert_eq!(now_datetime, now_systemtime); +} + +#[test] +fn std_eq() { + let now_datetime = UtcDateTime::now(); + let now_systemtime = SystemTime::from(now_datetime); + assert_eq!(now_systemtime, now_datetime); +} + +#[test] +fn eq_odt() { + let now_datetime = UtcDateTime::now(); + let now_odt = OffsetDateTime::from(now_datetime); + assert_eq!(now_datetime, now_odt); +} + +#[test] +fn odt_eq() { + let now_datetime = UtcDateTime::now(); + let now_odt = OffsetDateTime::from(now_datetime); + assert_eq!(now_odt, now_datetime); +} + +#[test] +fn ord_std() { + assert_eq!( + utc_datetime!(2019-01-01 0:00), + SystemTime::from(utc_datetime!(2019-01-01 0:00)) + ); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2020-01-01 0:00))); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-02-01 0:00))); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-01-02 0:00))); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-01-01 1:00:00))); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-01-01 0:01:00))); + assert!(utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-01-01 0:00:01))); + assert!( + utc_datetime!(2019-01-01 0:00) < SystemTime::from(utc_datetime!(2019-01-01 0:00:00.001)) + ); + assert!(utc_datetime!(2020-01-01 0:00) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!(utc_datetime!(2019-02-01 0:00) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!(utc_datetime!(2019-01-02 0:00) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!(utc_datetime!(2019-01-01 1:00:00) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!(utc_datetime!(2019-01-01 0:01:00) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!(utc_datetime!(2019-01-01 0:00:01) > SystemTime::from(utc_datetime!(2019-01-01 0:00))); + assert!( + utc_datetime!(2019-01-01 0:00:00.000_000_001) + > SystemTime::from(utc_datetime!(2019-01-01 0:00)) + ); +} + +#[test] +fn std_ord() { + assert_eq!( + SystemTime::from(utc_datetime!(2019-01-01 0:00)), + utc_datetime!(2019-01-01 0:00) + ); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2020-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2019-02-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2019-01-02 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2019-01-01 1:00:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2019-01-01 0:01:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00)) < utc_datetime!(2019-01-01 0:00:01)); + assert!( + SystemTime::from(utc_datetime!(2019-01-01 0:00)) + < utc_datetime!(2019-01-01 0:00:00.000_000_001) + ); + assert!(SystemTime::from(utc_datetime!(2020-01-01 0:00)) > utc_datetime!(2019-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-02-01 0:00)) > utc_datetime!(2019-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-02 0:00)) > utc_datetime!(2019-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 1:00:00)) > utc_datetime!(2019-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:01:00)) > utc_datetime!(2019-01-01 0:00)); + assert!(SystemTime::from(utc_datetime!(2019-01-01 0:00:01)) > utc_datetime!(2019-01-01 0:00)); + assert!( + SystemTime::from(utc_datetime!(2019-01-01 0:00:00.001)) > utc_datetime!(2019-01-01 0:00) + ); +} + +#[test] +fn ord_odt() { + assert_eq!( + utc_datetime!(2019-01-01 0:00), + datetime!(2019-01-01 0:00 UTC) + ); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2020-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-02-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-01-02 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-01-01 1:00:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-01-01 0:01:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-01-01 0:00:01 UTC)); + assert!(utc_datetime!(2019-01-01 0:00) < datetime!(2019-01-01 0:00:00.001 UTC)); + assert!(utc_datetime!(2020-01-01 0:00) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-02-01 0:00) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-02 0:00) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 1:00:00) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:01:00) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00:01) > datetime!(2019-01-01 0:00 UTC)); + assert!(utc_datetime!(2019-01-01 0:00:00.000_000_001) > datetime!(2019-01-01 0:00 UTC)); +} + +#[test] +fn odt_ord() { + assert_eq!( + datetime!(2019-01-01 0:00 UTC), + utc_datetime!(2019-01-01 0:00) + ); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2020-01-01 0:00)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-02-01 0:00)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-01-02 0:00)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-01-01 1:00:00)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-01-01 0:01:00)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-01-01 0:00:01)); + assert!(datetime!(2019-01-01 0:00 UTC) < utc_datetime!(2019-01-01 0:00:00.000_000_001)); + assert!(datetime!(2020-01-01 0:00 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-02-01 0:00 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-01-02 0:00 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-01-01 1:00:00 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-01-01 0:01:00 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-01-01 0:00:01 UTC) > utc_datetime!(2019-01-01 0:00)); + assert!(datetime!(2019-01-01 0:00:00.001 UTC) > utc_datetime!(2019-01-01 0:00)); +} + +#[test] +fn from_std() { + assert_eq!( + UtcDateTime::from(SystemTime::UNIX_EPOCH), + UtcDateTime::UNIX_EPOCH + ); + assert_eq!( + UtcDateTime::from(SystemTime::UNIX_EPOCH - 1.std_days()), + UtcDateTime::UNIX_EPOCH - 1.days() + ); + assert_eq!( + UtcDateTime::from(SystemTime::UNIX_EPOCH + 1.std_days()), + UtcDateTime::UNIX_EPOCH + 1.days() + ); +} + +#[test] +fn to_std() { + assert_eq!( + SystemTime::from(UtcDateTime::UNIX_EPOCH), + SystemTime::UNIX_EPOCH + ); + assert_eq!( + SystemTime::from(UtcDateTime::UNIX_EPOCH + 1.days()), + SystemTime::UNIX_EPOCH + 1.std_days() + ); + assert_eq!( + SystemTime::from(UtcDateTime::UNIX_EPOCH - 1.days()), + SystemTime::UNIX_EPOCH - 1.std_days() + ); +} + +#[test] +fn from_odt() { + assert_eq!( + UtcDateTime::from(OffsetDateTime::UNIX_EPOCH), + UtcDateTime::UNIX_EPOCH + ); + assert_eq!( + UtcDateTime::from(OffsetDateTime::UNIX_EPOCH - 1.std_days()), + UtcDateTime::UNIX_EPOCH - 1.days() + ); + assert_eq!( + UtcDateTime::from(OffsetDateTime::UNIX_EPOCH + 1.std_days()), + UtcDateTime::UNIX_EPOCH + 1.days() + ); +} + +#[test] +fn to_odt() { + assert_eq!( + OffsetDateTime::from(UtcDateTime::UNIX_EPOCH), + OffsetDateTime::UNIX_EPOCH + ); + assert_eq!( + OffsetDateTime::from(UtcDateTime::UNIX_EPOCH + 1.days()), + OffsetDateTime::UNIX_EPOCH + 1.std_days() + ); + assert_eq!( + OffsetDateTime::from(UtcDateTime::UNIX_EPOCH - 1.days()), + OffsetDateTime::UNIX_EPOCH - 1.std_days() + ); +} + +#[test] +fn checked_add_duration() { + // Successful addition + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add(5.nanoseconds()), + Some(utc_datetime!(2021-10-25 14:01:53.450_000_005)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add(4.seconds()), + Some(utc_datetime!(2021-10-25 14:01:57.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add(2.days()), + Some(utc_datetime!(2021-10-27 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add(1.weeks()), + Some(utc_datetime!(2021-11-01 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add((-5).nanoseconds()), + Some(utc_datetime!(2021-10-25 14:01:53.449_999_995)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add((-4).seconds()), + Some(utc_datetime!(2021-10-25 14:01:49.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add((-2).days()), + Some(utc_datetime!(2021-10-23 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_add((-1).weeks()), + Some(utc_datetime!(2021-10-18 14:01:53.45)) + ); + + // Addition with underflow + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).checked_add((-1).nanoseconds()), + None + ); + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).checked_add(Duration::MIN), + None + ); + assert_eq!( + utc_datetime!(-999_990-01-01 0:00).checked_add((-530).weeks()), + None + ); + + // Addition with overflow + assert_eq!( + utc_datetime!(+999_999-12-31 23:59:59.999_999_999).checked_add(1.nanoseconds()), + None + ); + assert_eq!( + utc_datetime!(+999_999-12-31 23:59:59.999_999_999).checked_add(Duration::MAX), + None + ); + assert_eq!( + utc_datetime!(+999_990-12-31 23:59:59.999_999_999).checked_add(530.weeks()), + None + ); +} + +#[test] +fn checked_sub_duration() { + // Successful subtraction + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub((-5).nanoseconds()), + Some(utc_datetime!(2021-10-25 14:01:53.450_000_005)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub((-4).seconds()), + Some(utc_datetime!(2021-10-25 14:01:57.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub((-2).days()), + Some(utc_datetime!(2021-10-27 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub((-1).weeks()), + Some(utc_datetime!(2021-11-01 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub(5.nanoseconds()), + Some(utc_datetime!(2021-10-25 14:01:53.449_999_995)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub(4.seconds()), + Some(utc_datetime!(2021-10-25 14:01:49.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub(2.days()), + Some(utc_datetime!(2021-10-23 14:01:53.45)) + ); + assert_eq!( + utc_datetime!(2021-10-25 14:01:53.45).checked_sub(1.weeks()), + Some(utc_datetime!(2021-10-18 14:01:53.45)) + ); + + // Subtraction with underflow + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).checked_sub(1.nanoseconds()), + None + ); + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).checked_sub(Duration::MAX), + None + ); + assert_eq!( + utc_datetime!(-999_990-01-01 0:00).checked_sub(530.weeks()), + None + ); + + // Subtraction with overflow + assert_eq!( + utc_datetime!(+999_999-12-31 23:59:59.999_999_999).checked_sub((-1).nanoseconds()), + None + ); + assert_eq!( + utc_datetime!(+999_999-12-31 23:59:59.999_999_999).checked_sub(Duration::MIN), + None + ); + assert_eq!( + utc_datetime!(+999_990-12-31 23:59:59.999_999_999).checked_sub((-530).weeks()), + None + ); + + // Subtracting 0 duration at MIN/MAX values with non-zero offset + assert_eq!( + utc_datetime!(+999_999-12-31 23:59:59.999_999_999).checked_sub(Duration::ZERO), + Some(utc_datetime!(+999_999-12-31 23:59:59.999_999_999)) + ); + assert_eq!( + utc_datetime!(-999_999-01-01 0:00).checked_sub(Duration::ZERO), + Some(utc_datetime!(-999_999-01-01 0:00)) + ); +} + +#[test] +fn saturating_add_duration() { + assert_eq!( + utc_datetime!(2021-11-12 17:47).saturating_add(2.days()), + utc_datetime!(2021-11-14 17:47) + ); + assert_eq!( + utc_datetime!(2021-11-12 17:47).saturating_add((-2).days()), + utc_datetime!(2021-11-10 17:47) + ); + + // Adding with underflow + assert_eq!( + utc_datetime!(-999999-01-01 0:00).saturating_add((-10).days()), + utc_datetime!(-999999-01-01 0:00) + ); + + // Adding with overflow + assert_eq!( + utc_datetime!(+999999-12-31 23:59:59.999_999_999).saturating_add(10.days()), + utc_datetime!(+999999-12-31 23:59:59.999_999_999) + ); + + // Adding zero duration at boundaries + assert_eq!( + utc_datetime!(-999999-01-01 0:00).saturating_add(Duration::ZERO), + utc_datetime!(-999999-01-01 0:00) + ); + assert_eq!( + utc_datetime!(+999999-12-31 23:59:59.999_999_999).saturating_add(Duration::ZERO), + utc_datetime!(+999999-12-31 23:59:59.999_999_999) + ); +} + +#[test] +fn saturating_sub_duration() { + assert_eq!( + utc_datetime!(2021-11-12 17:47).saturating_sub(2.days()), + utc_datetime!(2021-11-10 17:47) + ); + assert_eq!( + utc_datetime!(2021-11-12 17:47).saturating_sub((-2).days()), + utc_datetime!(2021-11-14 17:47) + ); + + // Subtracting with underflow + assert_eq!( + utc_datetime!(-999999-01-01 0:00).saturating_sub(10.days()), + utc_datetime!(-999999-01-01 0:00) + ); + + // Subtracting with overflow + assert_eq!( + utc_datetime!(+999999-12-31 23:59:59.999_999_999).saturating_sub((-10).days()), + utc_datetime!(+999999-12-31 23:59:59.999_999_999) + ); + + // Subtracting zero duration at boundaries + assert_eq!( + utc_datetime!(-999999-01-01 0:00).saturating_sub(Duration::ZERO), + utc_datetime!(-999999-01-01 0:00) + ); + assert_eq!( + utc_datetime!(+999999-12-31 23:59:59.999_999_999).saturating_sub(Duration::ZERO), + utc_datetime!(+999999-12-31 23:59:59.999_999_999) + ); +} + +#[test] +#[should_panic = "overflow adding duration to date"] +fn issue_621() { + let _ = UtcDateTime::UNIX_EPOCH + StdDuration::from_secs(18_157_382_926_370_278_155); +} diff --git a/time-macros/src/lib.rs b/time-macros/src/lib.rs index d97b44060..3f0c78e84 100644 --- a/time-macros/src/lib.rs +++ b/time-macros/src/lib.rs @@ -29,6 +29,7 @@ mod offset; mod serde_format_description; mod time; mod to_tokens; +mod utc_datetime; #[cfg(any(feature = "formatting", feature = "parsing"))] use std::iter::Peekable; @@ -57,7 +58,7 @@ macro_rules! impl_macros { )*}; } -impl_macros![date datetime offset time]; +impl_macros![date datetime utc_datetime offset time]; #[cfg(any(feature = "formatting", feature = "parsing"))] enum FormatDescriptionVersion { diff --git a/time-macros/src/utc_datetime.rs b/time-macros/src/utc_datetime.rs new file mode 100644 index 000000000..e4e564cc2 --- /dev/null +++ b/time-macros/src/utc_datetime.rs @@ -0,0 +1,39 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, TokenTree}; + +use crate::date::Date; +use crate::error::Error; +use crate::time::Time; +use crate::to_tokens::ToTokenTree; +use crate::{date, time}; + +pub(crate) struct UtcDateTime { + date: Date, + time: Time, +} + +pub(crate) fn parse(chars: &mut Peekable) -> Result { + let date = date::parse(chars)?; + let time = time::parse(chars)?; + + if let Some(token) = chars.peek() { + return Err(Error::UnexpectedToken { + tree: token.clone(), + }); + } + + Ok(UtcDateTime { date, time }) +} + +impl ToTokenTree for UtcDateTime { + fn into_token_tree(self) -> TokenTree { + quote_group! {{ + const DATE_TIME: ::time::UtcDateTime = ::time::UtcDateTime::new( + #(self.date), + #(self.time), + ); + DATE_TIME + }} + } +} diff --git a/time/src/date.rs b/time/src/date.rs index f321c3970..8d3c2d0f0 100644 --- a/time/src/date.rs +++ b/time/src/date.rs @@ -79,8 +79,8 @@ impl Date { /// /// # Safety /// - /// `ordinal` must not be zero. `year` should be in the range `MIN_YEAR..=MAX_YEAR`, but this - /// is not a safety invariant. + /// `ordinal` must be non-zero and at most the number of days in `year`. `year` should be in the + /// range `MIN_YEAR..=MAX_YEAR`, but this is not a safety invariant. #[doc(hidden)] pub const unsafe fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { debug_assert!(year >= MIN_YEAR); diff --git a/time/src/lib.rs b/time/src/lib.rs index 4d8043cd2..f9a80cc54 100644 --- a/time/src/lib.rs +++ b/time/src/lib.rs @@ -108,6 +108,7 @@ mod sys; #[cfg(test)] mod tests; mod time; +mod utc_date_time; mod utc_offset; pub mod util; mod weekday; @@ -125,6 +126,7 @@ pub use crate::month::Month; pub use crate::offset_date_time::OffsetDateTime; pub use crate::primitive_date_time::PrimitiveDateTime; pub use crate::time::Time; +pub use crate::utc_date_time::UtcDateTime; pub use crate::utc_offset::UtcOffset; pub use crate::weekday::Weekday; diff --git a/time/src/macros.rs b/time/src/macros.rs index b50438f7e..9b191fa9c 100644 --- a/time/src/macros.rs +++ b/time/src/macros.rs @@ -65,7 +65,7 @@ pub use time_macros::datetime; /// ); /// # Ok::<_, time::Error>(()) /// ``` -/// +/// /// The syntax accepted by this macro is the same as [`format_description::parse()`], which can /// be found in [the book](https://time-rs.github.io/book/api/format-description.html). /// @@ -130,3 +130,20 @@ pub use time_macros::offset; /// # Ok::<_, time::Error>(()) /// ``` pub use time_macros::time; +/// Construct a [`UtcDateTime`] with a statically known value. +/// +/// The resulting expression can be used in `const` or `static` declarations. +/// +/// The syntax accepted by this macro is the same as a space-separated [`date!`] and [`time!`]. +/// +/// [`UtcDateTime`]: crate::UtcDateTime +/// +/// ```rust +/// # use time::{Date, Month, macros::utc_datetime}; +/// assert_eq!( +/// utc_datetime!(2020-01-01 0:00), +/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().as_utc() +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +pub use time_macros::utc_datetime; diff --git a/time/src/offset_date_time.rs b/time/src/offset_date_time.rs index 7896efe8c..00728293a 100644 --- a/time/src/offset_date_time.rs +++ b/time/src/offset_date_time.rs @@ -26,12 +26,12 @@ use crate::internal_macros::{ }; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -use crate::{error, util, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{ + error, util, Date, Duration, Month, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; /// The Julian day of the Unix epoch. -// Safety: `ordinal` is not zero. -const UNIX_EPOCH_JULIAN_DAY: i32 = - unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.to_julian_day(); +const UNIX_EPOCH_JULIAN_DAY: i32 = OffsetDateTime::UNIX_EPOCH.to_julian_day(); /// A [`PrimitiveDateTime`] with a [`UtcOffset`]. /// @@ -236,6 +236,51 @@ impl OffsetDateTime { )) } + /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to UTC, returning a + /// [`UtcDateTime`]. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2000-01-01 0:00 +1) + /// .to_utc() + /// .year(), + /// 1999, + /// ); + /// ``` + /// + /// # Panics + /// + /// This method panics if the UTC date-time is outside the supported range. + pub const fn to_utc(self) -> UtcDateTime { + self.to_offset(UtcOffset::UTC).local_date_time.as_utc() + } + + /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to UTC, returning `None` if the + /// UTC date-time is invalid. Returns a [`UtcDateTime`]. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2000-01-01 0:00 +1) + /// .checked_to_utc() + /// .unwrap() + /// .year(), + /// 1999, + /// ); + /// assert_eq!( + /// datetime!(+999999-12-31 23:59:59 -1).checked_to_utc(), + /// None, + /// ); + /// ``` + pub const fn checked_to_utc(self) -> Option { + Some( + const_try_opt!(self.checked_to_offset(UtcOffset::UTC)) + .local_date_time + .as_utc(), + ) + } + /// Equivalent to `.to_offset(UtcOffset::UTC)`, but returning the year, ordinal, and time. This /// avoids constructing an invalid [`Date`] if the new value is out of range. pub(crate) const fn to_offset_raw(self, offset: UtcOffset) -> (i32, u16, Time) { @@ -426,7 +471,7 @@ impl OffsetDateTime { } /// Get the [`PrimitiveDateTime`] in the stored offset. - const fn date_time(self) -> PrimitiveDateTime { + pub(crate) const fn date_time(self) -> PrimitiveDateTime { self.local_date_time } diff --git a/time/src/parsing/parsable.rs b/time/src/parsing/parsable.rs index cc7ce61be..a34322f4d 100644 --- a/time/src/parsing/parsable.rs +++ b/time/src/parsing/parsable.rs @@ -34,7 +34,7 @@ impl Parsable for T where T::Target: Parsable {} mod sealed { #[allow(clippy::wildcard_imports)] use super::*; - use crate::PrimitiveDateTime; + use crate::{PrimitiveDateTime, UtcDateTime}; /// Parse the item using a format description and an input. pub trait Sealed { @@ -85,6 +85,11 @@ mod sealed { Ok(self.parse(input)?.try_into()?) } + /// Parse a [`UtcDateTime`] from the format description. + fn parse_utc_date_time(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + /// Parse a [`OffsetDateTime`] from the format description. fn parse_offset_date_time(&self, input: &[u8]) -> Result { Ok(self.parse(input)?.try_into()?) diff --git a/time/src/parsing/parsed.rs b/time/src/parsing/parsed.rs index bf6424fff..ba1bb4e34 100644 --- a/time/src/parsing/parsed.rs +++ b/time/src/parsing/parsed.rs @@ -22,7 +22,9 @@ use crate::parsing::component::{ parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period, }; use crate::parsing::ParsedItem; -use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{ + error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; /// Sealed to prevent downstream implementations. mod sealed { @@ -970,25 +972,43 @@ impl TryFrom for Time { } } +fn utc_offset_try_from_parsed( + parsed: Parsed, +) -> Result { + let hour = match (REQUIRED, parsed.offset_hour()) { + // An offset is required, but the hour is missing. Return an error. + (true, None) => return Err(InsufficientInformation), + // An offset is not required (e.g. for `UtcDateTime`). As the hour is missing, minutes and + // seconds are not parsed. This is UTC. + (false, None) => return Ok(UtcOffset::UTC), + // Any other situation has an hour present. + (_, Some(hour)) => hour, + }; + let minute = parsed.offset_minute_signed(); + // Force `second` to be `None` if `minute` is `None`. + let second = minute.and_then(|_| parsed.offset_second_signed()); + + let minute = minute.unwrap_or(0); + let second = second.unwrap_or(0); + + UtcOffset::from_hms(hour, minute, second).map_err(|mut err| { + // Provide the user a more accurate error. + if err.name == "hours" { + err.name = "offset hour"; + } else if err.name == "minutes" { + err.name = "offset minute"; + } else if err.name == "seconds" { + err.name = "offset second"; + } + err.into() + }) +} + impl TryFrom for UtcOffset { type Error = error::TryFromParsed; fn try_from(parsed: Parsed) -> Result { - let hour = parsed.offset_hour().ok_or(InsufficientInformation)?; - let minute = parsed.offset_minute_signed().unwrap_or(0); - let second = parsed.offset_second_signed().unwrap_or(0); - - Self::from_hms(hour, minute, second).map_err(|mut err| { - // Provide the user a more accurate error. - if err.name == "hours" { - err.name = "offset hour"; - } else if err.name == "minutes" { - err.name = "offset minute"; - } else if err.name == "seconds" { - err.name = "offset second"; - } - err.into() - }) + utc_offset_try_from_parsed::(parsed) } } @@ -1000,6 +1020,55 @@ impl TryFrom for PrimitiveDateTime { } } +impl TryFrom for UtcDateTime { + type Error = error::TryFromParsed; + + fn try_from(mut parsed: Parsed) -> Result { + if let Some(timestamp) = parsed.unix_timestamp_nanos() { + let mut value = Self::from_unix_timestamp_nanos(timestamp)?; + if let Some(subsecond) = parsed.subsecond() { + value = value.replace_nanosecond(subsecond)?; + } + return Ok(value); + } + + // Some well-known formats explicitly allow leap seconds. We don't currently support them, + // so treat it as the nearest preceding moment that can be represented. Because leap seconds + // always fall at the end of a month UTC, reject any that are at other times. + let leap_second_input = if parsed.leap_second_allowed && parsed.second() == Some(60) { + if parsed.set_second(59).is_none() { + bug!("59 is a valid second"); + } + if parsed.set_subsecond(999_999_999).is_none() { + bug!("999_999_999 is a valid subsecond"); + } + true + } else { + false + }; + + let dt = OffsetDateTime::new_in_offset( + Date::try_from(parsed)?, + Time::try_from(parsed)?, + utc_offset_try_from_parsed::(parsed)?, + ) + .to_utc(); + + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_message: Some("because leap seconds are not supported"), + }, + )); + } + Ok(dt) + } +} + impl TryFrom for OffsetDateTime { type Error = error::TryFromParsed; diff --git a/time/src/primitive_date_time.rs b/time/src/primitive_date_time.rs index e7ca99ddd..5c1b624b1 100644 --- a/time/src/primitive_date_time.rs +++ b/time/src/primitive_date_time.rs @@ -16,7 +16,9 @@ use crate::formatting::Formattable; use crate::internal_macros::{const_try, const_try_opt}; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; +use crate::{ + error, util, Date, Duration, Month, OffsetDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; /// Combined date and time. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -488,9 +490,26 @@ impl PrimitiveDateTime { /// 1_546_300_800, /// ); /// ``` + /// + /// **Note**: You may want a [`UtcDateTime`] instead, which can be obtained with the + /// [`PrimitiveDateTime::as_utc`] method. pub const fn assume_utc(self) -> OffsetDateTime { self.assume_offset(UtcOffset::UTC) } + + /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return a + /// [`UtcDateTime`]. + /// + /// ```rust + /// # use time_macros::datetime; + /// assert_eq!( + /// datetime!(2019-01-01 0:00).as_utc().unix_timestamp(), + /// 1_546_300_800, + /// ); + /// ``` + pub const fn as_utc(self) -> UtcDateTime { + UtcDateTime::from_primitive(self) + } // endregion attach offset // region: checked arithmetic diff --git a/time/src/quickcheck.rs b/time/src/quickcheck.rs index 312a09e19..8a3d17185 100644 --- a/time/src/quickcheck.rs +++ b/time/src/quickcheck.rs @@ -38,7 +38,9 @@ use alloc::boxed::Box; use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen}; -use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{ + Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; /// Obtain an arbitrary value between the minimum and maximum inclusive. macro_rules! arbitrary_between { @@ -155,6 +157,20 @@ impl Arbitrary for OffsetDateTime { } } +impl Arbitrary for UtcDateTime { + fn arbitrary(g: &mut Gen) -> Self { + Self::new(<_>::arbitrary(g), <_>::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + Box::new( + (self.date(), self.time()) + .shrink() + .map(|(date, time)| Self::new(date, time)), + ) + } +} + impl Arbitrary for Weekday { fn arbitrary(g: &mut Gen) -> Self { use Weekday::*; diff --git a/time/src/serde/mod.rs b/time/src/serde/mod.rs index 6a2af3be5..506145364 100644 --- a/time/src/serde/mod.rs +++ b/time/src/serde/mod.rs @@ -102,7 +102,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// maybe_dt: Option, /// } /// ``` -/// +/// /// Define the format separately to be used in multiple places: /// ```rust,no_run /// # use time::OffsetDateTime; @@ -153,7 +153,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// let str_ts = OffsetDateTime::now_utc().format(DATE_TIME_FORMAT).unwrap(); /// } /// ``` -/// +/// /// Customize the configuration of ISO 8601 formatting/parsing: /// ```rust,no_run /// # use time::OffsetDateTime; @@ -203,7 +203,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// } /// # fn main() {} /// ``` -/// +/// /// [`format_description::parse()`]: crate::format_description::parse() #[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing")))] pub use time_macros::serde_format_description as format_description; @@ -211,7 +211,9 @@ pub use time_macros::serde_format_description as format_description; use self::visitor::Visitor; #[cfg(feature = "parsing")] use crate::format_description::{modifier, BorrowedFormatItem, Component}; -use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{ + Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; // region: Date /// The format used when serializing and deserializing a human-readable `Date`. @@ -366,6 +368,44 @@ impl<'a> Deserialize<'a> for PrimitiveDateTime { } // endregion PrimitiveDateTime +// region: UtcDateTime +/// The format used when serializing and deserializing a human-readable `UtcDateTime`. +#[cfg(feature = "parsing")] +const UTC_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = PRIMITIVE_DATE_TIME_FORMAT; + +impl Serialize for UtcDateTime { + fn serialize(&self, serializer: S) -> Result { + #[cfg(feature = "serde-human-readable")] + if serializer.is_human_readable() { + let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else { + return Err(S::Error::custom("failed formatting `UtcDateTime`")); + }; + return serializer.serialize_str(&s); + } + + ( + self.year(), + self.ordinal(), + self.hour(), + self.minute(), + self.second(), + self.nanosecond(), + ) + .serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for UtcDateTime { + fn deserialize>(deserializer: D) -> Result { + if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() { + deserializer.deserialize_any(Visitor::(PhantomData)) + } else { + deserializer.deserialize_tuple(6, Visitor::(PhantomData)) + } + } +} +// endregion UtcDateTime + // region: Time /// The format used when serializing and deserializing a human-readable `Time`. #[cfg(feature = "parsing")] diff --git a/time/src/serde/visitor.rs b/time/src/serde/visitor.rs index f45ecbb1f..b70340206 100644 --- a/time/src/serde/visitor.rs +++ b/time/src/serde/visitor.rs @@ -10,12 +10,14 @@ use serde::Deserializer; #[cfg(feature = "parsing")] use super::{ DATE_FORMAT, OFFSET_DATE_TIME_FORMAT, PRIMITIVE_DATE_TIME_FORMAT, TIME_FORMAT, - UTC_OFFSET_FORMAT, + UTC_DATE_TIME_FORMAT, UTC_OFFSET_FORMAT, }; use crate::error::ComponentRange; #[cfg(feature = "parsing")] use crate::format_description::well_known::*; -use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{ + Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, +}; /// A serde visitor for various types. pub(super) struct Visitor(pub(super) PhantomData); @@ -134,6 +136,33 @@ impl<'a> de::Visitor<'a> for Visitor { } } +impl<'a> de::Visitor<'a> for Visitor { + type Value = UtcDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a `PrimitiveDateTime`") + } + + #[cfg(feature = "parsing")] + fn visit_str(self, value: &str) -> Result { + UtcDateTime::parse(value, &UTC_DATE_TIME_FORMAT).map_err(E::custom) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let year = item!(seq, "year")?; + let ordinal = item!(seq, "day of year")?; + let hour = item!(seq, "hour")?; + let minute = item!(seq, "minute")?; + let second = item!(seq, "second")?; + let nanosecond = item!(seq, "nanosecond")?; + + Date::from_ordinal_date(year, ordinal) + .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) + .map(UtcDateTime::from_primitive) + .map_err(ComponentRange::into_de_error) + } +} + impl<'a> de::Visitor<'a> for Visitor