From f69ef71c633b8060a8e330c1d0ade7d2217820f9 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sat, 27 Jan 2024 18:09:57 -0500 Subject: [PATCH] Remove internal `DateTime` struct While it would be great to have this in the future, Rust's functionality is not currently powerful enough to represent what is necessary without far too much overhead and needless complexity. --- tests/parsing.rs | 2 +- time/src/date_time/mod.rs | 1101 ------------------------------- time/src/date_time/offset_tz.rs | 240 ------- time/src/formatting/mod.rs | 5 +- time/src/internal_macros.rs | 5 +- time/src/lib.rs | 2 - time/src/offset_date_time.rs | 536 ++++++++++++--- time/src/parsing/parsable.rs | 33 +- time/src/parsing/parsed.rs | 65 +- time/src/primitive_date_time.rs | 260 ++++++-- time/src/quickcheck.rs | 38 +- 11 files changed, 692 insertions(+), 1595 deletions(-) delete mode 100644 time/src/date_time/mod.rs delete mode 100644 time/src/date_time/offset_tz.rs diff --git a/tests/parsing.rs b/tests/parsing.rs index 99756e5d0..0dc6887c9 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -400,7 +400,7 @@ fn rfc_3339_err() { )); assert!(matches!( PrimitiveDateTime::parse("2021-13-01T00:00:00Z", &Rfc3339), - Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "month" + invalid_component!("month") )); assert!(matches!( PrimitiveDateTime::parse("2021-01-02T03:04:60Z", &Rfc3339), diff --git a/time/src/date_time/mod.rs b/time/src/date_time/mod.rs deleted file mode 100644 index d9ad57c18..000000000 --- a/time/src/date_time/mod.rs +++ /dev/null @@ -1,1101 +0,0 @@ -//! The [`DateTime`] struct and its associated `impl`s. - -// TODO(jhpratt) Document everything before making public. -#![allow(clippy::missing_docs_in_private_items)] -// This is intentional, as the struct will likely be exposed at some point. -#![allow(unreachable_pub)] - -mod offset_tz; - -use core::cmp::Ordering; -use core::fmt; -use core::hash::{Hash, Hasher}; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::time::Duration as StdDuration; -#[cfg(feature = "formatting")] -use std::io; -#[cfg(feature = "std")] -use std::time::SystemTime; - -use deranged::RangedI64; -use num_conv::prelude::*; -use powerfmt::ext::FormatterExt; -use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; - -pub(crate) use self::offset_tz::*; -use crate::convert::*; -use crate::date::{MAX_YEAR, MIN_YEAR}; -#[cfg(feature = "formatting")] -use crate::formatting::Formattable; -use crate::internal_macros::{ - cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, impl_add_assign, - impl_sub_assign, -}; -#[cfg(feature = "parsing")] -use crate::parsing::Parsable; -use crate::{error, util, Date, Duration, Month, Time, UtcOffset, Weekday}; - -/// The Julian day of the Unix epoch. -// Safety: `ordinal` is not zero. -#[allow(clippy::undocumented_unsafe_blocks)] -const UNIX_EPOCH_JULIAN_DAY: i32 = - unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.to_julian_day(); - -pub struct DateTime { - pub(crate) date: Date, - pub(crate) time: Time, - pub(crate) offset: MemoryOffsetType, -} - -// Manual impl to remove extraneous bounds. -impl Clone for DateTime { - fn clone(&self) -> Self { - *self - } -} - -// Manual impl to remove extraneous bounds. -impl Copy for DateTime where MemoryOffsetType: Copy {} - -// region: constructors -impl DateTime { - pub const MIN: Self = Self { - date: Date::MIN, - time: Time::MIN, - offset: NoOffset, - }; - - pub const MAX: Self = Self { - date: Date::MAX, - time: Time::MAX, - offset: NoOffset, - }; -} - -impl DateTime { - pub const UNIX_EPOCH: Self = Self { - // Safety: `ordinal` is not zero. - date: unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }, - time: Time::MIDNIGHT, - offset: UtcOffset::UTC, - }; -} - -impl DateTime { - pub const fn new(date: Date, time: Time) -> Self - where - T: IsOffsetKindNone, - { - Self { - date, - time, - offset: NoOffset, - } - } - - pub const fn from_unix_timestamp(timestamp: i64) -> Result - where - T: HasLogicalOffset, - { - type Timestamp = RangedI64< - { Date::MIN.midnight().assume_utc().unix_timestamp() }, - { Date::MAX.with_time(Time::MAX).assume_utc().unix_timestamp() }, - >; - ensure_ranged!(Timestamp: timestamp); - - // Use the unchecked method here, as the input validity has already been verified. - let date = Date::from_julian_day_unchecked( - UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, Second::per(Day) as i64) as i32, - ); - - let seconds_within_day = timestamp.rem_euclid(Second::per(Day) as _); - // Safety: All values are in range. - let time = unsafe { - Time::__from_hms_nanos_unchecked( - (seconds_within_day / Second::per(Hour) as i64) as _, - ((seconds_within_day % Second::per(Hour) as i64) / Minute::per(Hour) as i64) as _, - (seconds_within_day % Second::per(Minute) as i64) as _, - 0, - ) - }; - - Ok(Self { - date, - time, - offset: offset_logical_to_memory::(UtcOffset::UTC), - }) - } - - pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result - where - T: HasLogicalOffset, - { - let datetime = const_try!(Self::from_unix_timestamp(div_floor!( - timestamp, - Nanosecond::per(Second) as i128 - ) as i64)); - - Ok(Self { - date: datetime.date, - // Safety: `nanosecond` is in range due to `rem_euclid`. - time: unsafe { - Time::__from_hms_nanos_unchecked( - datetime.hour(), - datetime.minute(), - datetime.second(), - timestamp.rem_euclid(Nanosecond::per(Second) as _) as u32, - ) - }, - offset: offset_logical_to_memory::(UtcOffset::UTC), - }) - } - // endregion constructors - - // region: now - // The return type will likely be loosened once `ZonedDateTime` is implemented. This is not a - // breaking change calls are currently limited to only `OffsetDateTime`. - #[cfg(feature = "std")] - pub fn now_utc() -> DateTime - where - T: IsOffsetKindFixed, - { - #[cfg(all( - target_family = "wasm", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" - ))] - { - js_sys::Date::new_0().into() - } - - #[cfg(not(all( - target_family = "wasm", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" - )))] - SystemTime::now().into() - } - - // The return type will likely be loosened once `ZonedDateTime` is implemented. This is not a - // breaking change calls are currently limited to only `OffsetDateTime`. - #[cfg(feature = "local-offset")] - pub fn now_local() -> Result, error::IndeterminateOffset> - where - T: IsOffsetKindFixed, - { - let t = DateTime::::now_utc(); - Ok(t.to_offset(UtcOffset::local_offset_at(crate::OffsetDateTime(t))?)) - } - // endregion now - - // region: getters - // region: component getters - pub const fn date(self) -> Date { - self.date - } - - pub const fn time(self) -> Time { - self.time - } - - pub const fn offset(self) -> UtcOffset - where - T: HasLogicalOffset, - { - offset_memory_to_logical::(self.offset) - } - // endregion component getters - - // region: date getters - pub const fn year(self) -> i32 { - self.date.year() - } - - pub const fn month(self) -> Month { - self.date.month() - } - - pub const fn day(self) -> u8 { - self.date.day() - } - - pub const fn ordinal(self) -> u16 { - self.date.ordinal() - } - - pub const fn iso_week(self) -> u8 { - self.date.iso_week() - } - - pub const fn sunday_based_week(self) -> u8 { - self.date.sunday_based_week() - } - - pub const fn monday_based_week(self) -> u8 { - self.date.monday_based_week() - } - - pub const fn to_calendar_date(self) -> (i32, Month, u8) { - self.date.to_calendar_date() - } - - pub const fn to_ordinal_date(self) -> (i32, u16) { - self.date.to_ordinal_date() - } - - pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { - self.date.to_iso_week_date() - } - - pub const fn weekday(self) -> Weekday { - self.date.weekday() - } - - pub const fn to_julian_day(self) -> i32 { - self.date.to_julian_day() - } - // endregion date getters - - // region: time getters - pub const fn as_hms(self) -> (u8, u8, u8) { - self.time.as_hms() - } - - pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { - self.time.as_hms_milli() - } - - pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { - self.time.as_hms_micro() - } - - pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { - self.time.as_hms_nano() - } - - pub const fn hour(self) -> u8 { - self.time.hour() - } - - pub const fn minute(self) -> u8 { - self.time.minute() - } - - pub const fn second(self) -> u8 { - self.time.second() - } - - pub const fn millisecond(self) -> u16 { - self.time.millisecond() - } - - pub const fn microsecond(self) -> u32 { - self.time.microsecond() - } - - pub const fn nanosecond(self) -> u32 { - self.time.nanosecond() - } - // endregion time getters - - // region: unix timestamp getters - pub const fn unix_timestamp(self) -> i64 - where - T: HasLogicalOffset, - { - let offset = offset_memory_to_logical::(self.offset).whole_seconds() as i64; - - let days = - (self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second::per(Day) as i64; - let hours = self.hour() as i64 * Second::per(Hour) as i64; - let minutes = self.minute() as i64 * Second::per(Minute) as i64; - let seconds = self.second() as i64; - days + hours + minutes + seconds - offset - } - - pub const fn unix_timestamp_nanos(self) -> i128 - where - T: HasLogicalOffset, - { - self.unix_timestamp() as i128 * Nanosecond::per(Second) as i128 + self.nanosecond() as i128 - } - // endregion unix timestamp getters - // endregion: getters - - // region: attach offset - pub const fn assume_offset(self, offset: UtcOffset) -> DateTime - where - T: SansLogicalOffset, - { - DateTime { - date: self.date, - time: self.time, - offset, - } - } - - pub const fn assume_utc(self) -> DateTime - where - T: SansLogicalOffset, - { - self.assume_offset(UtcOffset::UTC) - } - // endregion attach offset - - // region: to offset - pub const fn to_offset(self, offset: UtcOffset) -> DateTime - where - T: HasLogicalOffset, - { - expect_opt!( - self.checked_to_offset(offset), - "local datetime out of valid range" - ) - } - - pub const fn checked_to_offset(self, offset: UtcOffset) -> Option> - where - T: HasLogicalOffset, - { - let self_offset = offset_memory_to_logical::(self.offset); - - if self_offset.whole_hours() == offset.whole_hours() - && self_offset.minutes_past_hour() == offset.minutes_past_hour() - && self_offset.seconds_past_minute() == offset.seconds_past_minute() - { - return Some(DateTime { - date: self.date, - time: self.time, - offset, - }); - } - - let (year, ordinal, time) = self.to_offset_raw(offset); - - if year > MAX_YEAR || year < MIN_YEAR { - return None; - } - - Some(DateTime { - // Safety: `ordinal` is not zero. - date: unsafe { Date::__from_ordinal_date_unchecked(year, ordinal) }, - time, - offset, - }) - } - - /// 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) { - let Some(from) = offset_memory_to_logical_opt::(self.offset) else { - // No adjustment is needed because there is no offset. - return (self.year(), self.ordinal(), self.time); - }; - let to = offset; - - // Fast path for when no conversion is necessary. - if from.whole_hours() == to.whole_hours() - && from.minutes_past_hour() == to.minutes_past_hour() - && from.seconds_past_minute() == to.seconds_past_minute() - { - return (self.year(), self.ordinal(), self.time()); - } - - let mut second = self.second() as i16 - from.seconds_past_minute() as i16 - + to.seconds_past_minute() as i16; - let mut minute = - self.minute() as i16 - from.minutes_past_hour() as i16 + to.minutes_past_hour() as i16; - let mut hour = self.hour() as i8 - from.whole_hours() + to.whole_hours(); - let (mut year, ordinal) = self.to_ordinal_date(); - let mut ordinal = ordinal as i16; - - // Cascade the values twice. This is needed because the values are adjusted twice above. - cascade!(second in 0..Second::per(Minute) as i16 => minute); - cascade!(second in 0..Second::per(Minute) as i16 => minute); - cascade!(minute in 0..Minute::per(Hour) as i16 => hour); - cascade!(minute in 0..Minute::per(Hour) as i16 => hour); - cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); - cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); - cascade!(ordinal => year); - - debug_assert!(ordinal > 0); - debug_assert!(ordinal <= crate::util::days_in_year(year) as i16); - - ( - year, - ordinal as _, - // Safety: The cascades above ensure the values are in range. - unsafe { - Time::__from_hms_nanos_unchecked( - hour as _, - minute as _, - second as _, - self.nanosecond(), - ) - }, - ) - } - // endregion to offset - - // region: checked arithmetic - pub const fn checked_add(self, duration: Duration) -> Option { - let (date_adjustment, time) = self.time.adjusting_add(duration); - let date = const_try_opt!(self.date.checked_add(duration)); - - Some(Self { - date: match date_adjustment { - util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), - util::DateAdjustment::Next => const_try_opt!(date.next_day()), - util::DateAdjustment::None => date, - }, - time, - offset: self.offset, - }) - } - - pub const fn checked_sub(self, duration: Duration) -> Option { - let (date_adjustment, time) = self.time.adjusting_sub(duration); - let date = const_try_opt!(self.date.checked_sub(duration)); - - Some(Self { - date: match date_adjustment { - util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), - util::DateAdjustment::Next => const_try_opt!(date.next_day()), - util::DateAdjustment::None => date, - }, - time, - offset: self.offset, - }) - } - // endregion checked arithmetic - - // region: saturating arithmetic - pub const fn saturating_add(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_add(duration) { - datetime - } else if duration.is_negative() { - Self { - date: Date::MIN, - time: Time::MIN, - offset: self.offset, - } - } else { - Self { - date: Date::MAX, - time: Time::MAX, - offset: self.offset, - } - } - } - - pub const fn saturating_sub(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_sub(duration) { - datetime - } else if duration.is_negative() { - Self { - date: Date::MAX, - time: Time::MAX, - offset: self.offset, - } - } else { - Self { - date: Date::MIN, - time: Time::MIN, - offset: self.offset, - } - } - } - // endregion saturating arithmetic - - // region: replacement - #[must_use = "this does not modify the original value"] - pub const fn replace_time(self, time: Time) -> Self { - Self { - date: self.date, - time, - offset: self.offset, - } - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_date(self, date: Date) -> Self { - Self { - date, - time: self.time, - offset: self.offset, - } - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_date_time(self, date_time: DateTime) -> Self - where - T: HasLogicalOffset, - { - Self { - date: date_time.date, - time: date_time.time, - offset: self.offset, - } - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_year(self, year: i32) -> Result { - Ok(Self { - date: const_try!(self.date.replace_year(year)), - time: self.time, - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_month(self, month: Month) -> Result { - Ok(Self { - date: const_try!(self.date.replace_month(month)), - time: self.time, - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_day(self, day: u8) -> Result { - Ok(Self { - date: const_try!(self.date.replace_day(day)), - time: self.time, - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_hour(self, hour: u8) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_hour(hour)), - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_minute(self, minute: u8) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_minute(minute)), - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_second(self, second: u8) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_second(second)), - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_millisecond( - self, - millisecond: u16, - ) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_millisecond(millisecond)), - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_microsecond( - self, - microsecond: u32, - ) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_microsecond(microsecond)), - offset: self.offset, - }) - } - - #[must_use = "this does not modify the original value"] - pub const fn replace_nanosecond(self, nanosecond: u32) -> Result { - Ok(Self { - date: self.date, - time: const_try!(self.time.replace_nanosecond(nanosecond)), - offset: self.offset, - }) - } - - // Don't gate this on just having an offset, as `ZonedDateTime` cannot be set to an arbitrary - // offset. - #[must_use = "this does not modify the original value"] - pub const fn replace_offset(self, offset: UtcOffset) -> DateTime - where - T: IsOffsetKindFixed, - { - DateTime { - date: self.date, - time: self.time, - offset, - } - } - - // endregion replacement - - // region: formatting & parsing - #[cfg(feature = "formatting")] - pub fn format_into( - self, - output: &mut impl io::Write, - format: &(impl Formattable + ?Sized), - ) -> Result { - format.format_into( - output, - Some(self.date), - Some(self.time), - offset_memory_to_logical_opt::(self.offset), - ) - } - - #[cfg(feature = "formatting")] - pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result { - format.format( - Some(self.date), - Some(self.time), - offset_memory_to_logical_opt::(self.offset), - ) - } - - #[cfg(feature = "parsing")] - pub fn parse( - input: &str, - description: &(impl Parsable + ?Sized), - ) -> Result { - description.parse_date_time(input.as_bytes()) - } - - /// A helper method to check if the `OffsetDateTime` is a valid representation of a leap second. - /// Leap seconds, when parsed, are represented as the preceding nanosecond. However, leap - /// seconds can only occur as the last second of a month UTC. - #[cfg(feature = "parsing")] - pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool { - // Leap seconds aren't allowed if there is no offset. - if !T::HAS_LOGICAL_OFFSET { - return false; - } - - // This comparison doesn't need to be adjusted for the stored offset, so check it first for - // speed. - if self.nanosecond() != 999_999_999 { - return false; - } - - let (year, ordinal, time) = self.to_offset_raw(UtcOffset::UTC); - let Ok(date) = Date::from_ordinal_date(year, ordinal) else { - return false; - }; - - time.hour() == 23 - && time.minute() == 59 - && time.second() == 59 - && date.day() == util::days_in_year_month(year, date.month()) - } - - // endregion formatting & parsing - - // region: deprecated time getters - - // All the way at the bottom as it's low priority. These methods only exist for when - // `OffsetDateTime` is made an alias of `DateTime`. Consider hiding these methods from - // documentation in the future. - - #[doc(hidden)] - #[allow(dead_code)] // while functionally private - #[deprecated(since = "0.3.18", note = "use `as_hms` instead")] - pub const fn to_hms(self) -> (u8, u8, u8) - where - T: IsOffsetKindFixed, - { - self.time.as_hms() - } - - #[doc(hidden)] - #[allow(dead_code)] // while functionally private - #[deprecated(since = "0.3.18", note = "use `as_hms_milli` instead")] - pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) - where - T: IsOffsetKindFixed, - { - self.time.as_hms_milli() - } - - #[doc(hidden)] - #[allow(dead_code)] // while functionally private - #[deprecated(since = "0.3.18", note = "use `as_hms_micro` instead")] - pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) - where - T: IsOffsetKindFixed, - { - self.time.as_hms_micro() - } - - #[doc(hidden)] - #[allow(dead_code)] // while functionally private - #[deprecated(since = "0.3.18", note = "use `as_hms_nano` instead")] - pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) - where - T: IsOffsetKindFixed, - { - self.time.as_hms_nano() - } - // endregion deprecated time getters -} - -// region: trait impls -mod private { - use super::*; - - #[non_exhaustive] - #[derive(Debug, Clone, Copy)] - pub struct DateTimeMetadata { - pub(super) maybe_offset: Option, - } -} -pub(crate) use private::DateTimeMetadata; - -impl SmartDisplay for DateTime { - type Metadata = DateTimeMetadata; - - fn metadata(&self, _: FormatterOptions) -> Metadata { - let maybe_offset = offset_memory_to_logical_opt::(self.offset); - let width = match maybe_offset { - Some(offset) => smart_display::padded_width_of!(self.date, " ", self.time, " ", offset), - None => smart_display::padded_width_of!(self.date, " ", self.time), - }; - Metadata::new(width, self, DateTimeMetadata { maybe_offset }) - } - - fn fmt_with_metadata( - &self, - f: &mut fmt::Formatter<'_>, - metadata: Metadata, - ) -> fmt::Result { - match metadata.maybe_offset { - Some(offset) => f.pad_with_width( - metadata.unpadded_width(), - format_args!("{} {} {offset}", self.date, self.time), - ), - None => f.pad_with_width( - metadata.unpadded_width(), - format_args!("{} {}", self.date, self.time), - ), - } - } -} - -impl fmt::Display for DateTime { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - SmartDisplay::fmt(self, f) - } -} - -impl fmt::Debug for DateTime { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl PartialEq for DateTime { - fn eq(&self, rhs: &Self) -> bool { - if T::HAS_LOGICAL_OFFSET { - self.to_offset_raw(UtcOffset::UTC) == rhs.to_offset_raw(UtcOffset::UTC) - } else { - (self.date, self.time) == (rhs.date, rhs.time) - } - } -} - -impl Eq for DateTime {} - -impl PartialOrd for DateTime { - fn partial_cmp(&self, rhs: &Self) -> Option { - Some(self.cmp(rhs)) - } -} - -impl Ord for DateTime { - fn cmp(&self, rhs: &Self) -> Ordering { - if T::HAS_LOGICAL_OFFSET { - self.to_offset_raw(UtcOffset::UTC) - .cmp(&rhs.to_offset_raw(UtcOffset::UTC)) - } else { - (self.date, self.time).cmp(&(rhs.date, rhs.time)) - } - } -} - -impl Hash for DateTime { - fn hash(&self, hasher: &mut H) { - if T::HAS_LOGICAL_OFFSET { - self.to_offset_raw(UtcOffset::UTC).hash(hasher); - } else { - (self.date, self.time).hash(hasher); - } - } -} - -impl Add for DateTime { - type Output = Self; - - /// # Panics - /// - /// This may panic if an overflow occurs. - fn add(self, duration: Duration) -> Self { - self.checked_add(duration) - .expect("resulting value is out of range") - } -} - -impl Add for DateTime { - type Output = Self; - - /// # Panics - /// - /// This may panic if an overflow occurs. - fn add(self, duration: StdDuration) -> Self::Output { - let (is_next_day, time) = self.time.adjusting_add_std(duration); - - Self { - date: if is_next_day { - (self.date + duration) - .next_day() - .expect("resulting value is out of range") - } else { - self.date + duration - }, - time, - offset: self.offset, - } - } -} - -impl AddAssign for DateTime { - fn add_assign(&mut self, rhs: Duration) { - *self = *self + rhs; - } -} - -impl AddAssign for DateTime { - fn add_assign(&mut self, rhs: StdDuration) { - *self = *self + rhs; - } -} - -impl Sub for DateTime { - type Output = Self; - - /// # Panics - /// - /// This may panic if an overflow occurs. - fn sub(self, duration: Duration) -> Self { - self.checked_sub(duration) - .expect("resulting value is out of range") - } -} - -impl Sub for DateTime { - type Output = Self; - - /// # Panics - /// - /// This may panic if an overflow occurs. - fn sub(self, duration: StdDuration) -> Self::Output { - let (is_previous_day, time) = self.time.adjusting_sub_std(duration); - - Self { - date: if is_previous_day { - (self.date - duration) - .previous_day() - .expect("resulting value is out of range") - } else { - self.date - duration - }, - time, - offset: self.offset, - } - } -} - -impl SubAssign for DateTime { - fn sub_assign(&mut self, rhs: Duration) { - *self = *self - rhs; - } -} - -impl SubAssign for DateTime { - fn sub_assign(&mut self, rhs: StdDuration) { - *self = *self - rhs; - } -} - -impl Sub for DateTime { - type Output = Duration; - - fn sub(self, rhs: Self) -> Self::Output { - let base = (self.date - rhs.date) + (self.time - rhs.time); - - match ( - offset_memory_to_logical_opt::(self.offset), - offset_memory_to_logical_opt::(rhs.offset), - ) { - (Some(self_offset), Some(rhs_offset)) => { - let adjustment = Duration::seconds( - (self_offset.whole_seconds() - rhs_offset.whole_seconds()).extend::(), - ); - base - adjustment - } - (left, right) => { - debug_assert!( - left.is_none() && right.is_none(), - "offset type should not be different for the same type" - ); - base - } - } - } -} - -#[cfg(feature = "std")] -impl Add for SystemTime { - type Output = Self; - - fn add(self, duration: Duration) -> Self::Output { - if duration.is_zero() { - self - } else if duration.is_positive() { - self + duration.unsigned_abs() - } else { - debug_assert!(duration.is_negative()); - self - duration.unsigned_abs() - } - } -} - -impl_add_assign!(SystemTime: #[cfg(feature = "std")] Duration); - -#[cfg(feature = "std")] -impl Sub for SystemTime { - type Output = Self; - - fn sub(self, duration: Duration) -> Self::Output { - (DateTime::from(self) - duration).into() - } -} - -impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration); - -#[cfg(feature = "std")] -impl Sub for DateTime { - type Output = Duration; - - fn sub(self, rhs: SystemTime) -> Self::Output { - self - Self::from(rhs) - } -} - -#[cfg(feature = "std")] -impl Sub> for SystemTime { - type Output = Duration; - - fn sub(self, rhs: DateTime) -> Self::Output { - DateTime::::from(self) - rhs - } -} - -#[cfg(feature = "std")] -impl PartialEq for DateTime { - fn eq(&self, rhs: &SystemTime) -> bool { - self == &Self::from(*rhs) - } -} - -#[cfg(feature = "std")] -impl PartialEq> for SystemTime { - fn eq(&self, rhs: &DateTime) -> bool { - &DateTime::::from(*self) == rhs - } -} - -#[cfg(feature = "std")] -impl PartialOrd for DateTime { - fn partial_cmp(&self, other: &SystemTime) -> Option { - self.partial_cmp(&Self::from(*other)) - } -} - -#[cfg(feature = "std")] -impl PartialOrd> for SystemTime { - fn partial_cmp(&self, other: &DateTime) -> Option { - DateTime::::from(*self).partial_cmp(other) - } -} - -#[cfg(feature = "std")] -impl From for DateTime { - fn from(system_time: SystemTime) -> Self { - match system_time.duration_since(SystemTime::UNIX_EPOCH) { - Ok(duration) => Self::UNIX_EPOCH + duration, - Err(err) => Self::UNIX_EPOCH - err.duration(), - } - } -} - -#[allow(clippy::fallible_impl_from)] // caused by `debug_assert!` -#[cfg(feature = "std")] -impl From> for SystemTime { - fn from(datetime: DateTime) -> Self { - let duration = datetime - DateTime::::UNIX_EPOCH; - - if duration.is_zero() { - Self::UNIX_EPOCH - } else if duration.is_positive() { - Self::UNIX_EPOCH + duration.unsigned_abs() - } else { - debug_assert!(duration.is_negative()); - Self::UNIX_EPOCH - duration.unsigned_abs() - } - } -} - -#[allow(clippy::fallible_impl_from)] -#[cfg(all( - target_family = "wasm", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" -))] -impl From for DateTime { - /// # Panics - /// - /// This may panic if the timestamp can not be represented. - fn from(js_date: js_sys::Date) -> Self { - // get_time() returns milliseconds - let timestamp_nanos = (js_date.get_time() * Nanosecond::per(Millisecond) as f64) as i128; - Self::from_unix_timestamp_nanos(timestamp_nanos) - .expect("invalid timestamp: Timestamp cannot fit in range") - } -} - -#[cfg(all( - target_family = "wasm", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" -))] -impl From> for js_sys::Date { - fn from(datetime: DateTime) -> Self { - // new Date() takes milliseconds - let timestamp = (datetime.unix_timestamp_nanos() - / Nanosecond::per(Millisecond).cast_signed().extend::()) - as f64; - js_sys::Date::new(×tamp.into()) - } -} -// endregion trait impls diff --git a/time/src/date_time/offset_tz.rs b/time/src/date_time/offset_tz.rs deleted file mode 100644 index 68622e8bb..000000000 --- a/time/src/date_time/offset_tz.rs +++ /dev/null @@ -1,240 +0,0 @@ -#[cfg(feature = "parsing")] -use crate::error; -use crate::internal_macros::bug; -#[cfg(feature = "parsing")] -use crate::parsing::Parsed; -use crate::UtcOffset; - -pub(crate) type MemoryOffsetType = ::MemoryOffset; -pub(crate) type LogicalOffsetType = ::LogicalOffset; -pub(crate) type TzType = ::Tz; - -pub(crate) mod offset_kind { - #[derive(Debug, Clone, Copy)] - pub enum None {} - #[derive(Debug, Clone, Copy)] - pub enum Fixed {} -} - -#[derive(Debug, Clone, Copy)] -pub struct NoOffset; - -#[derive(Debug, Clone, Copy)] -pub struct NoTz; - -/// A type that is guaranteed to be either [`NoOffset`] or [`UtcOffset`]. -/// -/// # Safety -/// -/// This strait may only be implemented for [`NoOffset`] and [`UtcOffset`]. -pub unsafe trait MaybeOffsetType: Copy {} -// Safety: The trait is permitted to be implemented for this type. -unsafe impl MaybeOffsetType for NoOffset {} -// Safety: The trait is permitted to be implemented for this type. -unsafe impl MaybeOffsetType for UtcOffset {} - -/// A type that is guaranteed to be either [`NoTz`] or a type that implements `TimeZone`. -/// -/// # Safety -/// -/// This trait may only be implemented for [`NoTz`] and types that implement `TimeZone`. -pub unsafe trait MaybeTzType {} -// Safety: The trait is permitted to be implemented for this type. -unsafe impl MaybeTzType for NoTz {} -// TODO Add blanket implementation for all time zones here. - -/// # Safety -/// -/// - The associated type `Self_` must be `Self`. -/// - The associated const `HAS_MEMORY_OFFSET` must be `true` if and only if the associated type -/// `MemoryOffset` is `UtcOffset`. -/// - The associated const `HAS_LOGICAL_OFFSET` must be `true` if and only if the associated type -/// `LogicalOffset` is `UtcOffset`. -/// - The associated const `HAS_TZ` must be `true` if and only if the associated type `Tz` -/// implements `TimeZone`. -pub unsafe trait MaybeTz { - /// The offset type as it is stored in memory. - type MemoryOffset: MaybeOffsetType; - /// The offset type as it should be thought about. - /// - /// For example, a `DateTime` would have a logical offset type of [`UtcOffset`], but does - /// not actually store an offset in memory. - type LogicalOffset: MaybeOffsetType; - /// The type of the time zone, which is used to calculate the UTC offset at a given point in - /// time. - type Tz: MaybeTzType; - /// Required to be `Self`. Used for bound equality. - type Self_; - - /// True if and only if `Self::Memory` is `UtcOffset`. - const HAS_MEMORY_OFFSET: bool; - /// True if and only if `Self::Logical` is `UtcOffset`. - const HAS_LOGICAL_OFFSET: bool; - /// True if and only if `Self::Tz` implements `TimeZone`. - const HAS_TZ: bool; - /// `Some` if and only if the logical UTC offset is statically known. - // TODO(jhpratt) When const trait impls are stable, this can be removed in favor of - // `.as_offset_opt()`. - const STATIC_OFFSET: Option; - - fn offset_memory_to_logical_opt(offset: MemoryOffsetType) -> Option; - fn offset_memory_to_logical(offset: MemoryOffsetType) -> UtcOffset { - match Self::offset_memory_to_logical_opt(offset) { - Some(offset) => offset, - None => bug!("MaybeOffset::as_offset` called on a type without an offset in memory"), - } - } - fn offset_logical_to_memory(offset: UtcOffset) -> MemoryOffsetType; - fn as_tz(&self) -> &TzType; - - #[cfg(feature = "parsing")] - fn try_from_parsed(parsed: Parsed) -> Result, error::TryFromParsed>; -} - -// Safety: All requirements are upheld. -unsafe impl MaybeTz for offset_kind::None { - type MemoryOffset = NoOffset; - type LogicalOffset = NoOffset; - type Tz = NoTz; - - type Self_ = Self; - - const HAS_MEMORY_OFFSET: bool = false; - const HAS_LOGICAL_OFFSET: bool = false; - const HAS_TZ: bool = false; - const STATIC_OFFSET: Option = None; - - fn offset_memory_to_logical_opt(_: MemoryOffsetType) -> Option { - None - } - - fn offset_logical_to_memory(_: UtcOffset) -> MemoryOffsetType { - NoOffset - } - - fn as_tz(&self) -> &TzType { - &NoTz - } - - #[cfg(feature = "parsing")] - fn try_from_parsed(_: Parsed) -> Result, error::TryFromParsed> { - Ok(NoOffset) - } -} - -// Safety: All requirements are upheld. -unsafe impl MaybeTz for offset_kind::Fixed { - type MemoryOffset = UtcOffset; - type LogicalOffset = UtcOffset; - type Tz = NoTz; - - type Self_ = Self; - - const HAS_MEMORY_OFFSET: bool = true; - const HAS_LOGICAL_OFFSET: bool = true; - const HAS_TZ: bool = false; - const STATIC_OFFSET: Option = None; - - fn offset_memory_to_logical_opt(offset: MemoryOffsetType) -> Option { - Some(offset) - } - - fn offset_logical_to_memory(offset: UtcOffset) -> MemoryOffsetType { - offset - } - - fn as_tz(&self) -> &TzType { - &NoTz - } - - #[cfg(feature = "parsing")] - fn try_from_parsed(parsed: Parsed) -> Result, error::TryFromParsed> { - parsed.try_into() - } -} - -// region: const trait method hacks -// TODO(jhpratt) When const trait impls are stable, these methods can be removed in favor of the -// methods in `MaybeOffset`, which would then be made `const`. -pub(crate) const fn offset_memory_to_logical_opt( - offset: MemoryOffsetType, -) -> Option { - if T::STATIC_OFFSET.is_some() { - T::STATIC_OFFSET - } else if T::HAS_MEMORY_OFFSET { - #[repr(C)] // needed to guarantee they align at the start - union Convert { - input: MemoryOffsetType, - output: UtcOffset, - } - - // Safety: `T::HAS_OFFSET` indicates that `T::Offset` is `UtcOffset`. This code effectively - // performs a transmute from `T::Offset` to `UtcOffset`, which we know is the same type. - Some(unsafe { Convert:: { input: offset }.output }) - } else { - None - } -} - -pub(crate) const fn offset_memory_to_logical( - offset: MemoryOffsetType, -) -> LogicalOffsetType { - match offset_memory_to_logical_opt::(offset) { - Some(offset) => offset, - // Safety: `T` is bound by `HasLogicalOffset`. - None => unsafe { core::hint::unreachable_unchecked() }, - } -} - -// TODO Add `HasLogicalOffset` bound to `T` if possible. -pub(crate) const fn offset_logical_to_memory(offset: UtcOffset) -> MemoryOffsetType { - #[repr(C)] // needed to guarantee the types align at the start - union Convert { - input: UtcOffset, - output: MemoryOffsetType, - } - - // Safety: It is statically known that there are only two possibilities due to the trait bound - // of `T::MemoryOffsetType`, which ultimately relies on `MaybeOffsetType`. The two possibilities - // are: - // 1. UtcOffset -> UtcOffset - // 2. UtcOffset -> NoOffset - // (1) is valid because it is an identity conversion, which is always valid. (2) is valid - // because `NoOffset` is a 1-ZST, so converting to it is always valid. - unsafe { Convert:: { input: offset }.output } -} -// endregion const trait method hacks - -// region: marker traits -// Note: All traits in this region may be relied upon for soundness. The traits are not unsafe -// because the supertrait is unsafe. - -pub trait HasLogicalOffset: MaybeTz {} -impl> HasLogicalOffset for T {} - -pub trait SansLogicalOffset: MaybeTz + SansTz {} -impl + SansTz> SansLogicalOffset for T {} - -pub trait HasMemoryOffset: MaybeTz {} -impl> HasMemoryOffset for T {} - -pub trait SansMemoryOffset: MaybeTz {} -impl> SansMemoryOffset for T {} - -// TODO Add `HasTz` trait here. - -pub trait SansTz: MaybeTz {} -impl> SansTz for T {} - -pub trait IsOffsetKindNone: - MaybeTz + SansLogicalOffset + SansMemoryOffset + SansTz -{ -} -impl IsOffsetKindNone for offset_kind::None {} - -pub trait IsOffsetKindFixed: - MaybeTz + HasLogicalOffset + HasMemoryOffset + SansTz -{ -} -impl IsOffsetKindFixed for offset_kind::Fixed {} -// endregion marker traits diff --git a/time/src/formatting/mod.rs b/time/src/formatting/mod.rs index ece3cf5f6..b57c15222 100644 --- a/time/src/formatting/mod.rs +++ b/time/src/formatting/mod.rs @@ -457,10 +457,7 @@ fn fmt_unix_timestamp( sign_is_mandatory, }: modifier::UnixTimestamp, ) -> Result { - let date_time = date - .with_time(time) - .assume_offset(offset) - .to_offset(UtcOffset::UTC); + let date_time = OffsetDateTime::new_in_offset(date, time, offset).to_offset(UtcOffset::UTC); if date_time < OffsetDateTime::UNIX_EPOCH { write(output, b"-")?; diff --git a/time/src/internal_macros.rs b/time/src/internal_macros.rs index 857138983..e210573ec 100644 --- a/time/src/internal_macros.rs +++ b/time/src/internal_macros.rs @@ -184,6 +184,7 @@ macro_rules! expect_opt { } /// `unreachable!()`, but better. +#[cfg(any(feature = "formatting", feature = "parsing"))] macro_rules! bug { () => { compile_error!("provide an error message to help fix a possible bug") }; ($descr:literal $($rest:tt)?) => { @@ -191,7 +192,9 @@ macro_rules! bug { } } +#[cfg(any(feature = "formatting", feature = "parsing"))] +pub(crate) use bug; pub(crate) use { - __impl_assign, bug, cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, + __impl_assign, cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, impl_add_assign, impl_div_assign, impl_mul_assign, impl_sub_assign, }; diff --git a/time/src/lib.rs b/time/src/lib.rs index 110c28648..65496471e 100644 --- a/time/src/lib.rs +++ b/time/src/lib.rs @@ -86,7 +86,6 @@ extern crate alloc; mod date; -mod date_time; mod duration; pub mod error; pub mod ext; @@ -122,7 +121,6 @@ mod weekday; pub use time_core::convert; pub use crate::date::Date; -use crate::date_time::DateTime; pub use crate::duration::Duration; pub use crate::error::Error; #[cfg(feature = "std")] diff --git a/time/src/offset_date_time.rs b/time/src/offset_date_time.rs index dcdd2b2f9..311305b95 100644 --- a/time/src/offset_date_time.rs +++ b/time/src/offset_date_time.rs @@ -1,6 +1,5 @@ //! The [`OffsetDateTime`] struct and its associated `impl`s. -#[cfg(feature = "std")] use core::cmp::Ordering; #[cfg(feature = "std")] use core::convert::From; @@ -13,24 +12,63 @@ use std::io; #[cfg(feature = "std")] use std::time::SystemTime; -use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay}; +use deranged::RangedI64; +use num_conv::prelude::*; +use powerfmt::ext::FormatterExt as _; +use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; +use time_core::convert::{Day, Hour, Minute, Nanosecond, Second}; -use crate::date_time::{offset_kind, DateTimeMetadata}; +use crate::date::{MAX_YEAR, MIN_YEAR}; #[cfg(feature = "formatting")] use crate::formatting::Formattable; -use crate::internal_macros::{const_try, const_try_opt}; +use crate::internal_macros::{ + cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, +}; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -use crate::{error, Date, DateTime, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; +#[cfg(feature = "parsing")] +use crate::util; +use crate::{error, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; -/// The actual type doing all the work. -type Inner = DateTime; +/// The Julian day of the Unix epoch. +// Safety: `ordinal` is not zero. +#[allow(clippy::undocumented_unsafe_blocks)] +const UNIX_EPOCH_JULIAN_DAY: i32 = + unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.to_julian_day(); /// A [`PrimitiveDateTime`] with a [`UtcOffset`]. /// /// All comparisons are performed using the UTC time. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct OffsetDateTime(pub(crate) Inner); +#[derive(Clone, Copy, Eq)] +pub struct OffsetDateTime { + local_date_time: PrimitiveDateTime, + offset: UtcOffset, +} + +impl PartialEq for OffsetDateTime { + fn eq(&self, other: &Self) -> bool { + self.to_offset_raw(UtcOffset::UTC) == other.to_offset_raw(UtcOffset::UTC) + } +} + +impl PartialOrd for OffsetDateTime { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OffsetDateTime { + fn cmp(&self, other: &Self) -> Ordering { + self.to_offset_raw(UtcOffset::UTC) + .cmp(&other.to_offset_raw(UtcOffset::UTC)) + } +} + +impl Hash for OffsetDateTime { + fn hash(&self, state: &mut H) { + self.to_offset_raw(UtcOffset::UTC).hash(state); + } +} impl OffsetDateTime { /// Midnight, 1 January, 1970 (UTC). @@ -40,7 +78,12 @@ impl OffsetDateTime { /// # use time_macros::datetime; /// assert_eq!(OffsetDateTime::UNIX_EPOCH, datetime!(1970-01-01 0:00 UTC),); /// ``` - pub const UNIX_EPOCH: Self = Self(Inner::UNIX_EPOCH); + pub const UNIX_EPOCH: Self = Self::new_in_offset( + // Safety: `ordinal` is not zero. + unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }, + Time::MIDNIGHT, + UtcOffset::UTC, + ); // region: now /// Create a new `OffsetDateTime` with the current date and time in UTC. @@ -53,7 +96,21 @@ impl OffsetDateTime { /// ``` #[cfg(feature = "std")] pub fn now_utc() -> Self { - Self(Inner::now_utc()) + #[cfg(all( + target_family = "wasm", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" + ))] + { + js_sys::Date::new_0().into() + } + + #[cfg(not(all( + target_family = "wasm", + not(any(target_os = "emscripten", target_os = "wasi")), + feature = "wasm-bindgen" + )))] + SystemTime::now().into() } /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset. @@ -67,7 +124,8 @@ impl OffsetDateTime { /// ``` #[cfg(feature = "local-offset")] pub fn now_local() -> Result { - Inner::now_local().map(Self) + let t = Self::now_utc(); + Ok(t.to_offset(UtcOffset::local_offset_at(t)?)) } // endregion now @@ -85,7 +143,10 @@ impl OffsetDateTime { /// # Ok::<_, time::error::Error>(()) /// ``` pub const fn new_in_offset(date: Date, time: Time, offset: UtcOffset) -> Self { - PrimitiveDateTime::new(date, time).assume_offset(offset) + Self { + local_date_time: date.with_time(time), + offset, + } } /// Create a new `OffsetDateTime` with the given [`Date`] and [`Time`] in the UTC timezone. @@ -130,7 +191,10 @@ impl OffsetDateTime { /// /// This method panics if the local date-time in the new offset is outside the supported range. pub const fn to_offset(self, offset: UtcOffset) -> Self { - Self(self.0.to_offset(offset)) + expect_opt!( + self.checked_to_offset(offset), + "local datetime out of valid range" + ) } /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`], @@ -154,7 +218,74 @@ impl OffsetDateTime { /// ); /// ``` pub const fn checked_to_offset(self, offset: UtcOffset) -> Option { - Some(Self(const_try_opt!(self.0.checked_to_offset(offset)))) + if self.offset.whole_hours() == offset.whole_hours() + && self.offset.minutes_past_hour() == offset.minutes_past_hour() + && self.offset.seconds_past_minute() == offset.seconds_past_minute() + { + return Some(self.replace_offset(offset)); + } + + let (year, ordinal, time) = self.to_offset_raw(offset); + + if year > MAX_YEAR || year < MIN_YEAR { + return None; + } + + Some(Self::new_in_offset( + // Safety: `ordinal` is not zero. + unsafe { Date::__from_ordinal_date_unchecked(year, ordinal) }, + time, + offset, + )) + } + + /// 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) { + let from = self.offset; + let to = offset; + + // Fast path for when no conversion is necessary. + if from.whole_hours() == to.whole_hours() + && from.minutes_past_hour() == to.minutes_past_hour() + && from.seconds_past_minute() == to.seconds_past_minute() + { + return (self.year(), self.ordinal(), self.time()); + } + + let mut second = self.second() as i16 - from.seconds_past_minute() as i16 + + to.seconds_past_minute() as i16; + let mut minute = + self.minute() as i16 - from.minutes_past_hour() as i16 + to.minutes_past_hour() as i16; + let mut hour = self.hour() as i8 - from.whole_hours() + to.whole_hours(); + let (mut year, ordinal) = self.to_ordinal_date(); + let mut ordinal = ordinal as i16; + + // Cascade the values twice. This is needed because the values are adjusted twice above. + cascade!(second in 0..Second::per(Minute) as i16 => minute); + cascade!(second in 0..Second::per(Minute) as i16 => minute); + cascade!(minute in 0..Minute::per(Hour) as i16 => hour); + cascade!(minute in 0..Minute::per(Hour) as i16 => hour); + cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); + cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); + cascade!(ordinal => year); + + debug_assert!(ordinal > 0); + debug_assert!(ordinal <= crate::util::days_in_year(year) as i16); + + ( + year, + ordinal as _, + // Safety: The cascades above ensure the values are in range. + unsafe { + Time::__from_hms_nanos_unchecked( + hour as _, + minute as _, + second as _, + self.nanosecond(), + ) + }, + ) } // region: constructors @@ -187,7 +318,34 @@ impl OffsetDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub const fn from_unix_timestamp(timestamp: i64) -> Result { - Ok(Self(const_try!(Inner::from_unix_timestamp(timestamp)))) + type Timestamp = RangedI64< + { + OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC) + .unix_timestamp() + }, + { + OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::UTC).unix_timestamp() + }, + >; + ensure_ranged!(Timestamp: timestamp); + + // Use the unchecked method here, as the input validity has already been verified. + let date = Date::from_julian_day_unchecked( + UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, Second::per(Day) as i64) as i32, + ); + + let seconds_within_day = timestamp.rem_euclid(Second::per(Day) as _); + // Safety: All values are in range. + let time = unsafe { + Time::__from_hms_nanos_unchecked( + (seconds_within_day / Second::per(Hour) as i64) as _, + ((seconds_within_day % Second::per(Hour) as i64) / Minute::per(Hour) as i64) as _, + (seconds_within_day % Second::per(Minute) as i64) as _, + 0, + ) + }; + + Ok(Self::new_in_offset(date, time, UtcOffset::UTC)) } /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling @@ -206,9 +364,24 @@ impl OffsetDateTime { /// ); /// ``` pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result { - Ok(Self(const_try!(Inner::from_unix_timestamp_nanos( - timestamp - )))) + let datetime = const_try!(Self::from_unix_timestamp(div_floor!( + timestamp, + Nanosecond::per(Second) as i128 + ) as i64)); + + Ok(Self::new_in_offset( + datetime.date(), + // Safety: `nanosecond` is in range due to `rem_euclid`. + unsafe { + Time::__from_hms_nanos_unchecked( + datetime.hour(), + datetime.minute(), + datetime.second(), + timestamp.rem_euclid(Nanosecond::per(Second) as _) as u32, + ) + }, + UtcOffset::UTC, + )) } // endregion constructors @@ -221,7 +394,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1)); /// ``` pub const fn offset(self) -> UtcOffset { - self.0.offset() + self.offset } /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time). @@ -232,7 +405,13 @@ impl OffsetDateTime { /// assert_eq!(datetime!(1970-01-01 0:00 -1).unix_timestamp(), 3_600); /// ``` pub const fn unix_timestamp(self) -> i64 { - self.0.unix_timestamp() + let days = + (self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second::per(Day) as i64; + let hours = self.hour() as i64 * Second::per(Hour) as i64; + let minutes = self.minute() as i64 * Second::per(Minute) as i64; + let seconds = self.second() as i64; + let offset_seconds = self.offset.whole_seconds() as i64; + days + hours + minutes + seconds - offset_seconds } /// Get the Unix timestamp in nanoseconds. @@ -246,7 +425,12 @@ impl OffsetDateTime { /// ); /// ``` pub const fn unix_timestamp_nanos(self) -> i128 { - self.0.unix_timestamp_nanos() + self.unix_timestamp() as i128 * Nanosecond::per(Second) as i128 + self.nanosecond() as i128 + } + + /// Get the [`PrimitiveDateTime`] in the stored offset. + const fn date_time(self) -> PrimitiveDateTime { + self.local_date_time } /// Get the [`Date`] in the stored offset. @@ -262,7 +446,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn date(self) -> Date { - self.0.date() + self.date_time().date() } /// Get the [`Time`] in the stored offset. @@ -278,7 +462,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn time(self) -> Time { - self.0.time() + self.date_time().time() } // region: date getters @@ -296,7 +480,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020); /// ``` pub const fn year(self) -> i32 { - self.0.year() + self.date().year() } /// Get the month of the date in the stored offset. @@ -313,7 +497,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn month(self) -> Month { - self.0.month() + self.date().month() } /// Get the day of the date in the stored offset. @@ -331,7 +515,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn day(self) -> u8 { - self.0.day() + self.date().day() } /// Get the day of the year of the date in the stored offset. @@ -349,7 +533,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn ordinal(self) -> u16 { - self.0.ordinal() + self.date().ordinal() } /// Get the ISO week number of the date in the stored offset. @@ -364,7 +548,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53); /// ``` pub const fn iso_week(self) -> u8 { - self.0.iso_week() + self.date().iso_week() } /// Get the week number where week 1 begins on the first Sunday. @@ -379,7 +563,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2021-01-01 0:00 UTC).sunday_based_week(), 0); /// ``` pub const fn sunday_based_week(self) -> u8 { - self.0.sunday_based_week() + self.date().sunday_based_week() } /// Get the week number where week 1 begins on the first Monday. @@ -394,7 +578,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2021-01-01 0:00 UTC).monday_based_week(), 0); /// ``` pub const fn monday_based_week(self) -> u8 { - self.0.monday_based_week() + self.date().monday_based_week() } /// Get the year, month, and day. @@ -408,7 +592,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_calendar_date(self) -> (i32, Month, u8) { - self.0.to_calendar_date() + self.date().to_calendar_date() } /// Get the year and ordinal day number. @@ -421,7 +605,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_ordinal_date(self) -> (i32, u16) { - self.0.to_ordinal_date() + self.date().to_ordinal_date() } /// Get the ISO 8601 year, week number, and weekday. @@ -451,7 +635,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { - self.0.to_iso_week_date() + self.date().to_iso_week_date() } /// Get the weekday of the date in the stored offset. @@ -464,7 +648,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday); /// ``` pub const fn weekday(self) -> Weekday { - self.0.weekday() + self.date().weekday() } /// Get the Julian day for the date. The time is not taken into account for this calculation. @@ -480,7 +664,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-12-31 0:00 UTC).to_julian_day(), 2_458_849); /// ``` pub const fn to_julian_day(self) -> i32 { - self.0.to_julian_day() + self.date().to_julian_day() } // endregion date getters @@ -493,7 +677,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2020-01-01 23:59:59 UTC).to_hms(), (23, 59, 59)); /// ``` pub const fn to_hms(self) -> (u8, u8, u8) { - self.0.as_hms() + self.time().as_hms() } /// Get the clock hour, minute, second, and millisecond. @@ -510,7 +694,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) { - self.0.as_hms_milli() + self.time().as_hms_milli() } /// Get the clock hour, minute, second, and microsecond. @@ -527,7 +711,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) { - self.0.as_hms_micro() + self.time().as_hms_micro() } /// Get the clock hour, minute, second, and nanosecond. @@ -544,7 +728,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) { - self.0.as_hms_nano() + self.time().as_hms_nano() } /// Get the clock hour in the stored offset. @@ -562,7 +746,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn hour(self) -> u8 { - self.0.hour() + self.time().hour() } /// Get the minute within the hour in the stored offset. @@ -580,7 +764,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn minute(self) -> u8 { - self.0.minute() + self.time().minute() } /// Get the second within the minute in the stored offset. @@ -598,7 +782,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn second(self) -> u8 { - self.0.second() + self.time().second() } // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not @@ -614,7 +798,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999); /// ``` pub const fn millisecond(self) -> u16 { - self.0.millisecond() + self.time().millisecond() } /// Get the microseconds within the second in the stored offset. @@ -630,7 +814,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn microsecond(self) -> u32 { - self.0.microsecond() + self.time().microsecond() } /// Get the nanoseconds within the second in the stored offset. @@ -646,7 +830,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn nanosecond(self) -> u32 { - self.0.nanosecond() + self.time().nanosecond() } // endregion time getters // endregion getters @@ -669,7 +853,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option { - Some(Self(const_try_opt!(self.0.checked_add(duration)))) + Some(const_try_opt!(self.date_time().checked_add(duration)).assume_offset(self.offset())) } /// Computes `self - duration`, returning `None` if an overflow occurred. @@ -689,7 +873,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn checked_sub(self, duration: Duration) -> Option { - Some(Self(const_try_opt!(self.0.checked_sub(duration)))) + Some(const_try_opt!(self.date_time().checked_sub(duration)).assume_offset(self.offset())) } // endregion: checked arithmetic @@ -740,7 +924,13 @@ impl OffsetDateTime { /// ); /// ``` pub const fn saturating_add(self, duration: Duration) -> Self { - Self(self.0.saturating_add(duration)) + if let Some(datetime) = self.checked_add(duration) { + datetime + } else if duration.is_negative() { + PrimitiveDateTime::MIN.assume_offset(self.offset()) + } else { + PrimitiveDateTime::MAX.assume_offset(self.offset()) + } } /// Computes `self - duration`, saturating value on overflow. @@ -789,7 +979,13 @@ impl OffsetDateTime { /// ); /// ``` pub const fn saturating_sub(self, duration: Duration) -> Self { - Self(self.0.saturating_sub(duration)) + if let Some(datetime) = self.checked_sub(duration) { + datetime + } else if duration.is_negative() { + PrimitiveDateTime::MAX.assume_offset(self.offset()) + } else { + PrimitiveDateTime::MIN.assume_offset(self.offset()) + } } // endregion: saturating arithmetic } @@ -817,7 +1013,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_time(self, time: Time) -> Self { - Self(self.0.replace_time(time)) + Self::new_in_offset(self.date(), time, self.offset()) } /// Replace the date, which is assumed to be in the stored offset. The time and offset @@ -836,7 +1032,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_date(self, date: Date) -> Self { - Self(self.0.replace_date(date)) + Self::new_in_offset(date, self.time(), self.offset()) } /// Replace the date and time, which are assumed to be in the stored offset. The offset @@ -855,7 +1051,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_date_time(self, date_time: PrimitiveDateTime) -> Self { - Self(self.0.replace_date_time(date_time.0)) + date_time.assume_offset(self.offset()) } /// Replace the offset. The date and time components remain unchanged. @@ -869,7 +1065,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_offset(self, offset: UtcOffset) -> Self { - Self(self.0.replace_offset(offset)) + self.date_time().assume_offset(offset) } /// Replace the year. The month and day will be unchanged. @@ -884,7 +1080,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year /// ``` pub const fn replace_year(self, year: i32) -> Result { - Ok(Self(const_try!(self.0.replace_year(year)))) + Ok(const_try!(self.date_time().replace_year(year)).assume_offset(self.offset())) } /// Replace the month of the year. @@ -899,7 +1095,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 01 - 30 12:00 +01).replace_month(Month::February).is_err()); // 30 isn't a valid day in February /// ``` pub const fn replace_month(self, month: Month) -> Result { - Ok(Self(const_try!(self.0.replace_month(month)))) + Ok(const_try!(self.date_time().replace_month(month)).assume_offset(self.offset())) } /// Replace the day of the month. @@ -914,7 +1110,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day in February /// ``` pub const fn replace_day(self, day: u8) -> Result { - Ok(Self(const_try!(self.0.replace_day(day)))) + Ok(const_try!(self.date_time().replace_day(day)).assume_offset(self.offset())) } /// Replace the clock hour. @@ -928,7 +1124,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(24).is_err()); // 24 isn't a valid hour /// ``` pub const fn replace_hour(self, hour: u8) -> Result { - Ok(Self(const_try!(self.0.replace_hour(hour)))) + Ok(const_try!(self.date_time().replace_hour(hour)).assume_offset(self.offset())) } /// Replace the minutes within the hour. @@ -942,7 +1138,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(60).is_err()); // 60 isn't a valid minute /// ``` pub const fn replace_minute(self, minute: u8) -> Result { - Ok(Self(const_try!(self.0.replace_minute(minute)))) + Ok(const_try!(self.date_time().replace_minute(minute)).assume_offset(self.offset())) } /// Replace the seconds within the minute. @@ -956,7 +1152,7 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(60).is_err()); // 60 isn't a valid second /// ``` pub const fn replace_second(self, second: u8) -> Result { - Ok(Self(const_try!(self.0.replace_second(second)))) + Ok(const_try!(self.date_time().replace_second(second)).assume_offset(self.offset())) } /// Replace the milliseconds within the second. @@ -973,7 +1169,10 @@ impl OffsetDateTime { self, millisecond: u16, ) -> Result { - Ok(Self(const_try!(self.0.replace_millisecond(millisecond)))) + Ok( + const_try!(self.date_time().replace_millisecond(millisecond)) + .assume_offset(self.offset()), + ) } /// Replace the microseconds within the second. @@ -990,7 +1189,10 @@ impl OffsetDateTime { self, microsecond: u32, ) -> Result { - Ok(Self(const_try!(self.0.replace_microsecond(microsecond)))) + Ok( + const_try!(self.date_time().replace_microsecond(microsecond)) + .assume_offset(self.offset()), + ) } /// Replace the nanoseconds within the second. @@ -1004,7 +1206,10 @@ impl OffsetDateTime { /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond /// ``` pub const fn replace_nanosecond(self, nanosecond: u32) -> Result { - Ok(Self(const_try!(self.0.replace_nanosecond(nanosecond)))) + Ok( + const_try!(self.date_time().replace_nanosecond(nanosecond)) + .assume_offset(self.offset()), + ) } } // endregion replacement @@ -1019,7 +1224,12 @@ impl OffsetDateTime { output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result { - self.0.format_into(output, format) + format.format_into( + output, + Some(self.date()), + Some(self.time()), + Some(self.offset()), + ) } /// Format the `OffsetDateTime` using the provided [format @@ -1039,7 +1249,7 @@ impl OffsetDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result { - self.0.format(format) + format.format(Some(self.date()), Some(self.time()), Some(self.offset())) } } @@ -1065,15 +1275,39 @@ impl OffsetDateTime { input: &str, description: &(impl Parsable + ?Sized), ) -> Result { - Inner::parse(input, description).map(Self) + description.parse_offset_date_time(input.as_bytes()) + } + + /// A helper method to check if the `OffsetDateTime` is a valid representation of a leap second. + /// Leap seconds, when parsed, are represented as the preceding nanosecond. However, leap + /// seconds can only occur as the last second of a month UTC. + #[cfg(feature = "parsing")] + pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool { + // This comparison doesn't need to be adjusted for the stored offset, so check it first for + // speed. + if self.nanosecond() != 999_999_999 { + return false; + } + + let (year, ordinal, time) = self.to_offset_raw(UtcOffset::UTC); + let Ok(date) = Date::from_ordinal_date(year, ordinal) else { + return false; + }; + + time.hour() == 23 + && time.minute() == 59 + && time.second() == 59 + && date.day() == util::days_in_year_month(year, date.month()) } } impl SmartDisplay for OffsetDateTime { - type Metadata = DateTimeMetadata; + type Metadata = (); - fn metadata(&self, f: FormatterOptions) -> Metadata { - self.0.metadata(f).reuse() + fn metadata(&self, _: FormatterOptions) -> Metadata { + let width = + smart_display::padded_width_of!(self.date(), " ", self.time(), " ", self.offset()); + Metadata::new(width, self, ()) } fn fmt_with_metadata( @@ -1081,7 +1315,10 @@ impl SmartDisplay for OffsetDateTime { f: &mut fmt::Formatter<'_>, metadata: Metadata, ) -> fmt::Result { - self.0.fmt_with_metadata(f, metadata.reuse()) + f.pad_with_width( + metadata.unpadded_width(), + format_args!("{} {} {}", self.date(), self.time(), self.offset()), + ) } } @@ -1102,64 +1339,121 @@ impl fmt::Debug for OffsetDateTime { impl Add for OffsetDateTime { type Output = Self; - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0.add(rhs)) + /// # Panics + /// + /// This may panic if an overflow occurs. + fn add(self, duration: Duration) -> Self::Output { + self.checked_add(duration) + .expect("resulting value is out of range") } } impl Add for OffsetDateTime { type Output = Self; - fn add(self, rhs: StdDuration) -> Self::Output { - Self(self.0.add(rhs)) + /// # Panics + /// + /// This may panic if an overflow occurs. + fn add(self, duration: StdDuration) -> Self::Output { + let (is_next_day, time) = self.time().adjusting_add_std(duration); + + Self::new_in_offset( + if is_next_day { + (self.date() + duration) + .next_day() + .expect("resulting value is out of range") + } else { + self.date() + duration + }, + time, + self.offset, + ) } } impl AddAssign for OffsetDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn add_assign(&mut self, rhs: Duration) { - self.0.add_assign(rhs); + *self = *self + rhs; } } impl AddAssign for OffsetDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn add_assign(&mut self, rhs: StdDuration) { - self.0.add_assign(rhs); + *self = *self + rhs; } } impl Sub for OffsetDateTime { type Output = Self; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0.sub(rhs)) + self.checked_sub(rhs) + .expect("resulting value is out of range") } } impl Sub for OffsetDateTime { type Output = Self; - fn sub(self, rhs: StdDuration) -> Self::Output { - Self(self.0.sub(rhs)) + /// # Panics + /// + /// This may panic if an overflow occurs. + fn sub(self, duration: StdDuration) -> Self::Output { + let (is_previous_day, time) = self.time().adjusting_sub_std(duration); + + Self::new_in_offset( + if is_previous_day { + (self.date() - duration) + .previous_day() + .expect("resulting value is out of range") + } else { + self.date() - duration + }, + time, + self.offset, + ) } } impl SubAssign for OffsetDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub_assign(&mut self, rhs: Duration) { - self.0.sub_assign(rhs); + *self = *self - rhs; } } impl SubAssign for OffsetDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub_assign(&mut self, rhs: StdDuration) { - self.0.sub_assign(rhs); + *self = *self - rhs; } } impl Sub for OffsetDateTime { type Output = Duration; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, rhs: Self) -> Self::Output { - self.0.sub(rhs.0) + let base = self.date_time() - rhs.date_time(); + let adjustment = Duration::seconds( + (self.offset.whole_seconds() - rhs.offset.whole_seconds()).extend::(), + ); + base - adjustment } } @@ -1167,8 +1461,11 @@ impl Sub for OffsetDateTime { impl Sub for OffsetDateTime { type Output = Duration; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, rhs: SystemTime) -> Self::Output { - self.0.sub(rhs) + self - Self::from(rhs) } } @@ -1176,50 +1473,94 @@ impl Sub for OffsetDateTime { impl Sub for SystemTime { type Output = Duration; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, rhs: OffsetDateTime) -> Self::Output { - self.sub(rhs.0) + OffsetDateTime::from(self) - rhs + } +} + +#[cfg(feature = "std")] +impl Add for SystemTime { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + if duration.is_zero() { + self + } else if duration.is_positive() { + self + duration.unsigned_abs() + } else { + debug_assert!(duration.is_negative()); + self - duration.unsigned_abs() + } + } +} + +crate::internal_macros::impl_add_assign!(SystemTime: #[cfg(feature = "std")] Duration); + +#[cfg(feature = "std")] +impl Sub for SystemTime { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + (OffsetDateTime::from(self) - duration).into() } } +crate::internal_macros::impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration); + #[cfg(feature = "std")] impl PartialEq for OffsetDateTime { fn eq(&self, rhs: &SystemTime) -> bool { - self.0.eq(rhs) + self == &Self::from(*rhs) } } #[cfg(feature = "std")] impl PartialEq for SystemTime { fn eq(&self, rhs: &OffsetDateTime) -> bool { - self.eq(&rhs.0) + &OffsetDateTime::from(*self) == rhs } } #[cfg(feature = "std")] impl PartialOrd for OffsetDateTime { fn partial_cmp(&self, other: &SystemTime) -> Option { - self.0.partial_cmp(other) + self.partial_cmp(&Self::from(*other)) } } #[cfg(feature = "std")] impl PartialOrd for SystemTime { fn partial_cmp(&self, other: &OffsetDateTime) -> Option { - self.partial_cmp(&other.0) + OffsetDateTime::from(*self).partial_cmp(other) } } #[cfg(feature = "std")] impl From for OffsetDateTime { fn from(system_time: SystemTime) -> Self { - Self(Inner::from(system_time)) + match system_time.duration_since(SystemTime::UNIX_EPOCH) { + Ok(duration) => Self::UNIX_EPOCH + duration, + Err(err) => Self::UNIX_EPOCH - err.duration(), + } } } #[cfg(feature = "std")] impl From for SystemTime { fn from(datetime: OffsetDateTime) -> Self { - datetime.0.into() + let duration = datetime - OffsetDateTime::UNIX_EPOCH; + + if duration.is_zero() { + Self::UNIX_EPOCH + } else if duration.is_positive() { + Self::UNIX_EPOCH + duration.unsigned_abs() + } else { + debug_assert!(duration.is_negative()); + Self::UNIX_EPOCH - duration.unsigned_abs() + } } } @@ -1229,8 +1570,14 @@ impl From for SystemTime { feature = "wasm-bindgen" ))] impl From for OffsetDateTime { + /// # Panics + /// + /// This may panic if the timestamp can not be represented. fn from(js_date: js_sys::Date) -> Self { - Self(Inner::from(js_date)) + // get_time() returns milliseconds + let timestamp_nanos = (js_date.get_time() * Nanosecond::per(Millisecond) as f64) as i128; + Self::from_unix_timestamp_nanos(timestamp_nanos) + .expect("invalid timestamp: Timestamp cannot fit in range") } } @@ -1241,8 +1588,11 @@ impl From for OffsetDateTime { ))] impl From for js_sys::Date { fn from(datetime: OffsetDateTime) -> Self { - datetime.0.into() + // new Date() takes milliseconds + let timestamp = (datetime.unix_timestamp_nanos() + / Nanosecond::per(Millisecond).cast_signed().extend::()) + as f64; + js_sys::Date::new(×tamp.into()) } } - // endregion trait impls diff --git a/time/src/parsing/parsable.rs b/time/src/parsing/parsable.rs index 13adef87d..bed178283 100644 --- a/time/src/parsing/parsable.rs +++ b/time/src/parsing/parsable.rs @@ -4,7 +4,6 @@ use core::ops::Deref; use num_conv::prelude::*; -use crate::date_time::{offset_logical_to_memory, MaybeTz}; use crate::error::TryFromParsed; use crate::format_description::well_known::iso8601::EncodedConfig; use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; @@ -13,7 +12,7 @@ use crate::format_description::FormatItem; use crate::format_description::OwnedFormatItem; use crate::internal_macros::bug; use crate::parsing::{Parsed, ParsedItem}; -use crate::{error, Date, DateTime, Month, Time, UtcOffset, Weekday}; +use crate::{error, Date, Month, OffsetDateTime, Time, UtcOffset, Weekday}; /// A type that can be parsed. #[cfg_attr(__time_03_docs, doc(notable_trait))] @@ -35,6 +34,7 @@ impl Parsable for T where T::Target: Parsable {} mod sealed { #[allow(clippy::wildcard_imports)] use super::*; + use crate::{OffsetDateTime, PrimitiveDateTime}; /// Parse the item using a format description and an input. pub trait Sealed { @@ -77,8 +77,16 @@ mod sealed { Ok(self.parse(input)?.try_into()?) } - /// Parse a [`DateTime`] from the format description. - fn parse_date_time(&self, input: &[u8]) -> Result, error::Parse> { + /// Parse a [`PrimitiveDateTime`] from the format description. + fn parse_primitive_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()?) } } @@ -302,7 +310,7 @@ impl sealed::Sealed for Rfc2822 { Ok(input) } - fn parse_date_time(&self, input: &[u8]) -> Result, error::Parse> { + fn parse_offset_date_time(&self, input: &[u8]) -> Result { use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; use crate::parsing::combinator::{ @@ -441,9 +449,7 @@ impl sealed::Sealed for Rfc2822 { } let mut nanosecond = 0; - let leap_second_input = if !T::HAS_LOGICAL_OFFSET { - false - } else if second == 60 { + let leap_second_input = if second == 60 { second = 59; nanosecond = 999_999_999; true @@ -455,11 +461,7 @@ impl sealed::Sealed for Rfc2822 { let date = Date::from_calendar_date(year.cast_signed(), month, day)?; let time = Time::from_hms_nano(hour, minute, second, nanosecond)?; let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?; - Ok(DateTime { - date, - time, - offset: offset_logical_to_memory::(offset), - }) + Ok(OffsetDateTime::new_in_offset(date, time, offset)) })() .map_err(TryFromParsed::ComponentRange)?; @@ -586,7 +588,7 @@ impl sealed::Sealed for Rfc3339 { Ok(input) } - fn parse_date_time(&self, input: &[u8]) -> Result, error::Parse> { + fn parse_offset_date_time(&self, input: &[u8]) -> Result { use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; use crate::parsing::combinator::{ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, @@ -692,8 +694,7 @@ impl sealed::Sealed for Rfc3339 { .map_err(TryFromParsed::ComponentRange)?; let time = Time::from_hms_nano(hour, minute, second, nanosecond) .map_err(TryFromParsed::ComponentRange)?; - let offset = offset_logical_to_memory::(offset); - let dt = DateTime { date, time, offset }; + let dt = OffsetDateTime::new_in_offset(date, time, offset); if leap_second_input && !dt.is_valid_leap_second_stand_in() { return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( diff --git a/time/src/parsing/parsed.rs b/time/src/parsing/parsed.rs index 1b220286a..e6451c197 100644 --- a/time/src/parsing/parsed.rs +++ b/time/src/parsing/parsed.rs @@ -10,12 +10,11 @@ use num_conv::prelude::*; use crate::convert::{Day, Hour, Minute, Nanosecond, Second}; use crate::date::{MAX_YEAR, MIN_YEAR}; -use crate::date_time::{offset_kind, offset_logical_to_memory, DateTime, MaybeTz}; use crate::error::TryFromParsed::InsufficientInformation; #[cfg(feature = "alloc")] use crate::format_description::OwnedFormatItem; use crate::format_description::{modifier, Component, FormatItem}; -use crate::internal_macros::const_try_opt; +use crate::internal_macros::{bug, const_try_opt}; use crate::parsing::component::{ parse_day, parse_end, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour, parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second, @@ -160,13 +159,13 @@ pub struct Parsed { offset_second: OptionRangedI8<{ -((Second::per(Minute) - 1) as i8) }, { (Second::per(Minute) - 1) as _ }>, /// The Unix timestamp in nanoseconds. - // unix_timestamp_nanos: MaybeUninit, unix_timestamp_nanos: OptionRangedI128< - { Date::MIN.midnight().assume_utc().unix_timestamp_nanos() }, { - Date::MAX - .with_time(Time::MAX) - .assume_utc() + OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC) + .unix_timestamp_nanos() + }, + { + OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::UTC) .unix_timestamp_nanos() }, >, @@ -842,61 +841,45 @@ impl TryFrom for UtcOffset { } impl TryFrom for PrimitiveDateTime { - type Error = as TryFrom>::Error; + type Error = error::TryFromParsed; fn try_from(parsed: Parsed) -> Result { - parsed.try_into().map(Self) + Ok(Self::new(parsed.try_into()?, parsed.try_into()?)) } } impl TryFrom for OffsetDateTime { - type Error = as TryFrom>::Error; - - fn try_from(parsed: Parsed) -> Result { - parsed.try_into().map(Self) - } -} - -impl TryFrom for DateTime { type Error = error::TryFromParsed; - #[allow(clippy::unwrap_in_result)] // We know the values are valid. fn try_from(mut parsed: Parsed) -> Result { - if T::HAS_LOGICAL_OFFSET { - if let Some(timestamp) = parsed.unix_timestamp_nanos() { - let DateTime { date, time, offset } = - DateTime::::from_unix_timestamp_nanos(timestamp)?; - - let mut value = Self { - date, - time, - offset: offset_logical_to_memory::(offset), - }; - if let Some(subsecond) = parsed.subsecond() { - value = value.replace_nanosecond(subsecond)?; - } - return Ok(value); + 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) { - parsed.set_second(59).expect("59 is a valid second"); - parsed - .set_subsecond(999_999_999) - .expect("999_999_999 is a valid subsecond"); + 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 = Self { - date: Date::try_from(parsed)?, - time: Time::try_from(parsed)?, - offset: T::try_from_parsed(parsed)?, - }; + let dt = Self::new_in_offset( + Date::try_from(parsed)?, + Time::try_from(parsed)?, + UtcOffset::try_from(parsed)?, + ); if leap_second_input && !dt.is_valid_leap_second_stand_in() { return Err(error::TryFromParsed::ComponentRange( diff --git a/time/src/primitive_date_time.rs b/time/src/primitive_date_time.rs index 83a94610f..e99e060a1 100644 --- a/time/src/primitive_date_time.rs +++ b/time/src/primitive_date_time.rs @@ -6,22 +6,22 @@ use core::time::Duration as StdDuration; #[cfg(feature = "formatting")] use std::io; -use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay}; +use powerfmt::ext::FormatterExt as _; +use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; -use crate::date_time::{offset_kind, DateTimeMetadata}; #[cfg(feature = "formatting")] use crate::formatting::Formattable; use crate::internal_macros::{const_try, const_try_opt}; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -use crate::{error, Date, DateTime, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; - -/// The actual type doing all the work. -type Inner = DateTime; +use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; /// Combined date and time. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct PrimitiveDateTime(#[allow(clippy::missing_docs_in_private_items)] pub(crate) Inner); +pub struct PrimitiveDateTime { + date: Date, + time: Time, +} impl PrimitiveDateTime { /// The smallest value that can be represented by `PrimitiveDateTime`. @@ -51,7 +51,10 @@ impl PrimitiveDateTime { doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));" )] /// ``` - pub const MIN: Self = Self(Inner::MIN); + pub const MIN: Self = Self { + date: Date::MIN, + time: Time::MIDNIGHT, + }; /// The largest value that can be represented by `PrimitiveDateTime`. /// @@ -80,7 +83,10 @@ impl PrimitiveDateTime { doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));" )] /// ``` - pub const MAX: Self = Self(Inner::MAX); + pub const MAX: Self = Self { + date: Date::MAX, + time: Time::MAX, + }; /// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`]. /// @@ -93,7 +99,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn new(date: Date, time: Time) -> Self { - Self(Inner::new(date, time)) + Self { date, time } } // region: component getters @@ -104,7 +110,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01)); /// ``` pub const fn date(self) -> Date { - self.0.date() + self.date } /// Get the [`Time`] component of the `PrimitiveDateTime`. @@ -114,7 +120,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00)); /// ``` pub const fn time(self) -> Time { - self.0.time() + self.time } // endregion component getters @@ -128,7 +134,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020); /// ``` pub const fn year(self) -> i32 { - self.0.year() + self.date().year() } /// Get the month of the date. @@ -140,7 +146,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December); /// ``` pub const fn month(self) -> Month { - self.0.month() + self.date().month() } /// Get the day of the date. @@ -153,7 +159,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).day(), 31); /// ``` pub const fn day(self) -> u8 { - self.0.day() + self.date().day() } /// Get the day of the year. @@ -166,7 +172,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365); /// ``` pub const fn ordinal(self) -> u16 { - self.0.ordinal() + self.date().ordinal() } /// Get the ISO week number. @@ -182,7 +188,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53); /// ``` pub const fn iso_week(self) -> u8 { - self.0.iso_week() + self.date().iso_week() } /// Get the week number where week 1 begins on the first Sunday. @@ -197,7 +203,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).sunday_based_week(), 0); /// ``` pub const fn sunday_based_week(self) -> u8 { - self.0.sunday_based_week() + self.date().sunday_based_week() } /// Get the week number where week 1 begins on the first Monday. @@ -212,7 +218,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).monday_based_week(), 0); /// ``` pub const fn monday_based_week(self) -> u8 { - self.0.monday_based_week() + self.date().monday_based_week() } /// Get the year, month, and day. @@ -226,7 +232,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn to_calendar_date(self) -> (i32, Month, u8) { - self.0.to_calendar_date() + self.date().to_calendar_date() } /// Get the year and ordinal day number. @@ -236,7 +242,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1)); /// ``` pub const fn to_ordinal_date(self) -> (i32, u16) { - self.0.to_ordinal_date() + self.date().to_ordinal_date() } /// Get the ISO 8601 year, week number, and weekday. @@ -266,7 +272,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { - self.0.to_iso_week_date() + self.date().to_iso_week_date() } /// Get the weekday. @@ -288,7 +294,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday); /// ``` pub const fn weekday(self) -> Weekday { - self.0.weekday() + self.date().weekday() } /// Get the Julian day for the date. The time is not taken into account for this calculation. @@ -304,7 +310,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).to_julian_day(), 2_458_849); /// ``` pub const fn to_julian_day(self) -> i32 { - self.0.to_julian_day() + self.date().to_julian_day() } // endregion date getters @@ -317,7 +323,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2020-01-01 23:59:59).as_hms(), (23, 59, 59)); /// ``` pub const fn as_hms(self) -> (u8, u8, u8) { - self.0.as_hms() + self.time().as_hms() } /// Get the clock hour, minute, second, and millisecond. @@ -331,7 +337,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { - self.0.as_hms_milli() + self.time().as_hms_milli() } /// Get the clock hour, minute, second, and microsecond. @@ -345,7 +351,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { - self.0.as_hms_micro() + self.time().as_hms_micro() } /// Get the clock hour, minute, second, and nanosecond. @@ -359,7 +365,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { - self.0.as_hms_nano() + self.time().as_hms_nano() } /// Get the clock hour. @@ -372,7 +378,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23); /// ``` pub const fn hour(self) -> u8 { - self.0.hour() + self.time().hour() } /// Get the minute within the hour. @@ -385,7 +391,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59); /// ``` pub const fn minute(self) -> u8 { - self.0.minute() + self.time().minute() } /// Get the second within the minute. @@ -398,7 +404,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59); /// ``` pub const fn second(self) -> u8 { - self.0.second() + self.time().second() } /// Get the milliseconds within the second. @@ -411,7 +417,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999); /// ``` pub const fn millisecond(self) -> u16 { - self.0.millisecond() + self.time().millisecond() } /// Get the microseconds within the second. @@ -427,7 +433,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn microsecond(self) -> u32 { - self.0.microsecond() + self.time().microsecond() } /// Get the nanoseconds within the second. @@ -443,7 +449,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn nanosecond(self) -> u32 { - self.0.nanosecond() + self.time().nanosecond() } // endregion time getters @@ -467,7 +473,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime { - OffsetDateTime(self.0.assume_offset(offset)) + OffsetDateTime::new_in_offset(self.date, self.time, offset) } /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an @@ -481,7 +487,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn assume_utc(self) -> OffsetDateTime { - OffsetDateTime(self.0.assume_utc()) + self.assume_offset(UtcOffset::UTC) } // endregion attach offset @@ -503,7 +509,17 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option { - Some(Self(const_try_opt!(self.0.checked_add(duration)))) + let (date_adjustment, time) = self.time.adjusting_add(duration); + let date = const_try_opt!(self.date.checked_add(duration)); + + Some(Self { + date: match date_adjustment { + util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), + util::DateAdjustment::Next => const_try_opt!(date.next_day()), + util::DateAdjustment::None => date, + }, + time, + }) } /// Computes `self - duration`, returning `None` if an overflow occurred. @@ -523,7 +539,17 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn checked_sub(self, duration: Duration) -> Option { - Some(Self(const_try_opt!(self.0.checked_sub(duration)))) + let (date_adjustment, time) = self.time.adjusting_sub(duration); + let date = const_try_opt!(self.date.checked_sub(duration)); + + Some(Self { + date: match date_adjustment { + util::DateAdjustment::Previous => const_try_opt!(date.previous_day()), + util::DateAdjustment::Next => const_try_opt!(date.next_day()), + util::DateAdjustment::None => date, + }, + time, + }) } // endregion: checked arithmetic @@ -549,7 +575,13 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn saturating_add(self, duration: Duration) -> Self { - Self(self.0.saturating_add(duration)) + if let Some(datetime) = self.checked_add(duration) { + datetime + } else if duration.is_negative() { + Self::MIN + } else { + Self::MAX + } } /// Computes `self - duration`, saturating value on overflow. @@ -573,7 +605,13 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn saturating_sub(self, duration: Duration) -> Self { - Self(self.0.saturating_sub(duration)) + if let Some(datetime) = self.checked_sub(duration) { + datetime + } else if duration.is_negative() { + Self::MAX + } else { + Self::MIN + } } // endregion: saturating arithmetic } @@ -592,7 +630,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_time(self, time: Time) -> Self { - Self(self.0.replace_time(time)) + Self { + date: self.date, + time, + } } /// Replace the date, preserving the time. @@ -606,7 +647,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_date(self, date: Date) -> Self { - Self(self.0.replace_date(date)) + Self { + date, + time: self.time, + } } /// Replace the year. The month and day will be unchanged. @@ -622,7 +666,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_year(self, year: i32) -> Result { - Ok(Self(const_try!(self.0.replace_year(year)))) + Ok(Self { + date: const_try!(self.date.replace_year(year)), + time: self.time, + }) } /// Replace the month of the year. @@ -638,7 +685,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_month(self, month: Month) -> Result { - Ok(Self(const_try!(self.0.replace_month(month)))) + Ok(Self { + date: const_try!(self.date.replace_month(month)), + time: self.time, + }) } /// Replace the day of the month. @@ -654,7 +704,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_day(self, day: u8) -> Result { - Ok(Self(const_try!(self.0.replace_day(day)))) + Ok(Self { + date: const_try!(self.date.replace_day(day)), + time: self.time, + }) } /// Replace the clock hour. @@ -669,7 +722,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_hour(self, hour: u8) -> Result { - Ok(Self(const_try!(self.0.replace_hour(hour)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_hour(hour)), + }) } /// Replace the minutes within the hour. @@ -684,7 +740,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_minute(self, minute: u8) -> Result { - Ok(Self(const_try!(self.0.replace_minute(minute)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_minute(minute)), + }) } /// Replace the seconds within the minute. @@ -699,7 +758,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_second(self, second: u8) -> Result { - Ok(Self(const_try!(self.0.replace_second(second)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_second(second)), + }) } /// Replace the milliseconds within the second. @@ -717,7 +779,10 @@ impl PrimitiveDateTime { self, millisecond: u16, ) -> Result { - Ok(Self(const_try!(self.0.replace_millisecond(millisecond)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_millisecond(millisecond)), + }) } /// Replace the microseconds within the second. @@ -735,7 +800,10 @@ impl PrimitiveDateTime { self, microsecond: u32, ) -> Result { - Ok(Self(const_try!(self.0.replace_microsecond(microsecond)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_microsecond(microsecond)), + }) } /// Replace the nanoseconds within the second. @@ -750,7 +818,10 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_nanosecond(self, nanosecond: u32) -> Result { - Ok(Self(const_try!(self.0.replace_nanosecond(nanosecond)))) + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_nanosecond(nanosecond)), + }) } } // endregion replacement @@ -765,7 +836,7 @@ impl PrimitiveDateTime { output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result { - self.0.format_into(output, format) + format.format_into(output, Some(self.date), Some(self.time), None) } /// Format the `PrimitiveDateTime` using the provided [format @@ -782,7 +853,7 @@ impl PrimitiveDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result { - self.0.format(format) + format.format(Some(self.date), Some(self.time), None) } } @@ -805,15 +876,16 @@ impl PrimitiveDateTime { input: &str, description: &(impl Parsable + ?Sized), ) -> Result { - Inner::parse(input, description).map(Self) + description.parse_primitive_date_time(input.as_bytes()) } } impl SmartDisplay for PrimitiveDateTime { - type Metadata = DateTimeMetadata; + type Metadata = (); - fn metadata(&self, f: FormatterOptions) -> Metadata { - self.0.metadata(f).reuse() + fn metadata(&self, _: FormatterOptions) -> Metadata { + let width = smart_display::padded_width_of!(self.date, " ", self.time); + Metadata::new(width, self, ()) } fn fmt_with_metadata( @@ -821,7 +893,10 @@ impl SmartDisplay for PrimitiveDateTime { f: &mut fmt::Formatter<'_>, metadata: Metadata, ) -> fmt::Result { - self.0.fmt_with_metadata(f, metadata.reuse()) + f.pad_with_width( + metadata.unpadded_width(), + format_args!("{} {}", self.date, self.time), + ) } } @@ -842,64 +917,115 @@ impl fmt::Debug for PrimitiveDateTime { impl Add for PrimitiveDateTime { type Output = Self; + /// # Panics + /// + /// This may panic if an overflow occurs. fn add(self, duration: Duration) -> Self::Output { - Self(self.0.add(duration)) + self.checked_add(duration) + .expect("resulting value is out of range") } } impl Add for PrimitiveDateTime { type Output = Self; + /// # Panics + /// + /// This may panic if an overflow occurs. fn add(self, duration: StdDuration) -> Self::Output { - Self(self.0.add(duration)) + let (is_next_day, time) = self.time.adjusting_add_std(duration); + + Self { + date: if is_next_day { + (self.date + duration) + .next_day() + .expect("resulting value is out of range") + } else { + self.date + duration + }, + time, + } } } impl AddAssign for PrimitiveDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn add_assign(&mut self, duration: Duration) { - self.0.add_assign(duration); + *self = *self + duration; } } impl AddAssign for PrimitiveDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn add_assign(&mut self, duration: StdDuration) { - self.0.add_assign(duration); + *self = *self + duration; } } impl Sub for PrimitiveDateTime { type Output = Self; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, duration: Duration) -> Self::Output { - Self(self.0.sub(duration)) + self.checked_sub(duration) + .expect("resulting value is out of range") } } impl Sub for PrimitiveDateTime { type Output = Self; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, duration: StdDuration) -> Self::Output { - Self(self.0.sub(duration)) + let (is_previous_day, time) = self.time.adjusting_sub_std(duration); + + Self { + date: if is_previous_day { + (self.date - duration) + .previous_day() + .expect("resulting value is out of range") + } else { + self.date - duration + }, + time, + } } } impl SubAssign for PrimitiveDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub_assign(&mut self, duration: Duration) { - self.0.sub_assign(duration); + *self = *self - duration; } } impl SubAssign for PrimitiveDateTime { + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub_assign(&mut self, duration: StdDuration) { - self.0.sub_assign(duration); + *self = *self - duration; } } impl Sub for PrimitiveDateTime { type Output = Duration; + /// # Panics + /// + /// This may panic if an overflow occurs. fn sub(self, rhs: Self) -> Self::Output { - self.0.sub(rhs.0) + (self.date - rhs.date) + (self.time - rhs.time) } } // endregion trait impls diff --git a/time/src/quickcheck.rs b/time/src/quickcheck.rs index 2224eb142..312a09e19 100644 --- a/time/src/quickcheck.rs +++ b/time/src/quickcheck.rs @@ -38,7 +38,6 @@ use alloc::boxed::Box; use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen}; -use crate::date_time::{DateTime, MaybeTz, MemoryOffsetType, NoOffset}; use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; /// Obtain an arbitrary value between the minimum and maximum inclusive. @@ -116,11 +115,15 @@ impl Arbitrary for Time { impl Arbitrary for PrimitiveDateTime { fn arbitrary(g: &mut Gen) -> Self { - Self(<_>::arbitrary(g)) + Self::new(<_>::arbitrary(g), <_>::arbitrary(g)) } fn shrink(&self) -> Box> { - Box::new(self.0.shrink().map(Self)) + Box::new( + (self.date(), self.time()) + .shrink() + .map(|(date, time)| Self::new(date, time)), + ) } } @@ -140,31 +143,14 @@ impl Arbitrary for UtcOffset { impl Arbitrary for OffsetDateTime { fn arbitrary(g: &mut Gen) -> Self { - Self(<_>::arbitrary(g)) - } - - fn shrink(&self) -> Box> { - Box::new(self.0.shrink().map(Self)) - } -} - -impl Arbitrary for DateTime -where - MemoryOffsetType: Arbitrary, -{ - fn arbitrary(g: &mut Gen) -> Self { - Self { - date: <_>::arbitrary(g), - time: <_>::arbitrary(g), - offset: <_>::arbitrary(g), - } + Self::new_in_offset(<_>::arbitrary(g), <_>::arbitrary(g), <_>::arbitrary(g)) } fn shrink(&self) -> Box> { Box::new( - (self.date, self.time, self.offset) + (self.date(), self.time(), self.offset()) .shrink() - .map(|(date, time, offset)| Self { date, time, offset }), + .map(|(date, time, offset)| Self::new_in_offset(date, time, offset)), ) } } @@ -223,9 +209,3 @@ impl Arbitrary for Month { } } } - -impl Arbitrary for NoOffset { - fn arbitrary(_: &mut Gen) -> Self { - Self - } -}