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 - } -}