diff options
Diffstat (limited to 'vendor/time/src')
30 files changed, 3035 insertions, 1173 deletions
diff --git a/vendor/time/src/date_time.rs b/vendor/time/src/date_time.rs new file mode 100644 index 000000000..592ced1fb --- /dev/null +++ b/vendor/time/src/date_time.rs @@ -0,0 +1,1158 @@ +//! 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)] + +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::mem::size_of; +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 crate::date::{MAX_YEAR, MIN_YEAR}; +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +#[cfg(feature = "parsing")] +use crate::parsing::{Parsable, Parsed}; +use crate::{error, util, Date, Duration, Month, Time, UtcOffset, Weekday}; + +#[allow(missing_debug_implementations, missing_copy_implementations)] +pub(crate) mod offset_kind { + pub enum None {} + pub enum Fixed {} +} + +pub(crate) use sealed::MaybeOffset; +use sealed::*; +mod sealed { + use super::*; + + /// A type that is guaranteed to be either `()` or [`UtcOffset`]. + /// + /// **Do not** add any additional implementations of this trait. + #[allow(unreachable_pub)] // intentional + pub trait MaybeOffsetType {} + impl MaybeOffsetType for () {} + impl MaybeOffsetType for UtcOffset {} + + pub trait MaybeOffset: Sized { + /// The offset type as it is stored in memory. + #[cfg(feature = "quickcheck")] + type MemoryOffsetType: Copy + MaybeOffsetType + quickcheck::Arbitrary; + #[cfg(not(feature = "quickcheck"))] + type MemoryOffsetType: Copy + MaybeOffsetType; + + /// The offset type as it should be thought about. + /// + /// For example, a `DateTime<Utc>` has a logical offset type of [`UtcOffset`], but does not + /// actually store an offset in memory. + type LogicalOffsetType: Copy + MaybeOffsetType; + + /// Required to be `Self`. Used for bound equality. + type Self_; + + /// True if and only if `Self::LogicalOffsetType` is `UtcOffset`. + const HAS_LOGICAL_OFFSET: bool = + size_of::<Self::LogicalOffsetType>() == size_of::<UtcOffset>(); + /// True if and only if `Self::MemoryOffsetType` is `UtcOffset`. + const HAS_MEMORY_OFFSET: bool = + size_of::<Self::MemoryOffsetType>() == size_of::<UtcOffset>(); + + /// `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<UtcOffset>; + + #[cfg(feature = "parsing")] + fn try_from_parsed(parsed: Parsed) -> Result<Self::MemoryOffsetType, error::TryFromParsed>; + } + + // Traits to indicate whether a `MaybeOffset` has a logical offset type of `UtcOffset` or not. + + pub trait HasLogicalOffset: MaybeOffset<LogicalOffsetType = UtcOffset> {} + impl<T: MaybeOffset<LogicalOffsetType = UtcOffset>> HasLogicalOffset for T {} + + pub trait NoLogicalOffset: MaybeOffset<LogicalOffsetType = ()> {} + impl<T: MaybeOffset<LogicalOffsetType = ()>> NoLogicalOffset for T {} + + // Traits to indicate whether a `MaybeOffset` has a memory offset type of `UtcOffset` or not. + + pub trait HasMemoryOffset: MaybeOffset<MemoryOffsetType = UtcOffset> {} + impl<T: MaybeOffset<MemoryOffsetType = UtcOffset>> HasMemoryOffset for T {} + + pub trait NoMemoryOffset: MaybeOffset<MemoryOffsetType = ()> {} + impl<T: MaybeOffset<MemoryOffsetType = ()>> NoMemoryOffset for T {} + + // Traits to indicate backing type being implemented. + + pub trait IsOffsetKindNone: + MaybeOffset<Self_ = offset_kind::None, MemoryOffsetType = (), LogicalOffsetType = ()> + { + } + impl IsOffsetKindNone for offset_kind::None {} + + pub trait IsOffsetKindFixed: + MaybeOffset< + Self_ = offset_kind::Fixed, + MemoryOffsetType = UtcOffset, + LogicalOffsetType = UtcOffset, + > + { + } + impl IsOffsetKindFixed for offset_kind::Fixed {} +} + +impl MaybeOffset for offset_kind::None { + type MemoryOffsetType = (); + type LogicalOffsetType = (); + + type Self_ = Self; + + const STATIC_OFFSET: Option<UtcOffset> = None; + + #[cfg(feature = "parsing")] + fn try_from_parsed(_: Parsed) -> Result<(), error::TryFromParsed> { + Ok(()) + } +} + +impl MaybeOffset for offset_kind::Fixed { + type MemoryOffsetType = UtcOffset; + type LogicalOffsetType = UtcOffset; + + type Self_ = Self; + + const STATIC_OFFSET: Option<UtcOffset> = None; + + #[cfg(feature = "parsing")] + fn try_from_parsed(parsed: Parsed) -> Result<UtcOffset, 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 methods +// in `MaybeOffset`, which would then be made `const`. +const fn maybe_offset_as_offset_opt<O: MaybeOffset>( + offset: O::MemoryOffsetType, +) -> Option<UtcOffset> { + if O::STATIC_OFFSET.is_some() { + O::STATIC_OFFSET + } else if O::HAS_MEMORY_OFFSET { + union Convert<O: MaybeOffset> { + input: O::MemoryOffsetType, + output: UtcOffset, + } + + // Safety: `O::HAS_OFFSET` indicates that `O::Offset` is `UtcOffset`. This code effectively + // performs a transmute from `O::Offset` to `UtcOffset`, which we know is the same type. + Some(unsafe { Convert::<O> { input: offset }.output }) + } else { + None + } +} + +const fn maybe_offset_as_offset<O: MaybeOffset + HasLogicalOffset>( + offset: O::MemoryOffsetType, +) -> UtcOffset { + match maybe_offset_as_offset_opt::<O>(offset) { + Some(offset) => offset, + None => bug!("`MaybeOffset::as_offset` called on a type without an offset in memory"), + } +} + +pub(crate) const fn maybe_offset_from_offset<O: MaybeOffset>( + offset: UtcOffset, +) -> O::MemoryOffsetType { + union Convert<O: MaybeOffset> { + input: UtcOffset, + output: O::MemoryOffsetType, + } + + // Safety: It is statically known that there are only two possibilities due to the trait bound + // of `O::MemoryOffsetType`, which ultimately relies on `MaybeOffsetType`. The two possibilities + // are: + // 1. UtcOffset -> UtcOffset + // 2. UtcOffset -> () + // (1) is valid because it is an identity conversion, which is always valid. (2) is valid + // because `()` is a 1-ZST, so converting to it is always valid. + unsafe { Convert::<O> { input: offset }.output } +} +// endregion const trait methods hacks + +/// The Julian day of the Unix epoch. +const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day(); + +pub struct DateTime<O: MaybeOffset> { + pub(crate) date: Date, + pub(crate) time: Time, + pub(crate) offset: O::MemoryOffsetType, +} + +// Manual impl to remove extraneous bounds. +impl<O: MaybeOffset> Clone for DateTime<O> { + fn clone(&self) -> Self { + *self + } +} + +// Manual impl to remove extraneous bounds. +impl<O: MaybeOffset> Copy for DateTime<O> {} + +// region: constructors +impl DateTime<offset_kind::None> { + pub const MIN: Self = Self { + date: Date::MIN, + time: Time::MIN, + offset: (), + }; + + pub const MAX: Self = Self { + date: Date::MAX, + time: Time::MAX, + offset: (), + }; +} + +impl DateTime<offset_kind::Fixed> { + pub const UNIX_EPOCH: Self = Self { + date: Date::__from_ordinal_date_unchecked(1970, 1), + time: Time::MIDNIGHT, + offset: UtcOffset::UTC, + }; +} + +impl<O: MaybeOffset> DateTime<O> { + pub const fn new(date: Date, time: Time) -> Self + where + O: IsOffsetKindNone, + { + Self { + date, + time, + offset: (), + } + } + + pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange> + where + O: HasLogicalOffset, + { + #[allow(clippy::missing_docs_in_private_items)] + const MIN_TIMESTAMP: i64 = Date::MIN.midnight().assume_utc().unix_timestamp(); + #[allow(clippy::missing_docs_in_private_items)] + const MAX_TIMESTAMP: i64 = Date::MAX + .with_time(Time::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999)) + .assume_utc() + .unix_timestamp(); + + ensure_value_in_range!(timestamp in MIN_TIMESTAMP => MAX_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, 86_400) as i32, + ); + + let seconds_within_day = timestamp.rem_euclid(86_400); + let time = Time::__from_hms_nanos_unchecked( + (seconds_within_day / 3_600) as _, + ((seconds_within_day % 3_600) / 60) as _, + (seconds_within_day % 60) as _, + 0, + ); + + Ok(Self { + date, + time, + offset: maybe_offset_from_offset::<O>(UtcOffset::UTC), + }) + } + + pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange> + where + O: HasLogicalOffset, + { + let datetime = const_try!(Self::from_unix_timestamp( + div_floor!(timestamp, 1_000_000_000) as i64 + )); + + Ok(Self { + date: datetime.date, + time: Time::__from_hms_nanos_unchecked( + datetime.hour(), + datetime.minute(), + datetime.second(), + timestamp.rem_euclid(1_000_000_000) as u32, + ), + offset: maybe_offset_from_offset::<O>(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<offset_kind::Fixed> + where + O: 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<DateTime<offset_kind::Fixed>, error::IndeterminateOffset> + where + O: IsOffsetKindFixed, + { + let t = DateTime::<offset_kind::Fixed>::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 + O: HasLogicalOffset, + { + maybe_offset_as_offset::<O>(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 + O: HasLogicalOffset, + { + let offset = maybe_offset_as_offset::<O>(self.offset).whole_seconds() as i64; + + let days = (self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * 86_400; + let hours = self.hour() as i64 * 3_600; + let minutes = self.minute() as i64 * 60; + let seconds = self.second() as i64; + days + hours + minutes + seconds - offset + } + + pub const fn unix_timestamp_nanos(self) -> i128 + where + O: HasLogicalOffset, + { + self.unix_timestamp() as i128 * 1_000_000_000 + self.nanosecond() as i128 + } + // endregion unix timestamp getters + // endregion: getters + + // region: attach offset + pub const fn assume_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed> + where + O: NoLogicalOffset, + { + DateTime { + date: self.date, + time: self.time, + offset, + } + } + + pub const fn assume_utc(self) -> DateTime<offset_kind::Fixed> + where + O: NoLogicalOffset, + { + self.assume_offset(UtcOffset::UTC) + } + // endregion attach offset + + // region: to offset + pub const fn to_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed> + where + O: HasLogicalOffset, + { + let self_offset = maybe_offset_as_offset::<O>(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 DateTime { + date: self.date, + time: self.time, + offset, + }; + } + + let (year, ordinal, time) = self.to_offset_raw(offset); + + if year > MAX_YEAR || year < MIN_YEAR { + panic!("local datetime out of valid range"); + } + + DateTime { + date: 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) { + guard!(let Some(from) = maybe_offset_as_offset_opt::<O>(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..60 => minute); + cascade!(second in 0..60 => minute); + cascade!(minute in 0..60 => hour); + cascade!(minute in 0..60 => hour); + cascade!(hour in 0..24 => ordinal); + cascade!(hour in 0..24 => ordinal); + cascade!(ordinal => year); + + debug_assert!(ordinal > 0); + debug_assert!(ordinal <= crate::util::days_in_year(year) as i16); + + ( + year, + ordinal as _, + 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<Self> { + 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<Self> { + 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 + pub const fn replace_time(self, time: Time) -> Self { + Self { + date: self.date, + time, + offset: self.offset, + } + } + + pub const fn replace_date(self, date: Date) -> Self { + Self { + date, + time: self.time, + offset: self.offset, + } + } + + pub const fn replace_date_time(self, date_time: DateTime<offset_kind::None>) -> Self + where + O: HasLogicalOffset, + { + Self { + date: date_time.date, + time: date_time.time, + offset: self.offset, + } + } + + pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: const_try!(self.date.replace_year(year)), + time: self.time, + offset: self.offset, + }) + } + + pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: const_try!(self.date.replace_month(month)), + time: self.time, + offset: self.offset, + }) + } + + pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: const_try!(self.date.replace_day(day)), + time: self.time, + offset: self.offset, + }) + } + + pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_hour(hour)), + offset: self.offset, + }) + } + + pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_minute(minute)), + offset: self.offset, + }) + } + + pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_second(second)), + offset: self.offset, + }) + } + + pub const fn replace_millisecond( + self, + millisecond: u16, + ) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_millisecond(millisecond)), + offset: self.offset, + }) + } + + pub const fn replace_microsecond( + self, + microsecond: u32, + ) -> Result<Self, error::ComponentRange> { + Ok(Self { + date: self.date, + time: const_try!(self.time.replace_microsecond(microsecond)), + offset: self.offset, + }) + } + + pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { + 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. + pub const fn replace_offset(self, offset: UtcOffset) -> DateTime<offset_kind::Fixed> + where + O: 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<usize, error::Format> { + format.format_into( + output, + Some(self.date), + Some(self.time), + maybe_offset_as_offset_opt::<O>(self.offset), + ) + } + + #[cfg(feature = "formatting")] + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format( + Some(self.date), + Some(self.time), + maybe_offset_as_offset_opt::<O>(self.offset), + ) + } + + #[cfg(feature = "parsing")] + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + 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 !O::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); + guard!(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<Fixed>`. Consider hiding these methods from + // documentation in the future. + + #[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 + O: IsOffsetKindFixed, + { + self.time.as_hms() + } + + #[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 + O: IsOffsetKindFixed, + { + self.time.as_hms_milli() + } + + #[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 + O: IsOffsetKindFixed, + { + self.time.as_hms_micro() + } + + #[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 + O: IsOffsetKindFixed, + { + self.time.as_hms_nano() + } + // endregion deprecated time getters +} + +impl<O: MaybeOffset> fmt::Debug for DateTime<O> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + <Self as fmt::Display>::fmt(self, f) + } +} + +impl<O: MaybeOffset> fmt::Display for DateTime<O> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.date, self.time)?; + if let Some(offset) = maybe_offset_as_offset_opt::<O>(self.offset) { + write!(f, " {offset}")?; + } + Ok(()) + } +} + +// region: trait impls +impl<O: MaybeOffset> PartialEq for DateTime<O> { + fn eq(&self, rhs: &Self) -> bool { + if O::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<O: MaybeOffset> Eq for DateTime<O> {} + +impl<O: MaybeOffset> PartialOrd for DateTime<O> { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(self.cmp(rhs)) + } +} + +impl<O: MaybeOffset> Ord for DateTime<O> { + fn cmp(&self, rhs: &Self) -> Ordering { + if O::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<O: MaybeOffset> Hash for DateTime<O> { + fn hash<H: Hasher>(&self, hasher: &mut H) { + if O::HAS_LOGICAL_OFFSET { + self.to_offset_raw(UtcOffset::UTC).hash(hasher); + } else { + (self.date, self.time).hash(hasher); + } + } +} + +impl<O: MaybeOffset> Add<Duration> for DateTime<O> { + type Output = Self; + + fn add(self, duration: Duration) -> Self { + self.checked_add(duration) + .expect("resulting value is out of range") + } +} + +impl<O: MaybeOffset> Add<StdDuration> for DateTime<O> { + type Output = Self; + + 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<O: MaybeOffset> AddAssign<Duration> for DateTime<O> { + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl<O: MaybeOffset> AddAssign<StdDuration> for DateTime<O> { + fn add_assign(&mut self, rhs: StdDuration) { + *self = *self + rhs; + } +} + +impl<O: MaybeOffset> Sub<Duration> for DateTime<O> { + type Output = Self; + + fn sub(self, duration: Duration) -> Self { + self.checked_sub(duration) + .expect("resulting value is out of range") + } +} + +impl<O: MaybeOffset> Sub<StdDuration> for DateTime<O> { + type Output = Self; + + 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<O: MaybeOffset> SubAssign<Duration> for DateTime<O> { + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl<O: MaybeOffset> SubAssign<StdDuration> for DateTime<O> { + fn sub_assign(&mut self, rhs: StdDuration) { + *self = *self - rhs; + } +} + +impl<O: MaybeOffset> Sub<Self> for DateTime<O> { + type Output = Duration; + + fn sub(self, rhs: Self) -> Self::Output { + let base = (self.date - rhs.date) + (self.time - rhs.time); + + match ( + maybe_offset_as_offset_opt::<O>(self.offset), + maybe_offset_as_offset_opt::<O>(rhs.offset), + ) { + (Some(self_offset), Some(rhs_offset)) => { + let adjustment = Duration::seconds( + (self_offset.whole_seconds() - rhs_offset.whole_seconds()) as i64, + ); + 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<Duration> 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<Duration> 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<SystemTime> for DateTime<offset_kind::Fixed> { + type Output = Duration; + + fn sub(self, rhs: SystemTime) -> Self::Output { + self - Self::from(rhs) + } +} + +#[cfg(feature = "std")] +impl Sub<DateTime<offset_kind::Fixed>> for SystemTime { + type Output = Duration; + + fn sub(self, rhs: DateTime<offset_kind::Fixed>) -> Self::Output { + DateTime::<offset_kind::Fixed>::from(self) - rhs + } +} + +#[cfg(feature = "std")] +impl PartialEq<SystemTime> for DateTime<offset_kind::Fixed> { + fn eq(&self, rhs: &SystemTime) -> bool { + self == &Self::from(*rhs) + } +} + +#[cfg(feature = "std")] +impl PartialEq<DateTime<offset_kind::Fixed>> for SystemTime { + fn eq(&self, rhs: &DateTime<offset_kind::Fixed>) -> bool { + &DateTime::<offset_kind::Fixed>::from(*self) == rhs + } +} + +#[cfg(feature = "std")] +impl PartialOrd<SystemTime> for DateTime<offset_kind::Fixed> { + fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> { + self.partial_cmp(&Self::from(*other)) + } +} + +#[cfg(feature = "std")] +impl PartialOrd<DateTime<offset_kind::Fixed>> for SystemTime { + fn partial_cmp(&self, other: &DateTime<offset_kind::Fixed>) -> Option<Ordering> { + DateTime::<offset_kind::Fixed>::from(*self).partial_cmp(other) + } +} + +#[cfg(feature = "std")] +impl From<SystemTime> for DateTime<offset_kind::Fixed> { + 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<DateTime<offset_kind::Fixed>> for SystemTime { + fn from(datetime: DateTime<offset_kind::Fixed>) -> Self { + let duration = datetime - DateTime::<offset_kind::Fixed>::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<js_sys::Date> for DateTime<offset_kind::Fixed> { + fn from(js_date: js_sys::Date) -> Self { + // get_time() returns milliseconds + let timestamp_nanos = (js_date.get_time() * 1_000_000.0) 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<DateTime<offset_kind::Fixed>> for js_sys::Date { + fn from(datetime: DateTime<offset_kind::Fixed>) -> Self { + // new Date() takes milliseconds + let timestamp = (datetime.unix_timestamp_nanos() / 1_000_000) as f64; + js_sys::Date::new(×tamp.into()) + } +} +// endregion trait impls diff --git a/vendor/time/src/duration.rs b/vendor/time/src/duration.rs index f8d916f45..47c3f1516 100644 --- a/vendor/time/src/duration.rs +++ b/vendor/time/src/duration.rs @@ -326,7 +326,10 @@ impl Duration { if seconds.is_nan() { crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"); } - Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _) + let seconds_truncated = seconds as i64; + // This only works because we handle the overflow condition above. + let nanoseconds = ((seconds - seconds_truncated as f64) * 1_000_000_000.) as i32; + Self::new_unchecked(seconds_truncated, nanoseconds) } /// Creates a new `Duration` from the specified number of seconds represented as `f32`. @@ -343,7 +346,10 @@ impl Duration { if seconds.is_nan() { crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"); } - Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _) + let seconds_truncated = seconds as i64; + // This only works because we handle the overflow condition above. + let nanoseconds = ((seconds - seconds_truncated as f32) * 1_000_000_000.) as i32; + Self::new_unchecked(seconds_truncated, nanoseconds) } /// Create a new `Duration` with the given number of milliseconds. diff --git a/vendor/time/src/error/invalid_format_description.rs b/vendor/time/src/error/invalid_format_description.rs index 29c46edb1..4b312ce2d 100644 --- a/vendor/time/src/error/invalid_format_description.rs +++ b/vendor/time/src/error/invalid_format_description.rs @@ -37,6 +37,32 @@ pub enum InvalidFormatDescription { /// The zero-based index where the component name should start. index: usize, }, + /// A required modifier is missing. + #[non_exhaustive] + MissingRequiredModifier { + /// The name of the modifier that is missing. + name: &'static str, + /// The zero-based index of the component. + index: usize, + }, + /// Something was expected, but not found. + #[non_exhaustive] + Expected { + /// What was expected to be present, but wasn't. + what: &'static str, + /// The zero-based index the item was expected to be found at. + index: usize, + }, + /// Certain behavior is not supported in the given context. + #[non_exhaustive] + NotSupported { + /// The behavior that is not supported. + what: &'static str, + /// The context in which the behavior is not supported. + context: &'static str, + /// The zero-based index the error occurred at. + index: usize, + }, } impl From<InvalidFormatDescription> for crate::Error { @@ -72,6 +98,28 @@ impl fmt::Display for InvalidFormatDescription { MissingComponentName { index } => { write!(f, "missing component name at byte index {index}") } + MissingRequiredModifier { name, index } => { + write!( + f, + "missing required modifier `{name}` for component at byte index {index}" + ) + } + Expected { + what: expected, + index, + } => { + write!(f, "expected {expected} at byte index {index}") + } + NotSupported { + what, + context, + index, + } => { + write!( + f, + "{what} is not supported in {context} at byte index {index}" + ) + } } } } diff --git a/vendor/time/src/format_description/component.rs b/vendor/time/src/format_description/component.rs index 68d162e26..672559081 100644 --- a/vendor/time/src/format_description/component.rs +++ b/vendor/time/src/format_description/component.rs @@ -34,4 +34,8 @@ pub enum Component { OffsetMinute(modifier::OffsetMinute), /// Second within the minute of the UTC offset. OffsetSecond(modifier::OffsetSecond), + /// A number of bytes to ignore when parsing. This has no effect on formatting. + Ignore(modifier::Ignore), + /// A Unix timestamp. + UnixTimestamp(modifier::UnixTimestamp), } diff --git a/vendor/time/src/format_description/mod.rs b/vendor/time/src/format_description/mod.rs index 7712288e7..4d328f675 100644 --- a/vendor/time/src/format_description/mod.rs +++ b/vendor/time/src/format_description/mod.rs @@ -2,8 +2,7 @@ //! //! The formatted value will be output to the provided writer. Format descriptions can be //! [well-known](crate::format_description::well_known) or obtained by using the -//! [`format_description!`](crate::macros::format_description) macro, the -//! [`format_description::parse`](crate::format_description::parse()) function. +//! [`format_description!`](crate::macros::format_description) macro or a function listed below. mod borrowed_format_item; mod component; @@ -19,7 +18,7 @@ pub use owned_format_item::OwnedFormatItem; pub use self::component::Component; #[cfg(feature = "alloc")] -pub use self::parse::{parse, parse_owned}; +pub use self::parse::{parse, parse_borrowed, parse_owned}; /// Well-known formats, typically standards. pub mod well_known { diff --git a/vendor/time/src/format_description/modifier.rs b/vendor/time/src/format_description/modifier.rs index e01c18fda..cdac1ae97 100644 --- a/vendor/time/src/format_description/modifier.rs +++ b/vendor/time/src/format_description/modifier.rs @@ -1,5 +1,7 @@ //! Various modifiers for components. +use core::num::NonZeroU16; + // region: date modifiers /// Day of the month. #[non_exhaustive] @@ -234,6 +236,49 @@ pub enum Padding { None, } +/// Ignore some number of bytes. +/// +/// This has no effect when formatting. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ignore { + /// The number of bytes to ignore. + pub count: NonZeroU16, +} + +// Needed as `Default` is deliberately not implemented for `Ignore`. The number of bytes to ignore +// must be explicitly provided. +impl Ignore { + /// Create an instance of `Ignore` with the provided number of bytes to ignore. + pub const fn count(count: NonZeroU16) -> Self { + Self { count } + } +} + +/// The precision of a Unix timestamp. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnixTimestampPrecision { + /// Seconds since the Unix epoch. + Second, + /// Milliseconds since the Unix epoch. + Millisecond, + /// Microseconds since the Unix epoch. + Microsecond, + /// Nanoseconds since the Unix epoch. + Nanosecond, +} + +/// A Unix timestamp. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct UnixTimestamp { + /// The precision of the timestamp. + pub precision: UnixTimestampPrecision, + /// Whether the `+` sign must be present for a non-negative timestamp. + pub sign_is_mandatory: bool, +} + /// Generate the provided code if and only if `pub` is present. macro_rules! if_pub { (pub $(#[$attr:meta])*; $($x:tt)*) => { @@ -352,4 +397,13 @@ impl_const_default! { @pub OffsetSecond => Self { padding: Padding::Zero }; /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero). Padding => Self::Zero; + /// Creates a modifier that indicates the value represents the [number of seconds](Self::Second) + /// since the Unix epoch. + UnixTimestampPrecision => Self::Second; + /// Creates a modifier that indicates the value represents the [number of + /// seconds](UnixTimestampPrecision::Second) since the Unix epoch. The sign is not mandatory. + @pub UnixTimestamp => Self { + precision: UnixTimestampPrecision::Second, + sign_is_mandatory: false, + }; } diff --git a/vendor/time/src/format_description/parse/ast.rs b/vendor/time/src/format_description/parse/ast.rs index 6977cc9cd..f7b0ab78c 100644 --- a/vendor/time/src/format_description/parse/ast.rs +++ b/vendor/time/src/format_description/parse/ast.rs @@ -1,278 +1,381 @@ //! AST for parsing format descriptions. +use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; use core::iter; -use core::iter::Peekable; -use super::{lexer, Error, Location, Span}; +use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused}; /// One part of a complete format description. -#[allow(variant_size_differences)] pub(super) enum Item<'a> { /// A literal string, formatted and parsed as-is. - Literal { - /// The string itself. - value: &'a [u8], - /// Where the string originates from in the format string. - _span: Span, - }, + /// + /// This should never be present inside a nested format description. + Literal(Spanned<&'a [u8]>), /// A sequence of brackets. The first acts as the escape character. + /// + /// This should never be present if the lexer has `BACKSLASH_ESCAPE` set to `true`. EscapedBracket { /// The first bracket. - _first: Location, + _first: Unused<Location>, /// The second bracket. - _second: Location, + _second: Unused<Location>, }, /// Part of a type, along with its modifiers. Component { /// Where the opening bracket was in the format string. - _opening_bracket: Location, + _opening_bracket: Unused<Location>, /// Whitespace between the opening bracket and name. - _leading_whitespace: Option<Whitespace<'a>>, + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, /// The name of the component. - name: Name<'a>, + name: Spanned<&'a [u8]>, /// The modifiers for the component. - modifiers: Vec<Modifier<'a>>, + modifiers: Box<[Modifier<'a>]>, /// Whitespace between the modifiers and closing bracket. - _trailing_whitespace: Option<Whitespace<'a>>, + _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>, /// Where the closing bracket was in the format string. - _closing_bracket: Location, + _closing_bracket: Unused<Location>, + }, + /// An optional sequence of items. + Optional { + /// Where the opening bracket was in the format string. + opening_bracket: Location, + /// Whitespace between the opening bracket and "optional". + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// The "optional" keyword. + _optional_kw: Unused<Spanned<&'a [u8]>>, + /// Whitespace between the "optional" keyword and the opening bracket. + _whitespace: Unused<Spanned<&'a [u8]>>, + /// The items within the optional sequence. + nested_format_description: NestedFormatDescription<'a>, + /// Where the closing bracket was in the format string. + closing_bracket: Location, + }, + /// The first matching parse of a sequence of items. + First { + /// Where the opening bracket was in the format string. + opening_bracket: Location, + /// Whitespace between the opening bracket and "first". + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// The "first" keyword. + _first_kw: Unused<Spanned<&'a [u8]>>, + /// Whitespace between the "first" keyword and the opening bracket. + _whitespace: Unused<Spanned<&'a [u8]>>, + /// The sequences of items to try. + nested_format_descriptions: Box<[NestedFormatDescription<'a>]>, + /// Where the closing bracket was in the format string. + closing_bracket: Location, }, } -/// Whitespace within a component. -pub(super) struct Whitespace<'a> { - /// The whitespace itself. - pub(super) _value: &'a [u8], - /// Where the whitespace was in the format string. - pub(super) span: Span, -} - -/// The name of a component. -pub(super) struct Name<'a> { - /// The name itself. - pub(super) value: &'a [u8], - /// Where the name was in the format string. - pub(super) span: Span, +/// A format description that is nested within another format description. +pub(super) struct NestedFormatDescription<'a> { + /// Where the opening bracket was in the format string. + pub(super) _opening_bracket: Unused<Location>, + /// The items within the nested format description. + pub(super) items: Box<[Item<'a>]>, + /// Where the closing bracket was in the format string. + pub(super) _closing_bracket: Unused<Location>, + /// Whitespace between the closing bracket and the next item. + pub(super) _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>, } /// A modifier for a component. pub(super) struct Modifier<'a> { /// Whitespace preceding the modifier. - pub(super) _leading_whitespace: Whitespace<'a>, + pub(super) _leading_whitespace: Unused<Spanned<&'a [u8]>>, /// The key of the modifier. - pub(super) key: Key<'a>, + pub(super) key: Spanned<&'a [u8]>, /// Where the colon of the modifier was in the format string. - pub(super) _colon: Location, + pub(super) _colon: Unused<Location>, /// The value of the modifier. - pub(super) value: Value<'a>, -} - -/// The key of a modifier. -pub(super) struct Key<'a> { - /// The key itself. - pub(super) value: &'a [u8], - /// Where the key was in the format string. - pub(super) span: Span, + pub(super) value: Spanned<&'a [u8]>, } -/// The value of a modifier. -pub(super) struct Value<'a> { - /// The value itself. - pub(super) value: &'a [u8], - /// Where the value was in the format string. - pub(super) span: Span, +/// Parse the provided tokens into an AST. +pub(super) fn parse< + 'item: 'iter, + 'iter, + I: Iterator<Item = Result<lexer::Token<'item>, Error>>, + const VERSION: usize, +>( + tokens: &'iter mut lexer::Lexed<I>, +) -> impl Iterator<Item = Result<Item<'item>, Error>> + 'iter { + validate_version!(VERSION); + parse_inner::<_, false, VERSION>(tokens) } -/// Parse the provided tokens into an AST. -pub(super) fn parse<'a>( - tokens: impl Iterator<Item = lexer::Token<'a>>, -) -> impl Iterator<Item = Result<Item<'a>, Error>> { - let mut tokens = tokens.peekable(); +/// Parse the provided tokens into an AST. The const generic indicates whether the resulting +/// [`Item`] will be used directly or as part of a [`NestedFormatDescription`]. +fn parse_inner< + 'item, + I: Iterator<Item = Result<lexer::Token<'item>, Error>>, + const NESTED: bool, + const VERSION: usize, +>( + tokens: &mut lexer::Lexed<I>, +) -> impl Iterator<Item = Result<Item<'item>, Error>> + '_ { + validate_version!(VERSION); iter::from_fn(move || { - Some(match tokens.next()? { - lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }), + if NESTED && tokens.peek_closing_bracket().is_some() { + return None; + } + + let next = match tokens.next()? { + Ok(token) => token, + Err(err) => return Some(Err(err)), + }; + + Some(match next { + lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => { + bug!("literal should not be present in nested description") + } + lexer::Token::Literal(value) => Ok(Item::Literal(value)), lexer::Token::Bracket { kind: lexer::BracketKind::Opening, location, } => { - // escaped bracket - if let Some(&lexer::Token::Bracket { - kind: lexer::BracketKind::Opening, - location: second_location, - }) = tokens.peek() - { - tokens.next(); // consume - Ok(Item::EscapedBracket { - _first: location, - _second: second_location, - }) - } - // component - else { - parse_component(location, &mut tokens) + if version!(..=1) { + if let Some(second_location) = tokens.next_if_opening_bracket() { + Ok(Item::EscapedBracket { + _first: unused(location), + _second: unused(second_location), + }) + } else { + parse_component::<_, VERSION>(location, tokens) + } + } else { + parse_component::<_, VERSION>(location, tokens) } } lexer::Token::Bracket { kind: lexer::BracketKind::Closing, location: _, - } => unreachable!( - "internal error: closing bracket should have been consumed by `parse_component`", - ), + } if NESTED => { + bug!("closing bracket should be caught by the `if` statement") + } + lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location: _, + } => { + bug!("closing bracket should have been consumed by `parse_component`") + } lexer::Token::ComponentPart { - kind: _, - value: _, - span: _, - } => unreachable!( - "internal error: component part should have been consumed by `parse_component`", - ), + kind: _, // whitespace is significant in nested components + value, + } if NESTED => Ok(Item::Literal(value)), + lexer::Token::ComponentPart { kind: _, value: _ } => { + bug!("component part should have been consumed by `parse_component`") + } }) }) } /// Parse a component. This assumes that the opening bracket has already been consumed. -fn parse_component<'a>( +fn parse_component< + 'a, + I: Iterator<Item = Result<lexer::Token<'a>, Error>>, + const VERSION: usize, +>( opening_bracket: Location, - tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>, + tokens: &mut lexer::Lexed<I>, ) -> Result<Item<'a>, Error> { - let leading_whitespace = if let Some(&lexer::Token::ComponentPart { - kind: lexer::ComponentKind::Whitespace, - value, - span, - }) = tokens.peek() - { - tokens.next(); // consume - Some(Whitespace { - _value: value, - span, - }) - } else { - None - }; + validate_version!(VERSION); + let leading_whitespace = tokens.next_if_whitespace(); - let name = if let Some(&lexer::Token::ComponentPart { - kind: lexer::ComponentKind::NotWhitespace, - value, - span, - }) = tokens.peek() - { - tokens.next(); // consume - Name { value, span } - } else { - let span = leading_whitespace.map_or_else( - || Span { - start: opening_bracket, - end: opening_bracket, - }, - |whitespace| whitespace.span.shrink_to_end(), - ); + guard!(let Some(name) = tokens.next_if_not_whitespace() else { + let span = match leading_whitespace { + Some(Spanned { value: _, span }) => span, + None => opening_bracket.to(opening_bracket), + }; return Err(Error { - _inner: span.error("expected component name"), + _inner: unused(span.error("expected component name")), public: crate::error::InvalidFormatDescription::MissingComponentName { - index: span.start_byte(), + index: span.start.byte as _, }, }); - }; + }); + + if *name == b"optional" { + guard!(let Some(whitespace) = tokens.next_if_whitespace() else { + return Err(Error { + _inner: unused(name.span.error("expected whitespace after `optional`")), + public: crate::error::InvalidFormatDescription::Expected { + what: "whitespace after `optional`", + index: name.span.end.byte as _, + }, + }); + }); + + let nested = parse_nested::<_, VERSION>(whitespace.span.end, tokens)?; + + guard!(let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as _, + }, + }); + }); + + return Ok(Item::Optional { + opening_bracket, + _leading_whitespace: unused(leading_whitespace), + _optional_kw: unused(name), + _whitespace: unused(whitespace), + nested_format_description: nested, + closing_bracket, + }); + } + + if *name == b"first" { + guard!(let Some(whitespace) = tokens.next_if_whitespace() else { + return Err(Error { + _inner: unused(name.span.error("expected whitespace after `first`")), + public: crate::error::InvalidFormatDescription::Expected { + what: "whitespace after `first`", + index: name.span.end.byte as _, + }, + }); + }); + + let mut nested_format_descriptions = Vec::new(); + while let Ok(description) = parse_nested::<_, VERSION>(whitespace.span.end, tokens) { + nested_format_descriptions.push(description); + } + + guard!(let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as _, + }, + }); + }); + + return Ok(Item::First { + opening_bracket, + _leading_whitespace: unused(leading_whitespace), + _first_kw: unused(name), + _whitespace: unused(whitespace), + nested_format_descriptions: nested_format_descriptions.into_boxed_slice(), + closing_bracket, + }); + } let mut modifiers = Vec::new(); let trailing_whitespace = loop { - let whitespace = if let Some(&lexer::Token::ComponentPart { - kind: lexer::ComponentKind::Whitespace, - value, - span, - }) = tokens.peek() - { - tokens.next(); // consume - Whitespace { - _value: value, - span, - } - } else { - break None; - }; + guard!(let Some(whitespace) = tokens.next_if_whitespace() else { break None }); - if let Some(&lexer::Token::ComponentPart { - kind: lexer::ComponentKind::NotWhitespace, - value, - span, - }) = tokens.peek() - { - tokens.next(); // consume + // This is not necessary for proper parsing, but provides a much better error when a nested + // description is used where it's not allowed. + if let Some(location) = tokens.next_if_opening_bracket() { + return Err(Error { + _inner: unused( + location + .to(location) + .error("modifier must be of the form `key:value`"), + ), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from("["), + index: location.byte as _, + }, + }); + } - let colon_index = match value.iter().position(|&b| b == b':') { - Some(index) => index, - None => { - return Err(Error { - _inner: span.error("modifier must be of the form `key:value`"), - public: crate::error::InvalidFormatDescription::InvalidModifier { - value: String::from_utf8_lossy(value).into_owned(), - index: span.start_byte(), - }, - }); - } - }; - let key = &value[..colon_index]; - let value = &value[colon_index + 1..]; + guard!(let Some(Spanned { value, span }) = tokens.next_if_not_whitespace() else { + break Some(whitespace); + }); - if key.is_empty() { - return Err(Error { - _inner: span.shrink_to_start().error("expected modifier key"), - public: crate::error::InvalidFormatDescription::InvalidModifier { - value: String::new(), - index: span.start_byte(), - }, - }); - } - if value.is_empty() { - return Err(Error { - _inner: span.shrink_to_end().error("expected modifier value"), - public: crate::error::InvalidFormatDescription::InvalidModifier { - value: String::new(), - index: span.shrink_to_end().start_byte(), - }, - }); - } + guard!(let Some(colon_index) = value.iter().position(|&b| b == b':') else { + return Err(Error { + _inner: unused(span.error("modifier must be of the form `key:value`")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: span.start.byte as _, + }, + }); + }); + let key = &value[..colon_index]; + let value = &value[colon_index + 1..]; - modifiers.push(Modifier { - _leading_whitespace: whitespace, - key: Key { - value: key, - span: span.subspan(..colon_index), + if key.is_empty() { + return Err(Error { + _inner: unused(span.shrink_to_start().error("expected modifier key")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.start.byte as _, }, - _colon: span.start.offset(colon_index), - value: Value { - value, - span: span.subspan(colon_index + 1..), + }); + } + if value.is_empty() { + return Err(Error { + _inner: unused(span.shrink_to_end().error("expected modifier value")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.shrink_to_end().start.byte as _, }, }); - } else { - break Some(whitespace); } + + modifiers.push(Modifier { + _leading_whitespace: unused(whitespace), + key: key.spanned(span.shrink_to_before(colon_index as _)), + _colon: unused(span.start.offset(colon_index as _)), + value: value.spanned(span.shrink_to_after(colon_index as _)), + }); }; - let closing_bracket = if let Some(&lexer::Token::Bracket { - kind: lexer::BracketKind::Closing, - location, - }) = tokens.peek() - { - tokens.next(); // consume - location - } else { + guard!(let Some(closing_bracket) = tokens.next_if_closing_bracket() else { return Err(Error { - _inner: opening_bracket.error("unclosed bracket"), + _inner: unused(opening_bracket.error("unclosed bracket")), public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { - index: opening_bracket.byte, + index: opening_bracket.byte as _, }, }); - }; + }); Ok(Item::Component { - _opening_bracket: opening_bracket, - _leading_whitespace: leading_whitespace, + _opening_bracket: unused(opening_bracket), + _leading_whitespace: unused(leading_whitespace), name, - modifiers, - _trailing_whitespace: trailing_whitespace, - _closing_bracket: closing_bracket, + modifiers: modifiers.into_boxed_slice(), + _trailing_whitespace: unused(trailing_whitespace), + _closing_bracket: unused(closing_bracket), + }) +} + +/// Parse a nested format description. The location provided is the the most recent one consumed. +fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: usize>( + last_location: Location, + tokens: &mut lexer::Lexed<I>, +) -> Result<NestedFormatDescription<'a>, Error> { + validate_version!(VERSION); + guard!(let Some(opening_bracket) = tokens.next_if_opening_bracket() else { + return Err(Error { + _inner: unused(last_location.error("expected opening bracket")), + public: crate::error::InvalidFormatDescription::Expected { + what: "opening bracket", + index: last_location.byte as _, + }, + }); + }); + let items = parse_inner::<_, true, VERSION>(tokens).collect::<Result<_, _>>()?; + guard!(let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as _, + }, + }); + }); + let trailing_whitespace = tokens.next_if_whitespace(); + + Ok(NestedFormatDescription { + _opening_bracket: unused(opening_bracket), + items, + _closing_bracket: unused(closing_bracket), + _trailing_whitespace: unused(trailing_whitespace), }) } diff --git a/vendor/time/src/format_description/parse/format_item.rs b/vendor/time/src/format_description/parse/format_item.rs index 53146d522..c0e64d92a 100644 --- a/vendor/time/src/format_description/parse/format_item.rs +++ b/vendor/time/src/format_description/parse/format_item.rs @@ -1,8 +1,11 @@ //! Typed, validated representation of a parsed format description. +use alloc::boxed::Box; use alloc::string::String; +use core::num::NonZeroU16; +use core::str::{self, FromStr}; -use super::{ast, Error}; +use super::{ast, unused, Error, Span, Spanned}; /// Parse an AST iterator into a sequence of format items. pub(super) fn parse<'a>( @@ -12,12 +15,25 @@ pub(super) fn parse<'a>( } /// A description of how to format and parse one part of a type. -#[allow(variant_size_differences)] pub(super) enum Item<'a> { /// A literal string. Literal(&'a [u8]), /// Part of a type, along with its modifiers. Component(Component), + /// A sequence of optional items. + Optional { + /// The items themselves. + value: Box<[Self]>, + /// The span of the full sequence. + span: Span, + }, + /// The first matching parse of a sequence of format descriptions. + First { + /// The sequence of format descriptions. + value: Box<[Box<[Self]>]>, + /// The span of the full sequence. + span: Span, + }, } impl Item<'_> { @@ -32,20 +48,86 @@ impl Item<'_> { _trailing_whitespace: _, _closing_bracket: _, } => Item::Component(component_from_ast(&name, &modifiers)?), - ast::Item::Literal { value, _span: _ } => Item::Literal(value), + ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value), ast::Item::EscapedBracket { _first: _, _second: _, } => Item::Literal(b"["), + ast::Item::Optional { + opening_bracket, + _leading_whitespace: _, + _optional_kw: _, + _whitespace: _, + nested_format_description, + closing_bracket, + } => { + let items = nested_format_description + .items + .into_vec() + .into_iter() + .map(Item::from_ast) + .collect::<Result<_, _>>()?; + Item::Optional { + value: items, + span: opening_bracket.to(closing_bracket), + } + } + ast::Item::First { + opening_bracket, + _leading_whitespace: _, + _first_kw: _, + _whitespace: _, + nested_format_descriptions, + closing_bracket, + } => { + let items = nested_format_descriptions + .into_vec() + .into_iter() + .map(|nested_format_description| { + nested_format_description + .items + .into_vec() + .into_iter() + .map(Item::from_ast) + .collect() + }) + .collect::<Result<_, _>>()?; + Item::First { + value: items, + span: opening_bracket.to(closing_bracket), + } + } }) } } -impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> { - fn from(item: Item<'a>) -> Self { +impl<'a> TryFrom<Item<'a>> for crate::format_description::FormatItem<'a> { + type Error = Error; + + fn try_from(item: Item<'a>) -> Result<Self, Self::Error> { match item { - Item::Literal(literal) => Self::Literal(literal), - Item::Component(component) => Self::Component(component.into()), + Item::Literal(literal) => Ok(Self::Literal(literal)), + Item::Component(component) => Ok(Self::Component(component.into())), + Item::Optional { value: _, span } => Err(Error { + _inner: unused(span.error( + "optional items are not supported in runtime-parsed format descriptions", + )), + public: crate::error::InvalidFormatDescription::NotSupported { + what: "optional item", + context: "runtime-parsed format descriptions", + index: span.start.byte as _, + }, + }), + Item::First { value: _, span } => Err(Error { + _inner: unused(span.error( + "'first' items are not supported in runtime-parsed format descriptions", + )), + public: crate::error::InvalidFormatDescription::NotSupported { + what: "'first' item", + context: "runtime-parsed format descriptions", + index: span.start.byte as _, + }, + }), } } } @@ -55,17 +137,43 @@ impl From<Item<'_>> for crate::format_description::OwnedFormatItem { match item { Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), Item::Component(component) => Self::Component(component.into()), + Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())), + Item::First { value, span: _ } => { + Self::First(value.into_vec().into_iter().map(Into::into).collect()) + } + } + } +} + +impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem { + fn from(items: Box<[Item<'a>]>) -> Self { + let items = items.into_vec(); + if items.len() == 1 { + if let Ok([item]) = <[_; 1]>::try_from(items) { + item.into() + } else { + bug!("the length was just checked to be 1") + } + } else { + Self::Compound(items.into_iter().map(Self::from).collect()) } } } /// Declare the `Component` struct. macro_rules! component_definition { + (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; + (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; + (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; + (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; + ($vis:vis enum $name:ident { - $($variant:ident = $parse_variant:literal { - $($field:ident = $parse_field:literal: - Option<$field_type:ty> => $target_field:ident),* $(,)? - }),* $(,)? + $($variant:ident = $parse_variant:literal {$( + $(#[$required:tt])? + $field:ident = $parse_field:literal: + Option<$(#[$from_str:tt])? $field_type:ty> + => $target_field:ident + ),* $(,)?}),* $(,)? }) => { $vis enum $name { $($variant($variant),)* @@ -77,25 +185,48 @@ macro_rules! component_definition { $(impl $variant { /// Parse the component from the AST, given its modifiers. - fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> Result<Self, Error> { + fn with_modifiers( + modifiers: &[ast::Modifier<'_>], + _component_span: Span, + ) -> Result<Self, Error> + { let mut this = Self { $($field: None),* }; for modifier in modifiers { - $(if modifier.key.value.eq_ignore_ascii_case($parse_field) { - this.$field = <$field_type>::from_modifier_value(&modifier.value)?; + $(#[allow(clippy::string_lit_as_bytes)] + if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) { + this.$field = component_definition!(@if_from_str $($from_str)? + then { + parse_from_modifier_value::<$field_type>(&modifier.value)? + } else { + <$field_type>::from_modifier_value(&modifier.value)? + }); continue; })* return Err(Error { - _inner: modifier.key.span.error("invalid modifier key"), + _inner: unused(modifier.key.span.error("invalid modifier key")), public: crate::error::InvalidFormatDescription::InvalidModifier { - value: String::from_utf8_lossy(modifier.key.value).into_owned(), - index: modifier.key.span.start_byte(), + value: String::from_utf8_lossy(*modifier.key).into_owned(), + index: modifier.key.span.start.byte as _, } }); } + $(component_definition! { @if_required $($required)? then { + if this.$field.is_none() { + return Err(Error { + _inner: unused(_component_span.error("missing required modifier")), + public: + crate::error::InvalidFormatDescription::MissingRequiredModifier { + name: $parse_field, + index: _component_span.start.byte as _, + } + }); + } + }})* + Ok(this) } })* @@ -106,7 +237,16 @@ macro_rules! component_definition { $name::$variant($variant { $($field),* }) => { $crate::format_description::component::Component::$variant( $crate::format_description::modifier::$variant {$( - $target_field: $field.unwrap_or_default().into() + $target_field: component_definition! { @if_required $($required)? + then { + match $field { + Some(value) => value.into(), + None => bug!("required modifier was not set"), + } + } else { + $field.unwrap_or_default().into() + } + } ),*} ) } @@ -116,17 +256,18 @@ macro_rules! component_definition { /// Parse a component from the AST, given its name and modifiers. fn component_from_ast( - name: &ast::Name<'_>, + name: &Spanned<&[u8]>, modifiers: &[ast::Modifier<'_>], ) -> Result<Component, Error> { - $(if name.value.eq_ignore_ascii_case($parse_variant) { - return Ok(Component::$variant($variant::with_modifiers(&modifiers)?)); + $(#[allow(clippy::string_lit_as_bytes)] + if name.eq_ignore_ascii_case($parse_variant.as_bytes()) { + return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?,)); })* Err(Error { - _inner: name.span.error("invalid component"), + _inner: unused(name.span.error("invalid component")), public: crate::error::InvalidFormatDescription::InvalidComponentName { - name: String::from_utf8_lossy(name.value).into_owned(), - index: name.span.start_byte(), + name: String::from_utf8_lossy(name).into_owned(), + index: name.span.start.byte as _, }, }) } @@ -136,58 +277,66 @@ macro_rules! component_definition { // Keep in alphabetical order. component_definition! { pub(super) enum Component { - Day = b"day" { - padding = b"padding": Option<Padding> => padding, + Day = "day" { + padding = "padding": Option<Padding> => padding, + }, + Hour = "hour" { + padding = "padding": Option<Padding> => padding, + base = "repr": Option<HourBase> => is_12_hour_clock, }, - Hour = b"hour" { - padding = b"padding": Option<Padding> => padding, - base = b"repr": Option<HourBase> => is_12_hour_clock, + Ignore = "ignore" { + #[required] + count = "count": Option<#[from_str] NonZeroU16> => count, }, - Minute = b"minute" { - padding = b"padding": Option<Padding> => padding, + Minute = "minute" { + padding = "padding": Option<Padding> => padding, }, - Month = b"month" { - padding = b"padding": Option<Padding> => padding, - repr = b"repr": Option<MonthRepr> => repr, - case_sensitive = b"case_sensitive": Option<MonthCaseSensitive> => case_sensitive, + Month = "month" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<MonthRepr> => repr, + case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive, }, - OffsetHour = b"offset_hour" { - sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, - padding = b"padding": Option<Padding> => padding, + OffsetHour = "offset_hour" { + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, + padding = "padding": Option<Padding> => padding, }, - OffsetMinute = b"offset_minute" { - padding = b"padding": Option<Padding> => padding, + OffsetMinute = "offset_minute" { + padding = "padding": Option<Padding> => padding, }, - OffsetSecond = b"offset_second" { - padding = b"padding": Option<Padding> => padding, + OffsetSecond = "offset_second" { + padding = "padding": Option<Padding> => padding, }, - Ordinal = b"ordinal" { - padding = b"padding": Option<Padding> => padding, + Ordinal = "ordinal" { + padding = "padding": Option<Padding> => padding, }, - Period = b"period" { - case = b"case": Option<PeriodCase> => is_uppercase, - case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, + Period = "period" { + case = "case": Option<PeriodCase> => is_uppercase, + case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, }, - Second = b"second" { - padding = b"padding": Option<Padding> => padding, + Second = "second" { + padding = "padding": Option<Padding> => padding, }, - Subsecond = b"subsecond" { - digits = b"digits": Option<SubsecondDigits> => digits, + Subsecond = "subsecond" { + digits = "digits": Option<SubsecondDigits> => digits, }, - Weekday = b"weekday" { - repr = b"repr": Option<WeekdayRepr> => repr, - one_indexed = b"one_indexed": Option<WeekdayOneIndexed> => one_indexed, - case_sensitive = b"case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, + UnixTimestamp = "unix_timestamp" { + precision = "precision": Option<UnixTimestampPrecision> => precision, + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, }, - WeekNumber = b"week_number" { - padding = b"padding": Option<Padding> => padding, - repr = b"repr": Option<WeekNumberRepr> => repr, + Weekday = "weekday" { + repr = "repr": Option<WeekdayRepr> => repr, + one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed, + case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, }, - Year = b"year" { - padding = b"padding": Option<Padding> => padding, - repr = b"repr": Option<YearRepr> => repr, - base = b"base": Option<YearBase> => iso_week_based, - sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, + WeekNumber = "week_number" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<WeekNumberRepr> => repr, + }, + Year = "year" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<YearRepr> => repr, + base = "base": Option<YearBase> => iso_week_based, + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, }, } } @@ -212,19 +361,6 @@ macro_rules! target_value { }; } -// TODO use `#[derive(Default)]` on enums once MSRV is 1.62 (NET 2022-12-30) -/// Simulate `#[derive(Default)]` on enums. -macro_rules! derived_default_on_enum { - ($type:ty; $default:expr) => {}; - ($attr:meta $type:ty; $default:expr) => { - impl Default for $type { - fn default() -> Self { - $default - } - } - }; -} - /// Declare the various modifiers. /// /// For the general case, ordinary syntax can be used. Note that you _must_ declare a default @@ -251,25 +387,22 @@ macro_rules! modifier { ),* $(,)? } )+) => {$( + #[derive(Default)] enum $name { - $($variant),* + $($(#[$attr])? $variant),* } - $(derived_default_on_enum! { - $($attr)? $name; $name::$variant - })* - impl $name { /// Parse the modifier from its string representation. - fn from_modifier_value(value: &ast::Value<'_>) -> Result<Option<Self>, Error> { - $(if value.value.eq_ignore_ascii_case($parse_variant) { + fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> { + $(if value.eq_ignore_ascii_case($parse_variant) { return Ok(Some(Self::$variant)); })* Err(Error { - _inner: value.span.error("invalid modifier value"), + _inner: unused(value.span.error("invalid modifier value")), public: crate::error::InvalidFormatDescription::InvalidModifier { - value: String::from_utf8_lossy(value.value).into_owned(), - index: value.span.start_byte(), + value: String::from_utf8_lossy(value).into_owned(), + index: value.span.start.byte as _, }, }) } @@ -345,6 +478,14 @@ modifier! { OneOrMore = b"1+", } + enum UnixTimestampPrecision { + #[default] + Second = b"second", + Millisecond = b"millisecond", + Microsecond = b"microsecond", + Nanosecond = b"nanosecond", + } + enum WeekNumberRepr { #[default] Iso = b"iso", @@ -384,3 +525,18 @@ modifier! { LastTwo = b"last_two", } } + +/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8. +fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> { + str::from_utf8(value) + .ok() + .and_then(|val| val.parse::<T>().ok()) + .map(|val| Some(val)) + .ok_or_else(|| Error { + _inner: unused(value.span.error("invalid modifier value")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: value.span.start.byte as _, + }, + }) +} diff --git a/vendor/time/src/format_description/parse/lexer.rs b/vendor/time/src/format_description/parse/lexer.rs index e405ea8c8..1604fd497 100644 --- a/vendor/time/src/format_description/parse/lexer.rs +++ b/vendor/time/src/format_description/parse/lexer.rs @@ -2,17 +2,102 @@ use core::iter; -use super::{Location, Span}; +use super::{unused, Error, Location, Spanned, SpannedValue}; + +/// An iterator over the lexed tokens. +pub(super) struct Lexed<I: Iterator> { + /// The internal iterator. + iter: core::iter::Peekable<I>, +} + +impl<I: Iterator> Iterator for Lexed<I> { + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} + +impl<'iter, 'token: 'iter, I: Iterator<Item = Result<Token<'token>, Error>> + 'iter> Lexed<I> { + /// Peek at the next item in the iterator. + pub(super) fn peek(&mut self) -> Option<&I::Item> { + self.iter.peek() + } + + /// Consume the next token if it is whitespace. + pub(super) fn next_if_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> { + if let Some(&Ok(Token::ComponentPart { + kind: ComponentKind::Whitespace, + value, + })) = self.peek() + { + self.next(); // consume + Some(value) + } else { + None + } + } + + /// Consume the next token if it is a component item that is not whitespace. + pub(super) fn next_if_not_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> { + if let Some(&Ok(Token::ComponentPart { + kind: ComponentKind::NotWhitespace, + value, + })) = self.peek() + { + self.next(); // consume + Some(value) + } else { + None + } + } + + /// Consume the next token if it is an opening bracket. + pub(super) fn next_if_opening_bracket(&mut self) -> Option<Location> { + if let Some(&Ok(Token::Bracket { + kind: BracketKind::Opening, + location, + })) = self.peek() + { + self.next(); // consume + Some(location) + } else { + None + } + } + + /// Peek at the next token if it is a closing bracket. + pub(super) fn peek_closing_bracket(&'iter mut self) -> Option<&'iter Location> { + if let Some(Ok(Token::Bracket { + kind: BracketKind::Closing, + location, + })) = self.peek() + { + Some(location) + } else { + None + } + } + + /// Consume the next token if it is a closing bracket. + pub(super) fn next_if_closing_bracket(&mut self) -> Option<Location> { + if let Some(&Ok(Token::Bracket { + kind: BracketKind::Closing, + location, + })) = self.peek() + { + self.next(); // consume + Some(location) + } else { + None + } + } +} /// A token emitted by the lexer. There is no semantic meaning at this stage. pub(super) enum Token<'a> { /// A literal string, formatted and parsed as-is. - Literal { - /// The string itself. - value: &'a [u8], - /// Where the string was in the format string. - span: Span, - }, + Literal(Spanned<&'a [u8]>), /// An opening or closing bracket. May or may not be the start or end of a component. Bracket { /// Whether the bracket is opening or closing. @@ -25,9 +110,7 @@ pub(super) enum Token<'a> { /// Whether the part is whitespace or not. kind: ComponentKind, /// The part itself. - value: &'a [u8], - /// Where the part was in the format string. - span: Span, + value: Spanned<&'a [u8]>, }, } @@ -48,48 +131,89 @@ pub(super) enum ComponentKind { } /// Attach [`Location`] information to each byte in the iterator. -fn attach_location(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = (u8, Location)> { - let mut line = 1; - let mut column = 1; +fn attach_location<'item>( + iter: impl Iterator<Item = &'item u8>, +) -> impl Iterator<Item = (&'item u8, Location)> { let mut byte_pos = 0; iter.map(move |byte| { - let location = Location { - line, - column, - byte: byte_pos, - }; - column += 1; + let location = Location { byte: byte_pos }; byte_pos += 1; - - if byte == b'\n' { - line += 1; - column = 1; - } - (byte, location) }) } /// Parse the string into a series of [`Token`]s. -pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { +/// +/// `VERSION` controls the version of the format description that is being parsed. Currently, this +/// must be 1 or 2. +/// +/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`. +/// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may +/// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All +/// other characters result in a lex error. +pub(super) fn lex<const VERSION: usize>( + mut input: &[u8], +) -> Lexed<impl Iterator<Item = Result<Token<'_>, Error>>> { + validate_version!(VERSION); + let mut depth: u8 = 0; - let mut iter = attach_location(input.iter().copied()).peekable(); + let mut iter = attach_location(input.iter()).peekable(); let mut second_bracket_location = None; - iter::from_fn(move || { - // There is a flag set to emit the second half of an escaped bracket pair. - if let Some(location) = second_bracket_location.take() { - return Some(Token::Bracket { - kind: BracketKind::Opening, - location, - }); + let iter = iter::from_fn(move || { + // The flag is only set when version is zero. + if version!(..=1) { + // There is a flag set to emit the second half of an escaped bracket pair. + if let Some(location) = second_bracket_location.take() { + return Some(Ok(Token::Bracket { + kind: BracketKind::Opening, + location, + })); + } } - Some(match iter.next()? { - (b'[', location) => { - if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') { - // escaped bracket + Some(Ok(match iter.next()? { + // possible escape sequence + (b'\\', backslash_loc) if version!(2..) => { + match iter.next() { + Some((b'\\' | b'[' | b']', char_loc)) => { + // The escaped character is emitted as-is. + let char = &input[1..2]; + input = &input[2..]; + if depth == 0 { + Token::Literal(char.spanned(backslash_loc.to(char_loc))) + } else { + Token::ComponentPart { + kind: ComponentKind::NotWhitespace, + value: char.spanned(backslash_loc.to(char_loc)), + } + } + } + Some((_, loc)) => { + return Some(Err(Error { + _inner: unused(loc.error("invalid escape sequence")), + public: crate::error::InvalidFormatDescription::Expected { + what: "valid escape sequence", + index: loc.byte as _, + }, + })); + } + None => { + return Some(Err(Error { + _inner: unused(backslash_loc.error("unexpected end of input")), + public: crate::error::InvalidFormatDescription::Expected { + what: "valid escape sequence", + index: backslash_loc.byte as _, + }, + })); + } + } + } + // potentially escaped opening bracket + (b'[', location) if version!(..=1) => { + if let Some((_, second_location)) = iter.next_if(|&(&byte, _)| byte == b'[') { + // Escaped bracket. Store the location of the second so we can emit it later. second_bracket_location = Some(second_location); input = &input[2..]; } else { @@ -103,10 +227,21 @@ pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { location, } } + // opening bracket + (b'[', location) => { + depth += 1; + input = &input[1..]; + + Token::Bracket { + kind: BracketKind::Opening, + location, + } + } // closing bracket (b']', location) if depth > 0 => { depth -= 1; input = &input[1..]; + Token::Bracket { kind: BracketKind::Closing, location, @@ -117,17 +252,17 @@ pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { let mut bytes = 1; let mut end_location = start_location; - while let Some((_, location)) = iter.next_if(|&(byte, _)| byte != b'[') { + while let Some((_, location)) = + iter.next_if(|&(&byte, _)| !((version!(2..) && byte == b'\\') || byte == b'[')) + { end_location = location; bytes += 1; } let value = &input[..bytes]; input = &input[bytes..]; - Token::Literal { - value, - span: Span::start_end(start_location, end_location), - } + + Token::Literal(value.spanned(start_location.to(end_location))) } // component part (byte, start_location) => { @@ -136,7 +271,8 @@ pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { let is_whitespace = byte.is_ascii_whitespace(); while let Some((_, location)) = iter.next_if(|&(byte, _)| { - byte != b'[' && byte != b']' && is_whitespace == byte.is_ascii_whitespace() + !matches!(byte, b'\\' | b'[' | b']') + && is_whitespace == byte.is_ascii_whitespace() }) { end_location = location; bytes += 1; @@ -144,16 +280,20 @@ pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { let value = &input[..bytes]; input = &input[bytes..]; + Token::ComponentPart { kind: if is_whitespace { ComponentKind::Whitespace } else { ComponentKind::NotWhitespace }, - value, - span: Span::start_end(start_location, end_location), + value: value.spanned(start_location.to(end_location)), } } - }) - }) + })) + }); + + Lexed { + iter: iter.peekable(), + } } diff --git a/vendor/time/src/format_description/parse/mod.rs b/vendor/time/src/format_description/parse/mod.rs index c73a67449..2ab58f172 100644 --- a/vendor/time/src/format_description/parse/mod.rs +++ b/vendor/time/src/format_description/parse/mod.rs @@ -1,70 +1,110 @@ //! Parser for format descriptions. +use alloc::boxed::Box; use alloc::vec::Vec; -use core::ops::{RangeFrom, RangeTo}; + +/// A helper macro to make version restrictions simpler to read and write. +macro_rules! version { + ($range:expr) => { + $range.contains(&VERSION) + }; +} + +/// A helper macro to statically validate the version (when used as a const parameter). +macro_rules! validate_version { + ($version:ident) => { + #[allow(clippy::let_unit_value)] + let _ = $crate::format_description::parse::Version::<$version>::IS_VALID; + }; +} mod ast; mod format_item; mod lexer; +/// A struct that is used to ensure that the version is valid. +struct Version<const N: usize>; +impl<const N: usize> Version<N> { + /// A constant that panics if the version is not valid. This results in a post-monomorphization + /// error. + const IS_VALID: () = assert!(N >= 1 && N <= 2); +} + /// Parse a sequence of items from the format description. /// /// The syntax for the format description can be found in [the /// book](https://time-rs.github.io/book/api/format-description.html). +/// +/// This function exists for backward compatibility reasons. It is equivalent to calling +/// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of +/// `parse_borrowed`. pub fn parse( s: &str, ) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription> { - let lexed = lexer::lex(s.as_bytes()); - let ast = ast::parse(lexed); + parse_borrowed::<1>(s) +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format +/// description is provided as the const parameter. **It is recommended to use version 2.** +pub fn parse_borrowed<const VERSION: usize>( + s: &str, +) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription> +{ + validate_version!(VERSION); + let mut lexed = lexer::lex::<VERSION>(s.as_bytes()); + let ast = ast::parse::<_, VERSION>(&mut lexed); let format_items = format_item::parse(ast); Ok(format_items - .map(|res| res.map(Into::into)) - .collect::<Result<Vec<_>, _>>()?) + .map(|res| res.and_then(TryInto::try_into)) + .collect::<Result<_, _>>()?) } /// Parse a sequence of items from the format description. /// /// The syntax for the format description can be found in [the -/// book](https://time-rs.github.io/book/api/format-description.html). +/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format +/// description is provided as the const parameter. /// /// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means -/// that there is no lifetime that needs to be handled. +/// that there is no lifetime that needs to be handled. **It is recommended to use version 2.** /// /// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem -pub fn parse_owned( +pub fn parse_owned<const VERSION: usize>( s: &str, ) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> { - let lexed = lexer::lex(s.as_bytes()); - let ast = ast::parse(lexed); + validate_version!(VERSION); + let mut lexed = lexer::lex::<VERSION>(s.as_bytes()); + let ast = ast::parse::<_, VERSION>(&mut lexed); let format_items = format_item::parse(ast); let items = format_items .map(|res| res.map(Into::into)) - .collect::<Result<Vec<_>, _>>()? - .into_boxed_slice(); - Ok(crate::format_description::OwnedFormatItem::Compound(items)) + .collect::<Result<Box<_>, _>>()?; + Ok(items.into()) } /// A location within a string. #[derive(Clone, Copy)] struct Location { - /// The one-indexed line of the string. - line: usize, - /// The one-indexed column of the string. - column: usize, /// The zero-indexed byte of the string. - byte: usize, + byte: u32, } impl Location { + /// Create a new [`Span`] from `self` to `other`. + const fn to(self, end: Self) -> Span { + Span { start: self, end } + } + /// Offset the location by the provided amount. /// /// Note that this assumes the resulting location is on the same line as the original location. #[must_use = "this does not modify the original value"] - const fn offset(&self, offset: usize) -> Self { + const fn offset(&self, offset: u32) -> Self { Self { - line: self.line, - column: self.column + offset, byte: self.byte + offset, } } @@ -91,17 +131,6 @@ struct Span { } impl Span { - /// Create a new `Span` from the provided start and end locations. - const fn start_end(start: Location, end: Location) -> Self { - Self { start, end } - } - - /// Reduce this span to the provided range. - #[must_use = "this does not modify the original value"] - fn subspan(&self, range: impl Subspan) -> Self { - range.subspan(self) - } - /// Obtain a `Span` pointing at the start of the pre-existing span. #[must_use = "this does not modify the original value"] const fn shrink_to_start(&self) -> Self { @@ -120,6 +149,28 @@ impl Span { } } + /// Obtain a `Span` that ends before the provided position of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_before(&self, pos: u32) -> Self { + Self { + start: self.start, + end: Location { + byte: self.start.byte + pos - 1, + }, + } + } + + /// Obtain a `Span` that starts after provided position to the end of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_after(&self, pos: u32) -> Self { + Self { + start: Location { + byte: self.start.byte + pos + 1, + }, + end: self.end, + } + } + /// Create an error with the provided message at this span. const fn error(self, message: &'static str) -> ErrorInner { ErrorInner { @@ -127,46 +178,34 @@ impl Span { _span: self, } } - - /// Get the byte index that the span starts at. - const fn start_byte(&self) -> usize { - self.start.byte - } } -/// A trait for types that can be used to reduce a `Span`. -trait Subspan { - /// Reduce the provided `Span` to a new `Span`. - fn subspan(self, span: &Span) -> Span; +/// A value with an associated [`Span`]. +#[derive(Clone, Copy)] +struct Spanned<T> { + /// The value. + value: T, + /// Where the value was in the format string. + span: Span, } -impl Subspan for RangeFrom<usize> { - fn subspan(self, span: &Span) -> Span { - assert_eq!(span.start.line, span.end.line); +impl<T> core::ops::Deref for Spanned<T> { + type Target = T; - Span { - start: Location { - line: span.start.line, - column: span.start.column + self.start, - byte: span.start.byte + self.start, - }, - end: span.end, - } + fn deref(&self) -> &Self::Target { + &self.value } } -impl Subspan for RangeTo<usize> { - fn subspan(self, span: &Span) -> Span { - assert_eq!(span.start.line, span.end.line); +/// Helper trait to attach a [`Span`] to a value. +trait SpannedValue: Sized { + /// Attach a [`Span`] to a value. + fn spanned(self, span: Span) -> Spanned<Self>; +} - Span { - start: span.start, - end: Location { - line: span.start.line, - column: span.start.column + self.end - 1, - byte: span.start.byte + self.end - 1, - }, - } +impl<T> SpannedValue for T { + fn spanned(self, span: Span) -> Spanned<Self> { + Spanned { value: self, span } } } @@ -181,7 +220,7 @@ struct ErrorInner { /// A complete error description. struct Error { /// The internal error. - _inner: ErrorInner, + _inner: Unused<ErrorInner>, /// The error needed for interoperability with the rest of `time`. public: crate::error::InvalidFormatDescription, } @@ -191,3 +230,16 @@ impl From<Error> for crate::error::InvalidFormatDescription { error.public } } + +/// A value that may be used in the future, but currently is not. +/// +/// This struct exists so that data can semantically be passed around without _actually_ passing it +/// around. This way the data still exists if it is needed in the future. +// `PhantomData` is not used directly because we don't want to introduce any trait implementations. +struct Unused<T>(core::marker::PhantomData<T>); + +/// Indicate that a value is currently unused. +#[allow(clippy::missing_const_for_fn)] // false positive +fn unused<T>(_: T) -> Unused<T> { + Unused(core::marker::PhantomData) +} diff --git a/vendor/time/src/formatting/formattable.rs b/vendor/time/src/formatting/formattable.rs index 7fee2fbea..2c0d7badf 100644 --- a/vendor/time/src/formatting/formattable.rs +++ b/vendor/time/src/formatting/formattable.rs @@ -173,22 +173,21 @@ impl sealed::Sealed for Rfc2822 { &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3], )?; bytes += write(output, b", ")?; - bytes += format_number_pad_zero::<2, _, _>(output, day)?; + bytes += format_number_pad_zero::<2>(output, day)?; bytes += write(output, b" ")?; bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?; bytes += write(output, b" ")?; - bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += format_number_pad_zero::<4>(output, year as u32)?; bytes += write(output, b" ")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?; + bytes += format_number_pad_zero::<2>(output, time.hour())?; bytes += write(output, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?; + bytes += format_number_pad_zero::<2>(output, time.minute())?; bytes += write(output, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.second())?; + bytes += format_number_pad_zero::<2>(output, time.second())?; bytes += write(output, b" ")?; bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; - bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?; - bytes += - format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?; Ok(bytes) } @@ -217,40 +216,40 @@ impl sealed::Sealed for Rfc3339 { return Err(error::Format::InvalidComponent("offset_second")); } - bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += format_number_pad_zero::<4>(output, year as u32)?; bytes += write(output, b"-")?; - bytes += format_number_pad_zero::<2, _, _>(output, date.month() as u8)?; + bytes += format_number_pad_zero::<2>(output, date.month() as u8)?; bytes += write(output, b"-")?; - bytes += format_number_pad_zero::<2, _, _>(output, date.day())?; + bytes += format_number_pad_zero::<2>(output, date.day())?; bytes += write(output, b"T")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?; + bytes += format_number_pad_zero::<2>(output, time.hour())?; bytes += write(output, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?; + bytes += format_number_pad_zero::<2>(output, time.minute())?; bytes += write(output, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, time.second())?; + bytes += format_number_pad_zero::<2>(output, time.second())?; #[allow(clippy::if_not_else)] if time.nanosecond() != 0 { let nanos = time.nanosecond(); bytes += write(output, b".")?; bytes += if nanos % 10 != 0 { - format_number_pad_zero::<9, _, _>(output, nanos) + format_number_pad_zero::<9>(output, nanos) } else if (nanos / 10) % 10 != 0 { - format_number_pad_zero::<8, _, _>(output, nanos / 10) + format_number_pad_zero::<8>(output, nanos / 10) } else if (nanos / 100) % 10 != 0 { - format_number_pad_zero::<7, _, _>(output, nanos / 100) + format_number_pad_zero::<7>(output, nanos / 100) } else if (nanos / 1_000) % 10 != 0 { - format_number_pad_zero::<6, _, _>(output, nanos / 1_000) + format_number_pad_zero::<6>(output, nanos / 1_000) } else if (nanos / 10_000) % 10 != 0 { - format_number_pad_zero::<5, _, _>(output, nanos / 10_000) + format_number_pad_zero::<5>(output, nanos / 10_000) } else if (nanos / 100_000) % 10 != 0 { - format_number_pad_zero::<4, _, _>(output, nanos / 100_000) + format_number_pad_zero::<4>(output, nanos / 100_000) } else if (nanos / 1_000_000) % 10 != 0 { - format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000) + format_number_pad_zero::<3>(output, nanos / 1_000_000) } else if (nanos / 10_000_000) % 10 != 0 { - format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000) + format_number_pad_zero::<2>(output, nanos / 10_000_000) } else { - format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000) + format_number_pad_zero::<1>(output, nanos / 100_000_000) }?; } @@ -260,10 +259,9 @@ impl sealed::Sealed for Rfc3339 { } bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; - bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?; bytes += write(output, b":")?; - bytes += - format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?; Ok(bytes) } @@ -281,15 +279,15 @@ impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { if Self::FORMAT_DATE { let date = date.ok_or(error::Format::InsufficientTypeInformation)?; - bytes += iso8601::format_date::<_, CONFIG>(output, date)?; + bytes += iso8601::format_date::<CONFIG>(output, date)?; } if Self::FORMAT_TIME { let time = time.ok_or(error::Format::InsufficientTypeInformation)?; - bytes += iso8601::format_time::<_, CONFIG>(output, time)?; + bytes += iso8601::format_time::<CONFIG>(output, time)?; } if Self::FORMAT_OFFSET { let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; - bytes += iso8601::format_offset::<_, CONFIG>(output, offset)?; + bytes += iso8601::format_offset::<CONFIG>(output, offset)?; } if bytes == 0 { diff --git a/vendor/time/src/formatting/iso8601.rs b/vendor/time/src/formatting/iso8601.rs index 1724f96f5..229a07ebc 100644 --- a/vendor/time/src/formatting/iso8601.rs +++ b/vendor/time/src/formatting/iso8601.rs @@ -10,8 +10,8 @@ use crate::formatting::{format_float, format_number_pad_zero, write, write_if, w use crate::{error, Date, Time, UtcOffset}; /// Format the date portion of ISO 8601. -pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>( - output: &mut W, +pub(super) fn format_date<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, date: Date, ) -> Result<usize, error::Format> { let mut bytes = 0; @@ -21,44 +21,44 @@ pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>( let (year, month, day) = date.to_calendar_date(); if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { bytes += write_if_else(output, year < 0, b"-", b"+")?; - bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; } else if !(0..=9999).contains(&year) { return Err(error::Format::InvalidComponent("year")); } else { - bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += format_number_pad_zero::<4>(output, year as u32)?; } bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; - bytes += format_number_pad_zero::<2, _, _>(output, month as u8)?; + bytes += format_number_pad_zero::<2>(output, month as u8)?; bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; - bytes += format_number_pad_zero::<2, _, _>(output, day)?; + bytes += format_number_pad_zero::<2>(output, day)?; } DateKind::Week => { let (year, week, day) = date.to_iso_week_date(); if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { bytes += write_if_else(output, year < 0, b"-", b"+")?; - bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; } else if !(0..=9999).contains(&year) { return Err(error::Format::InvalidComponent("year")); } else { - bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += format_number_pad_zero::<4>(output, year as u32)?; } bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?; - bytes += format_number_pad_zero::<2, _, _>(output, week)?; + bytes += format_number_pad_zero::<2>(output, week)?; bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; - bytes += format_number_pad_zero::<1, _, _>(output, day.number_from_monday())?; + bytes += format_number_pad_zero::<1>(output, day.number_from_monday())?; } DateKind::Ordinal => { let (year, day) = date.to_ordinal_date(); if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { bytes += write_if_else(output, year < 0, b"-", b"+")?; - bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?; + bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; } else if !(0..=9999).contains(&year) { return Err(error::Format::InvalidComponent("year")); } else { - bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?; + bytes += format_number_pad_zero::<4>(output, year as u32)?; } bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?; - bytes += format_number_pad_zero::<3, _, _>(output, day)?; + bytes += format_number_pad_zero::<3>(output, day)?; } } @@ -66,8 +66,8 @@ pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>( } /// Format the time portion of ISO 8601. -pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>( - output: &mut W, +pub(super) fn format_time<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, time: Time, ) -> Result<usize, error::Format> { let mut bytes = 0; @@ -90,7 +90,7 @@ pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>( format_float(output, hours, 2, decimal_digits)?; } TimePrecision::Minute { decimal_digits } => { - bytes += format_number_pad_zero::<2, _, _>(output, hours)?; + bytes += format_number_pad_zero::<2>(output, hours)?; bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; let minutes = (minutes as f64) + (seconds as f64) / 60. @@ -98,9 +98,9 @@ pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>( bytes += format_float(output, minutes, 2, decimal_digits)?; } TimePrecision::Second { decimal_digits } => { - bytes += format_number_pad_zero::<2, _, _>(output, hours)?; + bytes += format_number_pad_zero::<2>(output, hours)?; bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, minutes)?; + bytes += format_number_pad_zero::<2>(output, minutes)?; bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; let seconds = (seconds as f64) + (nanoseconds as f64) / 1_000_000_000.; bytes += format_float(output, seconds, 2, decimal_digits)?; @@ -111,8 +111,8 @@ pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>( } /// Format the UTC offset portion of ISO 8601. -pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>( - output: &mut W, +pub(super) fn format_offset<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, offset: UtcOffset, ) -> Result<usize, error::Format> { if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() { @@ -126,13 +126,13 @@ pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>( return Err(error::Format::InvalidComponent("offset_second")); } bytes += write_if_else(output, offset.is_negative(), b"-", b"+")?; - bytes += format_number_pad_zero::<2, _, _>(output, hours.unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, hours.unsigned_abs())?; if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 { return Err(error::Format::InvalidComponent("offset_minute")); } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute { bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?; - bytes += format_number_pad_zero::<2, _, _>(output, minutes.unsigned_abs())?; + bytes += format_number_pad_zero::<2>(output, minutes.unsigned_abs())?; } Ok(bytes) diff --git a/vendor/time/src/formatting/mod.rs b/vendor/time/src/formatting/mod.rs index e5409cc58..1df4478a8 100644 --- a/vendor/time/src/formatting/mod.rs +++ b/vendor/time/src/formatting/mod.rs @@ -8,7 +8,7 @@ use std::io; pub use self::formattable::Formattable; use crate::format_description::{modifier, Component}; -use crate::{error, Date, Time, UtcOffset}; +use crate::{error, Date, OffsetDateTime, Time, UtcOffset}; #[allow(clippy::missing_docs_in_private_items)] const MONTH_NAMES: [&[u8]; 12] = [ @@ -152,19 +152,13 @@ pub(crate) fn format_float( Some(digits_after_decimal) => { let digits_after_decimal = digits_after_decimal.get() as usize; let width = digits_before_decimal as usize + 1 + digits_after_decimal; - write!( - output, - "{value:0>width$.digits_after_decimal$}", - value = value, - width = width, - digits_after_decimal = digits_after_decimal, - )?; + write!(output, "{value:0>width$.digits_after_decimal$}")?; Ok(width) } None => { let value = value.trunc() as u64; let width = digits_before_decimal as usize; - write!(output, "{value:0>width$?}", value = value, width = width)?; + write!(output, "{value:0>width$}")?; Ok(width) } } @@ -173,28 +167,24 @@ pub(crate) fn format_float( /// Format a number with the provided padding and width. /// /// The sign must be written by the caller. -pub(crate) fn format_number<const WIDTH: u8, W: io::Write, V: itoa::Integer + DigitCount + Copy>( - output: &mut W, - value: V, +pub(crate) fn format_number<const WIDTH: u8>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, padding: modifier::Padding, ) -> Result<usize, io::Error> { match padding { - modifier::Padding::Space => format_number_pad_space::<WIDTH, _, _>(output, value), - modifier::Padding::Zero => format_number_pad_zero::<WIDTH, _, _>(output, value), - modifier::Padding::None => write(output, itoa::Buffer::new().format(value).as_bytes()), + modifier::Padding::Space => format_number_pad_space::<WIDTH>(output, value), + modifier::Padding::Zero => format_number_pad_zero::<WIDTH>(output, value), + modifier::Padding::None => format_number_pad_none(output, value), } } /// Format a number with the provided width and spaces as padding. /// /// The sign must be written by the caller. -pub(crate) fn format_number_pad_space< - const WIDTH: u8, - W: io::Write, - V: itoa::Integer + DigitCount + Copy, ->( - output: &mut W, - value: V, +pub(crate) fn format_number_pad_space<const WIDTH: u8>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, ) -> Result<usize, io::Error> { let mut bytes = 0; for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { @@ -207,13 +197,9 @@ pub(crate) fn format_number_pad_space< /// Format a number with the provided width and zeros as padding. /// /// The sign must be written by the caller. -pub(crate) fn format_number_pad_zero< - const WIDTH: u8, - W: io::Write, - V: itoa::Integer + DigitCount + Copy, ->( - output: &mut W, - value: V, +pub(crate) fn format_number_pad_zero<const WIDTH: u8>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, ) -> Result<usize, io::Error> { let mut bytes = 0; for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { @@ -223,6 +209,16 @@ pub(crate) fn format_number_pad_zero< Ok(bytes) } +/// Format a number with no padding. +/// +/// If the sign is mandatory, the sign must be written by the caller. +pub(crate) fn format_number_pad_none( + output: &mut impl io::Write, + value: impl itoa::Integer + Copy, +) -> Result<usize, io::Error> { + write(output, itoa::Buffer::new().format(value).as_bytes()) +} + /// Format the provided component into the designated output. An `Err` will be returned if the /// component requires information that it does not provide or if the value cannot be output to the /// stream. @@ -249,6 +245,10 @@ pub(crate) fn format_component( (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?, (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?, (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?, + (Ignore(_), ..) => 0, + (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => { + fmt_unix_timestamp(output, date, time, offset, modifier)? + } _ => return Err(error::Format::InsufficientTypeInformation), }) } @@ -260,7 +260,7 @@ fn fmt_day( date: Date, modifier::Day { padding }: modifier::Day, ) -> Result<usize, io::Error> { - format_number::<2, _, _>(output, date.day(), padding) + format_number::<2>(output, date.day(), padding) } /// Format the month into the designated output. @@ -274,9 +274,7 @@ fn fmt_month( }: modifier::Month, ) -> Result<usize, io::Error> { match repr { - modifier::MonthRepr::Numerical => { - format_number::<2, _, _>(output, date.month() as u8, padding) - } + modifier::MonthRepr::Numerical => format_number::<2>(output, date.month() as u8, padding), modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]), modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]), } @@ -288,7 +286,7 @@ fn fmt_ordinal( date: Date, modifier::Ordinal { padding }: modifier::Ordinal, ) -> Result<usize, io::Error> { - format_number::<3, _, _>(output, date.ordinal(), padding) + format_number::<3>(output, date.ordinal(), padding) } /// Format the weekday into the designated output. @@ -310,12 +308,12 @@ fn fmt_weekday( output, WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize], ), - modifier::WeekdayRepr::Sunday => format_number::<1, _, _>( + modifier::WeekdayRepr::Sunday => format_number::<1>( output, date.weekday().number_days_from_sunday() + one_indexed as u8, modifier::Padding::None, ), - modifier::WeekdayRepr::Monday => format_number::<1, _, _>( + modifier::WeekdayRepr::Monday => format_number::<1>( output, date.weekday().number_days_from_monday() + one_indexed as u8, modifier::Padding::None, @@ -329,7 +327,7 @@ fn fmt_week_number( date: Date, modifier::WeekNumber { padding, repr }: modifier::WeekNumber, ) -> Result<usize, io::Error> { - format_number::<2, _, _>( + format_number::<2>( output, match repr { modifier::WeekNumberRepr::Iso => date.iso_week(), @@ -362,11 +360,11 @@ fn fmt_year( }; let format_number = match repr { #[cfg(feature = "large-dates")] - modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6, _, _>, + modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6>, #[cfg(feature = "large-dates")] - modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5, _, _>, - modifier::YearRepr::Full => format_number::<4, _, _>, - modifier::YearRepr::LastTwo => format_number::<2, _, _>, + modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5>, + modifier::YearRepr::Full => format_number::<4>, + modifier::YearRepr::LastTwo => format_number::<2>, }; let mut bytes = 0; if repr != modifier::YearRepr::LastTwo { @@ -397,7 +395,7 @@ fn fmt_hour( (hour, true) if hour < 12 => hour, (hour, true) => hour - 12, }; - format_number::<2, _, _>(output, value, padding) + format_number::<2>(output, value, padding) } /// Format the minute into the designated output. @@ -406,7 +404,7 @@ fn fmt_minute( time: Time, modifier::Minute { padding }: modifier::Minute, ) -> Result<usize, io::Error> { - format_number::<2, _, _>(output, time.minute(), padding) + format_number::<2>(output, time.minute(), padding) } /// Format the period into the designated output. @@ -432,7 +430,7 @@ fn fmt_second( time: Time, modifier::Second { padding }: modifier::Second, ) -> Result<usize, io::Error> { - format_number::<2, _, _>(output, time.second(), padding) + format_number::<2>(output, time.second(), padding) } /// Format the subsecond into the designated output. @@ -445,23 +443,23 @@ fn fmt_subsecond<W: io::Write>( let nanos = time.nanosecond(); if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) { - format_number_pad_zero::<9, _, _>(output, nanos) + format_number_pad_zero::<9>(output, nanos) } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) { - format_number_pad_zero::<8, _, _>(output, nanos / 10) + format_number_pad_zero::<8>(output, nanos / 10) } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) { - format_number_pad_zero::<7, _, _>(output, nanos / 100) + format_number_pad_zero::<7>(output, nanos / 100) } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) { - format_number_pad_zero::<6, _, _>(output, nanos / 1_000) + format_number_pad_zero::<6>(output, nanos / 1_000) } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) { - format_number_pad_zero::<5, _, _>(output, nanos / 10_000) + format_number_pad_zero::<5>(output, nanos / 10_000) } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) { - format_number_pad_zero::<4, _, _>(output, nanos / 100_000) + format_number_pad_zero::<4>(output, nanos / 100_000) } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) { - format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000) + format_number_pad_zero::<3>(output, nanos / 1_000_000) } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) { - format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000) + format_number_pad_zero::<2>(output, nanos / 10_000_000) } else { - format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000) + format_number_pad_zero::<1>(output, nanos / 100_000_000) } } // endregion time formatters @@ -482,7 +480,7 @@ fn fmt_offset_hour( } else if sign_is_mandatory { bytes += write(output, b"+")?; } - bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?; + bytes += format_number::<2>(output, offset.whole_hours().unsigned_abs(), padding)?; Ok(bytes) } @@ -492,7 +490,7 @@ fn fmt_offset_minute( offset: UtcOffset, modifier::OffsetMinute { padding }: modifier::OffsetMinute, ) -> Result<usize, io::Error> { - format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding) + format_number::<2>(output, offset.minutes_past_hour().unsigned_abs(), padding) } /// Format the offset second into the designated output. @@ -501,6 +499,46 @@ fn fmt_offset_second( offset: UtcOffset, modifier::OffsetSecond { padding }: modifier::OffsetSecond, ) -> Result<usize, io::Error> { - format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding) + format_number::<2>(output, offset.seconds_past_minute().unsigned_abs(), padding) } // endregion offset formatters + +/// Format the Unix timestamp into the designated output. +fn fmt_unix_timestamp( + output: &mut impl io::Write, + date: Date, + time: Time, + offset: UtcOffset, + modifier::UnixTimestamp { + precision, + sign_is_mandatory, + }: modifier::UnixTimestamp, +) -> Result<usize, io::Error> { + let date_time = date + .with_time(time) + .assume_offset(offset) + .to_offset(UtcOffset::UTC); + + if date_time < OffsetDateTime::UNIX_EPOCH { + write(output, b"-")?; + } else if sign_is_mandatory { + write(output, b"+")?; + } + + match precision { + modifier::UnixTimestampPrecision::Second => { + format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs()) + } + modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none( + output, + (date_time.unix_timestamp_nanos() / 1_000_000).unsigned_abs(), + ), + modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none( + output, + (date_time.unix_timestamp_nanos() / 1_000).unsigned_abs(), + ), + modifier::UnixTimestampPrecision::Nanosecond => { + format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs()) + } + } +} diff --git a/vendor/time/src/instant.rs b/vendor/time/src/instant.rs index 2d2f65eef..58579b101 100644 --- a/vendor/time/src/instant.rs +++ b/vendor/time/src/instant.rs @@ -159,10 +159,15 @@ impl Sub<Instant> for StdInstant { impl Add<Duration> for Instant { type Output = Self; + /// # Panics + /// + /// This function may panic if the resulting point in time cannot be represented by the + /// underlying data structure. fn add(self, duration: Duration) -> Self::Output { if duration.is_positive() { Self(self.0 + duration.unsigned_abs()) } else if duration.is_negative() { + #[allow(clippy::unchecked_duration_subtraction)] Self(self.0 - duration.unsigned_abs()) } else { debug_assert!(duration.is_zero()); @@ -193,8 +198,13 @@ impl_add_assign!(StdInstant: Duration); impl Sub<Duration> for Instant { type Output = Self; + /// # Panics + /// + /// This function may panic if the resulting point in time cannot be represented by the + /// underlying data structure. fn sub(self, duration: Duration) -> Self::Output { if duration.is_positive() { + #[allow(clippy::unchecked_duration_subtraction)] Self(self.0 - duration.unsigned_abs()) } else if duration.is_negative() { Self(self.0 + duration.unsigned_abs()) @@ -216,7 +226,12 @@ impl Sub<Duration> for StdInstant { impl Sub<StdDuration> for Instant { type Output = Self; + /// # Panics + /// + /// This function may panic if the resulting point in time cannot be represented by the + /// underlying data structure. fn sub(self, duration: StdDuration) -> Self::Output { + #[allow(clippy::unchecked_duration_subtraction)] Self(self.0 - duration) } } diff --git a/vendor/time/src/lib.rs b/vendor/time/src/lib.rs index b9868c178..ed4042e03 100644 --- a/vendor/time/src/lib.rs +++ b/vendor/time/src/lib.rs @@ -73,16 +73,10 @@ //! Enables [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) support for converting //! [JavaScript dates](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Date.html), as //! well as obtaining the UTC offset from JavaScript. -//! -//! <small> -//! One feature only available to end users is the <code>unsound_local_offset</code> cfg. This -//! enables obtaining the system's UTC offset even when it is unsound. To enable this, use the -//! <code>RUSTFLAGS</code> environment variable. This is untested and officially unsupported. Do not -//! use this unless you understand the risk. -//! </small> #![doc(html_playground_url = "https://play.rust-lang.org")] #![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))] +#![cfg_attr(coverage_nightly, feature(no_coverage))] #![cfg_attr(not(feature = "std"), no_std)] #![deny( anonymous_parameters, @@ -92,7 +86,6 @@ clippy::obfuscated_if_else, clippy::std_instead_of_core, clippy::undocumented_unsafe_blocks, - const_err, illegal_floating_point_literal_pattern, late_bound_lifetime_arguments, path_statements, @@ -115,6 +108,7 @@ clippy::print_stdout, clippy::todo, clippy::unimplemented, + clippy::uninlined_format_args, clippy::unnested_or_patterns, clippy::unwrap_in_result, clippy::unwrap_used, @@ -139,6 +133,13 @@ #[cfg(feature = "alloc")] extern crate alloc; +// TODO(jhpratt) remove this after a while +#[cfg(unsound_local_offset)] +compile_error!( + "The `unsound_local_offset` flag was removed in time 0.3.18. If you need this functionality, \ + see the `time::util::local_offset::set_soundness` function." +); + // region: macros /// Helper macro for easily implementing `OpAssign`. macro_rules! __impl_assign { @@ -299,9 +300,21 @@ macro_rules! expect_opt { } }; } + +/// `unreachable!()`, but better. +macro_rules! bug { + () => { compile_error!("provide an error message to help fix a possible bug") }; + ($descr:literal $($rest:tt)?) => { + panic!(concat!("internal error: ", $descr) $($rest)?) + } +} // endregion macros +#[macro_use] +mod shim; + mod date; +mod date_time; mod duration; pub mod error; pub mod ext; @@ -334,6 +347,7 @@ pub mod util; mod weekday; 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/vendor/time/src/offset_date_time.rs b/vendor/time/src/offset_date_time.rs index c39a2b1d0..1e342ca8e 100644 --- a/vendor/time/src/offset_date_time.rs +++ b/vendor/time/src/offset_date_time.rs @@ -1,39 +1,33 @@ //! The [`OffsetDateTime`] struct and its associated `impl`s. +#[cfg(feature = "std")] use core::cmp::Ordering; #[cfg(feature = "std")] use core::convert::From; use core::fmt; -use core::hash::{Hash, Hasher}; -use core::ops::{Add, Sub}; +use core::hash::Hash; +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 crate::date::{MAX_YEAR, MIN_YEAR}; +use crate::date_time::offset_kind; #[cfg(feature = "formatting")] use crate::formatting::Formattable; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -#[cfg(feature = "parsing")] -use crate::util; -use crate::{error, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{error, Date, DateTime, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; -/// The Julian day of the Unix epoch. -const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day(); +/// The actual type doing all the work. +type Inner = DateTime<offset_kind::Fixed>; /// A [`PrimitiveDateTime`] with a [`UtcOffset`]. /// /// All comparisons are performed using the UTC time. -#[derive(Clone, Copy, Eq)] -pub struct OffsetDateTime { - /// The [`PrimitiveDateTime`], which is _always_ in the stored offset. - pub(crate) local_datetime: PrimitiveDateTime, - /// The [`UtcOffset`], which will be added to the [`PrimitiveDateTime`] as necessary. - pub(crate) offset: UtcOffset, -} +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OffsetDateTime(pub(crate) Inner); impl OffsetDateTime { /// Midnight, 1 January, 1970 (UTC). @@ -43,9 +37,7 @@ impl OffsetDateTime { /// # use time_macros::datetime; /// assert_eq!(OffsetDateTime::UNIX_EPOCH, datetime!(1970-01-01 0:00 UTC),); /// ``` - pub const UNIX_EPOCH: Self = Date::__from_ordinal_date_unchecked(1970, 1) - .midnight() - .assume_utc(); + pub const UNIX_EPOCH: Self = Self(Inner::UNIX_EPOCH); // region: now /// Create a new `OffsetDateTime` with the current date and time in UTC. @@ -58,21 +50,7 @@ impl OffsetDateTime { /// ``` #[cfg(feature = "std")] pub fn now_utc() -> Self { - #[cfg(all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" - ))] - { - js_sys::Date::new_0().into() - } - - #[cfg(not(all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")), - feature = "wasm-bindgen" - )))] - SystemTime::now().into() + Self(Inner::now_utc()) } /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset. @@ -86,8 +64,7 @@ impl OffsetDateTime { /// ``` #[cfg(feature = "local-offset")] pub fn now_local() -> Result<Self, error::IndeterminateOffset> { - let t = Self::now_utc(); - Ok(t.to_offset(UtcOffset::local_offset_at(t)?)) + Inner::now_local().map(Self) } // endregion now @@ -117,68 +94,7 @@ 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 { - 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 self; - } - - let (year, ordinal, time) = self.to_offset_raw(offset); - - if year > MAX_YEAR || year < MIN_YEAR { - panic!("local datetime out of valid range"); - } - - Date::__from_ordinal_date_unchecked(year, ordinal) - .with_time(time) - .assume_offset(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. - 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..60 => minute); - cascade!(second in 0..60 => minute); - cascade!(minute in 0..60 => hour); - cascade!(minute in 0..60 => hour); - cascade!(hour in 0..24 => ordinal); - cascade!(hour in 0..24 => ordinal); - cascade!(ordinal => year); - - debug_assert!(ordinal > 0); - debug_assert!(ordinal <= crate::util::days_in_year(year) as i16); - - ( - year, - ordinal as _, - Time::__from_hms_nanos_unchecked( - hour as _, - minute as _, - second as _, - self.nanosecond(), - ), - ) + Self(self.0.to_offset(offset)) } // region: constructors @@ -211,30 +127,7 @@ impl OffsetDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange> { - #[allow(clippy::missing_docs_in_private_items)] - const MIN_TIMESTAMP: i64 = Date::MIN.midnight().assume_utc().unix_timestamp(); - #[allow(clippy::missing_docs_in_private_items)] - const MAX_TIMESTAMP: i64 = Date::MAX - .with_time(Time::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999)) - .assume_utc() - .unix_timestamp(); - - ensure_value_in_range!(timestamp in MIN_TIMESTAMP => MAX_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, 86_400) as i32, - ); - - let seconds_within_day = timestamp.rem_euclid(86_400); - let time = Time::__from_hms_nanos_unchecked( - (seconds_within_day / 3_600) as _, - ((seconds_within_day % 3_600) / 60) as _, - (seconds_within_day % 60) as _, - 0, - ); - - Ok(PrimitiveDateTime::new(date, time).assume_utc()) + Ok(Self(const_try!(Inner::from_unix_timestamp(timestamp)))) } /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling @@ -253,19 +146,9 @@ impl OffsetDateTime { /// ); /// ``` pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange> { - let datetime = const_try!(Self::from_unix_timestamp( - div_floor!(timestamp, 1_000_000_000) as i64 - )); - - Ok(datetime - .local_datetime - .replace_time(Time::__from_hms_nanos_unchecked( - datetime.local_datetime.hour(), - datetime.local_datetime.minute(), - datetime.local_datetime.second(), - timestamp.rem_euclid(1_000_000_000) as u32, - )) - .assume_utc()) + Ok(Self(const_try!(Inner::from_unix_timestamp_nanos( + timestamp + )))) } // endregion constructors @@ -278,7 +161,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1)); /// ``` pub const fn offset(self) -> UtcOffset { - self.offset + self.0.offset() } /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time). @@ -289,14 +172,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(1970-01-01 0:00 -1).unix_timestamp(), 3_600); /// ``` pub const fn unix_timestamp(self) -> i64 { - let offset = self.offset.whole_seconds() as i64; - - let days = - (self.local_datetime.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * 86_400; - let hours = self.local_datetime.hour() as i64 * 3_600; - let minutes = self.local_datetime.minute() as i64 * 60; - let seconds = self.local_datetime.second() as i64; - days + hours + minutes + seconds - offset + self.0.unix_timestamp() } /// Get the Unix timestamp in nanoseconds. @@ -310,7 +186,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn unix_timestamp_nanos(self) -> i128 { - self.unix_timestamp() as i128 * 1_000_000_000 + self.nanosecond() as i128 + self.0.unix_timestamp_nanos() } /// Get the [`Date`] in the stored offset. @@ -326,7 +202,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn date(self) -> Date { - self.local_datetime.date() + self.0.date() } /// Get the [`Time`] in the stored offset. @@ -342,7 +218,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn time(self) -> Time { - self.local_datetime.time() + self.0.time() } // region: date getters @@ -360,7 +236,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020); /// ``` pub const fn year(self) -> i32 { - self.date().year() + self.0.year() } /// Get the month of the date in the stored offset. @@ -377,7 +253,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn month(self) -> Month { - self.date().month() + self.0.month() } /// Get the day of the date in the stored offset. @@ -395,7 +271,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn day(self) -> u8 { - self.date().day() + self.0.day() } /// Get the day of the year of the date in the stored offset. @@ -413,7 +289,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn ordinal(self) -> u16 { - self.date().ordinal() + self.0.ordinal() } /// Get the ISO week number of the date in the stored offset. @@ -428,7 +304,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53); /// ``` pub const fn iso_week(self) -> u8 { - self.date().iso_week() + self.0.iso_week() } /// Get the week number where week 1 begins on the first Sunday. @@ -443,7 +319,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.date().sunday_based_week() + self.0.sunday_based_week() } /// Get the week number where week 1 begins on the first Monday. @@ -458,7 +334,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.date().monday_based_week() + self.0.monday_based_week() } /// Get the year, month, and day. @@ -472,7 +348,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_calendar_date(self) -> (i32, Month, u8) { - self.date().to_calendar_date() + self.0.to_calendar_date() } /// Get the year and ordinal day number. @@ -485,7 +361,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_ordinal_date(self) -> (i32, u16) { - self.date().to_ordinal_date() + self.0.to_ordinal_date() } /// Get the ISO 8601 year, week number, and weekday. @@ -515,7 +391,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { - self.date().to_iso_week_date() + self.0.to_iso_week_date() } /// Get the weekday of the date in the stored offset. @@ -528,7 +404,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday); /// ``` pub const fn weekday(self) -> Weekday { - self.date().weekday() + self.0.weekday() } /// Get the Julian day for the date. The time is not taken into account for this calculation. @@ -544,7 +420,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.date().to_julian_day() + self.0.to_julian_day() } // endregion date getters @@ -557,7 +433,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.time().as_hms() + self.0.as_hms() } /// Get the clock hour, minute, second, and millisecond. @@ -574,7 +450,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) { - self.time().as_hms_milli() + self.0.as_hms_milli() } /// Get the clock hour, minute, second, and microsecond. @@ -591,7 +467,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) { - self.time().as_hms_micro() + self.0.as_hms_micro() } /// Get the clock hour, minute, second, and nanosecond. @@ -608,7 +484,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) { - self.time().as_hms_nano() + self.0.as_hms_nano() } /// Get the clock hour in the stored offset. @@ -626,7 +502,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn hour(self) -> u8 { - self.time().hour() + self.0.hour() } /// Get the minute within the hour in the stored offset. @@ -644,7 +520,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn minute(self) -> u8 { - self.time().minute() + self.0.minute() } /// Get the second within the minute in the stored offset. @@ -662,7 +538,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn second(self) -> u8 { - self.time().second() + self.0.second() } // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not @@ -678,7 +554,7 @@ impl OffsetDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999); /// ``` pub const fn millisecond(self) -> u16 { - self.time().millisecond() + self.0.millisecond() } /// Get the microseconds within the second in the stored offset. @@ -694,7 +570,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn microsecond(self) -> u32 { - self.time().microsecond() + self.0.microsecond() } /// Get the nanoseconds within the second in the stored offset. @@ -710,7 +586,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn nanosecond(self) -> u32 { - self.time().nanosecond() + self.0.nanosecond() } // endregion time getters // endregion getters @@ -733,7 +609,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option<Self> { - Some(const_try_opt!(self.local_datetime.checked_add(duration)).assume_offset(self.offset)) + Some(Self(const_try_opt!(self.0.checked_add(duration)))) } /// Computes `self - duration`, returning `None` if an overflow occurred. @@ -753,7 +629,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn checked_sub(self, duration: Duration) -> Option<Self> { - Some(const_try_opt!(self.local_datetime.checked_sub(duration)).assume_offset(self.offset)) + Some(Self(const_try_opt!(self.0.checked_sub(duration)))) } // endregion: checked arithmetic @@ -804,14 +680,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn saturating_add(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_add(duration) { - datetime - } else if duration.is_negative() { - PrimitiveDateTime::MIN.assume_offset(self.offset) - } else { - debug_assert!(duration.is_positive()); - PrimitiveDateTime::MAX.assume_offset(self.offset) - } + Self(self.0.saturating_add(duration)) } /// Computes `self - duration`, saturating value on overflow. @@ -860,14 +729,7 @@ impl OffsetDateTime { /// ); /// ``` pub const fn saturating_sub(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_sub(duration) { - datetime - } else if duration.is_negative() { - PrimitiveDateTime::MAX.assume_offset(self.offset) - } else { - debug_assert!(duration.is_positive()); - PrimitiveDateTime::MIN.assume_offset(self.offset) - } + Self(self.0.saturating_sub(duration)) } // endregion: saturating arithmetic } @@ -895,9 +757,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_time(self, time: Time) -> Self { - self.local_datetime - .replace_time(time) - .assume_offset(self.offset) + Self(self.0.replace_time(time)) } /// Replace the date, which is assumed to be in the stored offset. The time and offset @@ -916,9 +776,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_date(self, date: Date) -> Self { - self.local_datetime - .replace_date(date) - .assume_offset(self.offset) + Self(self.0.replace_date(date)) } /// Replace the date and time, which are assumed to be in the stored offset. The offset @@ -937,7 +795,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_date_time(self, date_time: PrimitiveDateTime) -> Self { - date_time.assume_offset(self.offset) + Self(self.0.replace_date_time(date_time.0)) } /// Replace the offset. The date and time components remain unchanged. @@ -951,7 +809,7 @@ impl OffsetDateTime { /// ``` #[must_use = "This method does not mutate the original `OffsetDateTime`."] pub const fn replace_offset(self, offset: UtcOffset) -> Self { - self.local_datetime.assume_offset(offset) + Self(self.0.replace_offset(offset)) } /// Replace the year. The month and day will be unchanged. @@ -966,7 +824,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_year(year)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_year(year)))) } /// Replace the month of the year. @@ -981,7 +839,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_month(month)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_month(month)))) } /// Replace the day of the month. @@ -996,7 +854,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_day(day)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_day(day)))) } /// Replace the clock hour. @@ -1010,7 +868,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_hour(hour)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_hour(hour)))) } /// Replace the minutes within the hour. @@ -1024,7 +882,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_minute(minute)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_minute(minute)))) } /// Replace the seconds within the minute. @@ -1038,7 +896,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<Self, error::ComponentRange> { - Ok(const_try!(self.local_datetime.replace_second(second)).assume_offset(self.offset)) + Ok(Self(const_try!(self.0.replace_second(second)))) } /// Replace the milliseconds within the second. @@ -1055,10 +913,7 @@ impl OffsetDateTime { self, millisecond: u16, ) -> Result<Self, error::ComponentRange> { - Ok( - const_try!(self.local_datetime.replace_millisecond(millisecond)) - .assume_offset(self.offset), - ) + Ok(Self(const_try!(self.0.replace_millisecond(millisecond)))) } /// Replace the microseconds within the second. @@ -1075,10 +930,7 @@ impl OffsetDateTime { self, microsecond: u32, ) -> Result<Self, error::ComponentRange> { - Ok( - const_try!(self.local_datetime.replace_microsecond(microsecond)) - .assume_offset(self.offset), - ) + Ok(Self(const_try!(self.0.replace_microsecond(microsecond)))) } /// Replace the nanoseconds within the second. @@ -1092,10 +944,7 @@ 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<Self, error::ComponentRange> { - Ok( - const_try!(self.local_datetime.replace_nanosecond(nanosecond)) - .assume_offset(self.offset), - ) + Ok(Self(const_try!(self.0.replace_nanosecond(nanosecond)))) } } // endregion replacement @@ -1110,12 +959,7 @@ impl OffsetDateTime { output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result<usize, error::Format> { - format.format_into( - output, - Some(self.date()), - Some(self.time()), - Some(self.offset), - ) + self.0.format_into(output, format) } /// Format the `OffsetDateTime` using the provided [format @@ -1135,7 +979,7 @@ impl OffsetDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { - format.format(Some(self.date()), Some(self.time()), Some(self.offset)) + self.0.format(format) } } @@ -1161,36 +1005,13 @@ impl OffsetDateTime { input: &str, description: &(impl Parsable + ?Sized), ) -> Result<Self, error::Parse> { - 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. - 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 date = match Date::from_ordinal_date(year, ordinal) { - Ok(date) => date, - Err(_) => return false, - }; - - time.hour() == 23 - && time.minute() == 59 - && time.second() == 59 - && date.day() == util::days_in_year_month(year, date.month()) + Inner::parse(input, description).map(Self) } } impl fmt::Display for OffsetDateTime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} {} {}", self.date(), self.time(), self.offset) + self.0.fmt(f) } } @@ -1202,104 +1023,76 @@ impl fmt::Debug for OffsetDateTime { // endregion formatting & parsing // region: trait impls -impl PartialEq for OffsetDateTime { - fn eq(&self, rhs: &Self) -> bool { - self.to_offset_raw(UtcOffset::UTC) == rhs.to_offset_raw(UtcOffset::UTC) +impl Add<Duration> for OffsetDateTime { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0.add(rhs)) } } -impl PartialOrd for OffsetDateTime { - fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { - Some(self.cmp(rhs)) +impl Add<StdDuration> for OffsetDateTime { + type Output = Self; + + fn add(self, rhs: StdDuration) -> Self::Output { + Self(self.0.add(rhs)) } } -impl Ord for OffsetDateTime { - fn cmp(&self, rhs: &Self) -> Ordering { - self.to_offset_raw(UtcOffset::UTC) - .cmp(&rhs.to_offset_raw(UtcOffset::UTC)) +impl AddAssign<Duration> for OffsetDateTime { + fn add_assign(&mut self, rhs: Duration) { + self.0.add_assign(rhs); } } -impl Hash for OffsetDateTime { - fn hash<H: Hasher>(&self, hasher: &mut H) { - // We need to distinguish this from a `PrimitiveDateTime`, which would otherwise conflict. - hasher.write(b"OffsetDateTime"); - self.to_offset_raw(UtcOffset::UTC).hash(hasher); +impl AddAssign<StdDuration> for OffsetDateTime { + fn add_assign(&mut self, rhs: StdDuration) { + self.0.add_assign(rhs); } } -impl<T> Add<T> for OffsetDateTime -where - PrimitiveDateTime: Add<T, Output = PrimitiveDateTime>, -{ +impl Sub<Duration> for OffsetDateTime { type Output = Self; - fn add(self, rhs: T) -> Self::Output { - (self.local_datetime + rhs).assume_offset(self.offset) + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0.sub(rhs)) } } -impl_add_assign!(OffsetDateTime: Duration, StdDuration); - -impl<T> Sub<T> for OffsetDateTime -where - PrimitiveDateTime: Sub<T, Output = PrimitiveDateTime>, -{ +impl Sub<StdDuration> for OffsetDateTime { type Output = Self; - fn sub(self, rhs: T) -> Self::Output { - (self.local_datetime - rhs).assume_offset(self.offset) + fn sub(self, rhs: StdDuration) -> Self::Output { + Self(self.0.sub(rhs)) } } -impl_sub_assign!(OffsetDateTime: Duration, StdDuration); - -impl Sub for OffsetDateTime { - type Output = Duration; - - fn sub(self, rhs: Self) -> Self::Output { - let adjustment = - Duration::seconds((self.offset.whole_seconds() - rhs.offset.whole_seconds()) as i64); - self.local_datetime - rhs.local_datetime - adjustment +impl SubAssign<Duration> for OffsetDateTime { + fn sub_assign(&mut self, rhs: Duration) { + self.0.sub_assign(rhs); } } -#[cfg(feature = "std")] -impl Add<Duration> 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 SubAssign<StdDuration> for OffsetDateTime { + fn sub_assign(&mut self, rhs: StdDuration) { + self.0.sub_assign(rhs); } } -impl_add_assign!(SystemTime: #[cfg(feature = "std")] Duration); - -#[cfg(feature = "std")] -impl Sub<Duration> for SystemTime { - type Output = Self; +impl Sub for OffsetDateTime { + type Output = Duration; - fn sub(self, duration: Duration) -> Self::Output { - (OffsetDateTime::from(self) - duration).into() + fn sub(self, rhs: Self) -> Self::Output { + self.0.sub(rhs.0) } } -impl_sub_assign!(SystemTime: #[cfg(feature = "std")] Duration); - #[cfg(feature = "std")] impl Sub<SystemTime> for OffsetDateTime { type Output = Duration; fn sub(self, rhs: SystemTime) -> Self::Output { - self - Self::from(rhs) + self.0.sub(rhs) } } @@ -1308,90 +1101,71 @@ impl Sub<OffsetDateTime> for SystemTime { type Output = Duration; fn sub(self, rhs: OffsetDateTime) -> Self::Output { - OffsetDateTime::from(self) - rhs + self.sub(rhs.0) } } #[cfg(feature = "std")] impl PartialEq<SystemTime> for OffsetDateTime { fn eq(&self, rhs: &SystemTime) -> bool { - self == &Self::from(*rhs) + self.0.eq(rhs) } } #[cfg(feature = "std")] impl PartialEq<OffsetDateTime> for SystemTime { fn eq(&self, rhs: &OffsetDateTime) -> bool { - &OffsetDateTime::from(*self) == rhs + self.eq(&rhs.0) } } #[cfg(feature = "std")] impl PartialOrd<SystemTime> for OffsetDateTime { fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> { - self.partial_cmp(&Self::from(*other)) + self.0.partial_cmp(other) } } #[cfg(feature = "std")] impl PartialOrd<OffsetDateTime> for SystemTime { fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> { - OffsetDateTime::from(*self).partial_cmp(other) + self.partial_cmp(&other.0) } } #[cfg(feature = "std")] impl From<SystemTime> for OffsetDateTime { 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(), - } + Self(Inner::from(system_time)) } } -#[allow(clippy::fallible_impl_from)] // caused by `debug_assert!` #[cfg(feature = "std")] impl From<OffsetDateTime> for SystemTime { fn from(datetime: OffsetDateTime) -> Self { - 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() - } + datetime.0.into() } } -#[allow(clippy::fallible_impl_from)] #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi")), feature = "wasm-bindgen" ))] impl From<js_sys::Date> for OffsetDateTime { fn from(js_date: js_sys::Date) -> Self { - // get_time() returns milliseconds - let timestamp_nanos = (js_date.get_time() * 1_000_000.0) as i128; - Self::from_unix_timestamp_nanos(timestamp_nanos) - .expect("invalid timestamp: Timestamp cannot fit in range") + Self(Inner::from(js_date)) } } #[cfg(all( - target_arch = "wasm32", + target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi")), feature = "wasm-bindgen" ))] impl From<OffsetDateTime> for js_sys::Date { fn from(datetime: OffsetDateTime) -> Self { - // new Date() takes milliseconds - let timestamp = (datetime.unix_timestamp_nanos() / 1_000_000) as f64; - js_sys::Date::new(×tamp.into()) + datetime.0.into() } } diff --git a/vendor/time/src/parsing/component.rs b/vendor/time/src/parsing/component.rs index 38d632a0d..e7b852706 100644 --- a/vendor/time/src/parsing/component.rs +++ b/vendor/time/src/parsing/component.rs @@ -6,7 +6,7 @@ use crate::format_description::modifier; #[cfg(feature = "large-dates")] use crate::parsing::combinator::n_to_m_digits_padded; use crate::parsing::combinator::{ - any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, opt, sign, + any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, n_to_m_digits, opt, sign, }; use crate::parsing::ParsedItem; use crate::{Month, Weekday}; @@ -259,16 +259,18 @@ pub(crate) fn parse_subsecond( // region: offset components /// Parse the "hour" component of a `UtcOffset`. +/// +/// Returns the value and whether the value is negative. This is used for when "-0" is parsed. pub(crate) fn parse_offset_hour( input: &[u8], modifiers: modifier::OffsetHour, -) -> Option<ParsedItem<'_, i8>> { +) -> Option<ParsedItem<'_, (i8, bool)>> { let ParsedItem(input, sign) = opt(sign)(input); let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?; match sign { - Some(b'-') => Some(ParsedItem(input, -(hour as i8))), + Some(b'-') => Some(ParsedItem(input, (-(hour as i8), true))), None if modifiers.sign_is_mandatory => None, - _ => Some(ParsedItem(input, hour as i8)), + _ => Some(ParsedItem(input, (hour as i8, false))), } } @@ -294,3 +296,39 @@ pub(crate) fn parse_offset_second( ) } // endregion offset components + +/// Ignore the given number of bytes. +pub(crate) fn parse_ignore( + input: &[u8], + modifiers: modifier::Ignore, +) -> Option<ParsedItem<'_, ()>> { + let modifier::Ignore { count } = modifiers; + let input = input.get((count.get() as usize)..)?; + Some(ParsedItem(input, ())) +} + +/// Parse the Unix timestamp component. +pub(crate) fn parse_unix_timestamp( + input: &[u8], + modifiers: modifier::UnixTimestamp, +) -> Option<ParsedItem<'_, i128>> { + let ParsedItem(input, sign) = opt(sign)(input); + let ParsedItem(input, nano_timestamp) = match modifiers.precision { + modifier::UnixTimestampPrecision::Second => { + n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * 1_000_000_000) + } + modifier::UnixTimestampPrecision::Millisecond => { + n_to_m_digits::<1, 17, u128>(input)?.map(|val| val * 1_000_000) + } + modifier::UnixTimestampPrecision::Microsecond => { + n_to_m_digits::<1, 20, u128>(input)?.map(|val| val * 1_000) + } + modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?, + }; + + match sign { + Some(b'-') => Some(ParsedItem(input, -(nano_timestamp as i128))), + None if modifiers.sign_is_mandatory => None, + _ => Some(ParsedItem(input, nano_timestamp as _)), + } +} diff --git a/vendor/time/src/parsing/parsable.rs b/vendor/time/src/parsing/parsable.rs index badb63808..78bbe64f0 100644 --- a/vendor/time/src/parsing/parsable.rs +++ b/vendor/time/src/parsing/parsable.rs @@ -2,6 +2,7 @@ use core::ops::Deref; +use crate::date_time::{maybe_offset_from_offset, MaybeOffset}; use crate::error::TryFromParsed; use crate::format_description::well_known::iso8601::EncodedConfig; use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; @@ -9,7 +10,7 @@ use crate::format_description::FormatItem; #[cfg(feature = "alloc")] use crate::format_description::OwnedFormatItem; use crate::parsing::{Parsed, ParsedItem}; -use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; +use crate::{error, Date, DateTime, Month, Time, UtcOffset, Weekday}; /// A type that can be parsed. #[cfg_attr(__time_03_docs, doc(notable_trait))] @@ -28,7 +29,6 @@ impl<T: Deref> Parsable for T where T::Target: Parsable {} /// Seal the trait to prevent downstream users from implementing it, while still allowing it to /// exist in generic bounds. mod sealed { - #[allow(clippy::wildcard_imports)] use super::*; @@ -71,13 +71,11 @@ mod sealed { Ok(self.parse(input)?.try_into()?) } - /// Parse a [`PrimitiveDateTime`] from the format description. - fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> { - Ok(self.parse(input)?.try_into()?) - } - - /// Parse a [`OffsetDateTime`] from the format description. - fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + /// Parse a [`DateTime`] from the format description. + fn parse_date_time<O: MaybeOffset>( + &self, + input: &[u8], + ) -> Result<DateTime<O>, error::Parse> { Ok(self.parse(input)?.try_into()?) } } @@ -239,7 +237,7 @@ impl sealed::Sealed for Rfc2822 { }; // The RFC explicitly allows leap seconds. - parsed.set_leap_second_allowed(true); + parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true); #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 let zone_literal = first_match( @@ -299,7 +297,7 @@ impl sealed::Sealed for Rfc2822 { Ok(input) } - fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + fn parse_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, error::Parse> { use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; use crate::parsing::combinator::{ @@ -436,7 +434,9 @@ impl sealed::Sealed for Rfc2822 { } let mut nanosecond = 0; - let leap_second_input = if second == 60 { + let leap_second_input = if !O::HAS_LOGICAL_OFFSET { + false + } else if second == 60 { second = 59; nanosecond = 999_999_999; true @@ -448,7 +448,11 @@ impl sealed::Sealed for Rfc2822 { let date = Date::from_calendar_date(year as _, month, day)?; let time = Time::from_hms_nano(hour, minute, second, nanosecond)?; let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?; - Ok(date.with_time(time).assume_offset(offset)) + Ok(DateTime { + date, + time, + offset: maybe_offset_from_offset::<O>(offset), + }) })() .map_err(TryFromParsed::ComponentRange)?; @@ -529,7 +533,7 @@ impl sealed::Sealed for Rfc3339 { }; // The RFC explicitly allows leap seconds. - parsed.set_leap_second_allowed(true); + parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true); if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) { parsed @@ -574,7 +578,7 @@ impl sealed::Sealed for Rfc3339 { Ok(input) } - fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { + fn parse_date_time<O: MaybeOffset>(&self, input: &[u8]) -> Result<DateTime<O>, error::Parse> { use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; use crate::parsing::combinator::{ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, @@ -672,11 +676,13 @@ impl sealed::Sealed for Rfc3339 { false }; - let dt = Month::from_number(month) + let date = Month::from_number(month) .and_then(|month| Date::from_calendar_date(year as _, month, day)) - .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) - .map(|date| date.assume_offset(offset)) .map_err(TryFromParsed::ComponentRange)?; + let time = Time::from_hms_nano(hour, minute, second, nanosecond) + .map_err(TryFromParsed::ComponentRange)?; + let offset = maybe_offset_from_offset::<O>(offset); + let dt = DateTime { date, time, offset }; if leap_second_input && !dt.is_valid_leap_second_stand_in() { return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( @@ -744,7 +750,7 @@ impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { if !date_is_present && !time_is_present && !offset_is_present { match first_error { Some(err) => return Err(err), - None => unreachable!("an error should be present if no components were parsed"), + None => bug!("an error should be present if no components were parsed"), } } diff --git a/vendor/time/src/parsing/parsed.rs b/vendor/time/src/parsing/parsed.rs index 7b2279cbb..26405cb11 100644 --- a/vendor/time/src/parsing/parsed.rs +++ b/vendor/time/src/parsing/parsed.rs @@ -3,15 +3,16 @@ use core::mem::MaybeUninit; use core::num::{NonZeroU16, NonZeroU8}; +use crate::date_time::{maybe_offset_from_offset, offset_kind, DateTime, MaybeOffset}; use crate::error::TryFromParsed::InsufficientInformation; use crate::format_description::modifier::{WeekNumberRepr, YearRepr}; #[cfg(feature = "alloc")] use crate::format_description::OwnedFormatItem; use crate::format_description::{Component, FormatItem}; use crate::parsing::component::{ - parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute, - parse_offset_second, parse_ordinal, parse_period, parse_second, parse_subsecond, - parse_week_number, parse_weekday, parse_year, Period, + parse_day, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour, + parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second, + parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period, }; use crate::parsing::ParsedItem; use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; @@ -98,6 +99,10 @@ impl sealed::AnyFormatItem for OwnedFormatItem { } } +/// The type of the `flags` field in [`Parsed`]. Allows for changing a single location and having it +/// effect all uses. +type Flag = u32; + /// All information parsed. /// /// This information is directly used to construct the final values. @@ -107,7 +112,7 @@ impl sealed::AnyFormatItem for OwnedFormatItem { #[derive(Debug, Clone, Copy)] pub struct Parsed { /// Bitflags indicating whether a particular field is present. - flags: u16, + flags: Flag, /// Calendar year. year: MaybeUninit<i32>, /// The last two digits of the calendar year. @@ -149,26 +154,35 @@ pub struct Parsed { offset_minute: MaybeUninit<i8>, /// Seconds within the minute of the UTC offset. offset_second: MaybeUninit<i8>, + /// The Unix timestamp in nanoseconds. + unix_timestamp_nanos: MaybeUninit<i128>, } #[allow(clippy::missing_docs_in_private_items)] impl Parsed { - const YEAR_FLAG: u16 = 1 << 0; - const YEAR_LAST_TWO_FLAG: u16 = 1 << 1; - const ISO_YEAR_FLAG: u16 = 1 << 2; - const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3; - const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4; - const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5; - const HOUR_24_FLAG: u16 = 1 << 6; - const MINUTE_FLAG: u16 = 1 << 7; - const SECOND_FLAG: u16 = 1 << 8; - const SUBSECOND_FLAG: u16 = 1 << 9; - const OFFSET_HOUR_FLAG: u16 = 1 << 10; - const OFFSET_MINUTE_FLAG: u16 = 1 << 11; - const OFFSET_SECOND_FLAG: u16 = 1 << 12; + const YEAR_FLAG: Flag = 1 << 0; + const YEAR_LAST_TWO_FLAG: Flag = 1 << 1; + const ISO_YEAR_FLAG: Flag = 1 << 2; + const ISO_YEAR_LAST_TWO_FLAG: Flag = 1 << 3; + const SUNDAY_WEEK_NUMBER_FLAG: Flag = 1 << 4; + const MONDAY_WEEK_NUMBER_FLAG: Flag = 1 << 5; + const HOUR_24_FLAG: Flag = 1 << 6; + const MINUTE_FLAG: Flag = 1 << 7; + const SECOND_FLAG: Flag = 1 << 8; + const SUBSECOND_FLAG: Flag = 1 << 9; + const OFFSET_HOUR_FLAG: Flag = 1 << 10; + const OFFSET_MINUTE_FLAG: Flag = 1 << 11; + const OFFSET_SECOND_FLAG: Flag = 1 << 12; /// Indicates whether a leap second is permitted to be parsed. This is required by some /// well-known formats. - const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13; + pub(super) const LEAP_SECOND_ALLOWED_FLAG: Flag = 1 << 13; + /// Indicates whether the `UtcOffset` is negative. This information is obtained when parsing the + /// offset hour, but may not otherwise be stored due to "-0" being equivalent to "0". + const OFFSET_IS_NEGATIVE_FLAG: Flag = 1 << 14; + /// Does the value at `OFFSET_IS_NEGATIVE_FLAG` have any semantic meaning, or is it just the + /// default value? If the latter, the value should be considered to have no meaning. + const OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED: Flag = 1 << 15; + const UNIX_TIMESTAMP_NANOS_FLAG: Flag = 1 << 16; } impl Parsed { @@ -196,6 +210,7 @@ impl Parsed { offset_hour: MaybeUninit::uninit(), offset_minute: MaybeUninit::uninit(), offset_second: MaybeUninit::uninit(), + unix_timestamp_nanos: MaybeUninit::uninit(), } } @@ -315,7 +330,13 @@ impl Parsed { .and_then(|parsed| parsed.consume_value(|value| self.set_subsecond(value))) .ok_or(InvalidComponent("subsecond")), Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers) - .and_then(|parsed| parsed.consume_value(|value| self.set_offset_hour(value))) + .and_then(|parsed| { + parsed.consume_value(|(value, is_negative)| { + self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED, true); + self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG, is_negative); + self.set_offset_hour(value) + }) + }) .ok_or(InvalidComponent("offset hour")), Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers) .and_then(|parsed| { @@ -327,6 +348,28 @@ impl Parsed { parsed.consume_value(|value| self.set_offset_second_signed(value)) }) .ok_or(InvalidComponent("offset second")), + Component::Ignore(modifiers) => parse_ignore(input, modifiers) + .map(ParsedItem::<()>::into_inner) + .ok_or(InvalidComponent("ignore")), + Component::UnixTimestamp(modifiers) => parse_unix_timestamp(input, modifiers) + .and_then(|parsed| { + parsed.consume_value(|value| self.set_unix_timestamp_nanos(value)) + }) + .ok_or(InvalidComponent("unix_timestamp")), + } + } + + /// Get the value of the provided flag. + const fn get_flag(&self, flag: Flag) -> bool { + self.flags & flag == flag + } + + /// Set the value of the provided flag. + pub(super) fn set_flag(&mut self, flag: Flag, value: bool) { + if value { + self.flags |= flag; + } else { + self.flags &= !flag; } } } @@ -345,7 +388,7 @@ macro_rules! getters { (! @$flag:ident $name:ident : $ty:ty) => { /// Obtain the named component. pub const fn $name(&self) -> Option<$ty> { - if self.flags & Self::$flag != Self::$flag { + if !self.get_flag(Self::$flag) { None } else { // SAFETY: We just checked if the field is present. @@ -376,6 +419,7 @@ impl Parsed { @SECOND_FLAG second: u8, @SUBSECOND_FLAG subsecond: u32, @OFFSET_HOUR_FLAG offset_hour: i8, + @UNIX_TIMESTAMP_NANOS_FLAG unix_timestamp_nanos: i128, } /// Obtain the absolute value of the offset minute. @@ -386,11 +430,19 @@ impl Parsed { /// Obtain the offset minute as an `i8`. pub const fn offset_minute_signed(&self) -> Option<i8> { - if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG { + if !self.get_flag(Self::OFFSET_MINUTE_FLAG) { None } else { // SAFETY: We just checked if the field is present. - Some(unsafe { self.offset_minute.assume_init() }) + let value = unsafe { self.offset_minute.assume_init() }; + + if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED) + && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG)) + { + Some(-value) + } else { + Some(value) + } } } @@ -402,17 +454,20 @@ impl Parsed { /// Obtain the offset second as an `i8`. pub const fn offset_second_signed(&self) -> Option<i8> { - if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG { + if !self.get_flag(Self::OFFSET_SECOND_FLAG) { None } else { // SAFETY: We just checked if the field is present. - Some(unsafe { self.offset_second.assume_init() }) - } - } + let value = unsafe { self.offset_second.assume_init() }; - /// Obtain whether leap seconds are permitted in the current format. - pub(crate) const fn leap_second_allowed(&self) -> bool { - self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG + if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED) + && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG)) + { + Some(-value) + } else { + Some(value) + } + } } } @@ -434,7 +489,7 @@ macro_rules! setters { /// Set the named component. pub fn $setter_name(&mut self, value: $ty) -> Option<()> { self.$name = MaybeUninit::new(value); - self.flags |= Self::$flag; + self.set_flag(Self::$flag, true); Some(()) } }; @@ -464,6 +519,7 @@ impl Parsed { @SECOND_FLAG set_second second: u8, @SUBSECOND_FLAG set_subsecond subsecond: u32, @OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8, + @UNIX_TIMESTAMP_NANOS_FLAG set_unix_timestamp_nanos unix_timestamp_nanos: i128, } /// Set the named component. @@ -482,7 +538,7 @@ impl Parsed { /// Set the `offset_minute` component. pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> { self.offset_minute = MaybeUninit::new(value); - self.flags |= Self::OFFSET_MINUTE_FLAG; + self.set_flag(Self::OFFSET_MINUTE_FLAG, true); Some(()) } @@ -502,18 +558,9 @@ impl Parsed { /// Set the `offset_second` component. pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> { self.offset_second = MaybeUninit::new(value); - self.flags |= Self::OFFSET_SECOND_FLAG; + self.set_flag(Self::OFFSET_SECOND_FLAG, true); Some(()) } - - /// Set the leap second allowed flag. - pub(crate) fn set_leap_second_allowed(&mut self, value: bool) { - if value { - self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG; - } else { - self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG; - } - } } /// Generate build methods for each of the fields. @@ -564,6 +611,7 @@ impl Parsed { @SECOND_FLAG with_second second: u8, @SUBSECOND_FLAG with_subsecond subsecond: u32, @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8, + @UNIX_TIMESTAMP_NANOS_FLAG with_unix_timestamp_nanos unix_timestamp_nanos: i128, } /// Set the named component and return `self`. @@ -718,31 +766,58 @@ impl TryFrom<Parsed> for UtcOffset { } impl TryFrom<Parsed> for PrimitiveDateTime { - type Error = error::TryFromParsed; + type Error = <DateTime<offset_kind::None> as TryFrom<Parsed>>::Error; fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { - Ok(Self::new(parsed.try_into()?, parsed.try_into()?)) + parsed.try_into().map(Self) } } impl TryFrom<Parsed> for OffsetDateTime { + type Error = <DateTime<offset_kind::Fixed> as TryFrom<Parsed>>::Error; + + fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { + parsed.try_into().map(Self) + } +} + +impl<O: MaybeOffset> TryFrom<Parsed> for DateTime<O> { type Error = error::TryFromParsed; #[allow(clippy::unwrap_in_result)] // We know the values are valid. fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> { + if O::HAS_LOGICAL_OFFSET { + if let Some(timestamp) = parsed.unix_timestamp_nanos() { + let DateTime { date, time, offset } = + DateTime::<offset_kind::Fixed>::from_unix_timestamp_nanos(timestamp)?; + return Ok(Self { + date, + time, + offset: maybe_offset_from_offset::<O>(offset), + }); + } + } + // 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"); - true - } else { - false + let leap_second_input = + if parsed.get_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG) && 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"); + true + } else { + false + }; + + let dt = Self { + date: Date::try_from(parsed)?, + time: Time::try_from(parsed)?, + offset: O::try_from_parsed(parsed)?, }; - let dt = PrimitiveDateTime::try_from(parsed)?.assume_offset(parsed.try_into()?); + if leap_second_input && !dt.is_valid_leap_second_stand_in() { return Err(error::TryFromParsed::ComponentRange( error::ComponentRange { diff --git a/vendor/time/src/parsing/shim.rs b/vendor/time/src/parsing/shim.rs index 00aaf4852..ced7feb03 100644 --- a/vendor/time/src/parsing/shim.rs +++ b/vendor/time/src/parsing/shim.rs @@ -31,7 +31,7 @@ macro_rules! impl_parse_bytes { } )*) } -impl_parse_bytes! { u8 u16 u32 } +impl_parse_bytes! { u8 u16 u32 u128 } /// Parse the given types from bytes. macro_rules! impl_parse_bytes_nonzero { diff --git a/vendor/time/src/primitive_date_time.rs b/vendor/time/src/primitive_date_time.rs index 6e842092a..9850f96d4 100644 --- a/vendor/time/src/primitive_date_time.rs +++ b/vendor/time/src/primitive_date_time.rs @@ -1,25 +1,24 @@ //! The [`PrimitiveDateTime`] struct and its associated `impl`s. use core::fmt; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration as StdDuration; #[cfg(feature = "formatting")] use std::io; +use crate::date_time::offset_kind; #[cfg(feature = "formatting")] use crate::formatting::Formattable; #[cfg(feature = "parsing")] use crate::parsing::Parsable; -use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; +use crate::{error, Date, DateTime, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday}; + +/// The actual type doing all the work. +type Inner = DateTime<offset_kind::None>; /// Combined date and time. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct PrimitiveDateTime { - #[allow(clippy::missing_docs_in_private_items)] - pub(crate) date: Date, - #[allow(clippy::missing_docs_in_private_items)] - pub(crate) time: Time, -} +pub struct PrimitiveDateTime(#[allow(clippy::missing_docs_in_private_items)] pub(crate) Inner); impl PrimitiveDateTime { /// The smallest value that can be represented by `PrimitiveDateTime`. @@ -49,7 +48,7 @@ impl PrimitiveDateTime { doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));" )] /// ``` - pub const MIN: Self = Self::new(Date::MIN, Time::MIN); + pub const MIN: Self = Self(Inner::MIN); /// The largest value that can be represented by `PrimitiveDateTime`. /// @@ -78,7 +77,7 @@ impl PrimitiveDateTime { doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));" )] /// ``` - pub const MAX: Self = Self::new(Date::MAX, Time::MAX); + pub const MAX: Self = Self(Inner::MAX); /// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`]. /// @@ -91,7 +90,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn new(date: Date, time: Time) -> Self { - Self { date, time } + Self(Inner::new(date, time)) } // region: component getters @@ -102,7 +101,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01)); /// ``` pub const fn date(self) -> Date { - self.date + self.0.date() } /// Get the [`Time`] component of the `PrimitiveDateTime`. @@ -111,7 +110,7 @@ impl PrimitiveDateTime { /// # use time_macros::{datetime, time}; /// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00)); pub const fn time(self) -> Time { - self.time + self.0.time() } // endregion component getters @@ -125,7 +124,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020); /// ``` pub const fn year(self) -> i32 { - self.date.year() + self.0.year() } /// Get the month of the date. @@ -137,7 +136,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December); /// ``` pub const fn month(self) -> Month { - self.date.month() + self.0.month() } /// Get the day of the date. @@ -150,7 +149,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).day(), 31); /// ``` pub const fn day(self) -> u8 { - self.date.day() + self.0.day() } /// Get the day of the year. @@ -163,7 +162,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365); /// ``` pub const fn ordinal(self) -> u16 { - self.date.ordinal() + self.0.ordinal() } /// Get the ISO week number. @@ -179,7 +178,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53); /// ``` pub const fn iso_week(self) -> u8 { - self.date.iso_week() + self.0.iso_week() } /// Get the week number where week 1 begins on the first Sunday. @@ -194,7 +193,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).sunday_based_week(), 0); /// ``` pub const fn sunday_based_week(self) -> u8 { - self.date.sunday_based_week() + self.0.sunday_based_week() } /// Get the week number where week 1 begins on the first Monday. @@ -209,7 +208,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2021-01-01 0:00).monday_based_week(), 0); /// ``` pub const fn monday_based_week(self) -> u8 { - self.date.monday_based_week() + self.0.monday_based_week() } /// Get the year, month, and day. @@ -223,7 +222,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn to_calendar_date(self) -> (i32, Month, u8) { - self.date.to_calendar_date() + self.0.to_calendar_date() } /// Get the year and ordinal day number. @@ -233,7 +232,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.date.to_ordinal_date() + self.0.to_ordinal_date() } /// Get the ISO 8601 year, week number, and weekday. @@ -263,7 +262,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { - self.date.to_iso_week_date() + self.0.to_iso_week_date() } /// Get the weekday. @@ -285,7 +284,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday); /// ``` pub const fn weekday(self) -> Weekday { - self.date.weekday() + self.0.weekday() } /// Get the Julian day for the date. The time is not taken into account for this calculation. @@ -301,7 +300,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.date.to_julian_day() + self.0.to_julian_day() } // endregion date getters @@ -314,7 +313,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.time.as_hms() + self.0.as_hms() } /// Get the clock hour, minute, second, and millisecond. @@ -328,7 +327,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { - self.time.as_hms_milli() + self.0.as_hms_milli() } /// Get the clock hour, minute, second, and microsecond. @@ -342,7 +341,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { - self.time.as_hms_micro() + self.0.as_hms_micro() } /// Get the clock hour, minute, second, and nanosecond. @@ -356,7 +355,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { - self.time.as_hms_nano() + self.0.as_hms_nano() } /// Get the clock hour. @@ -369,7 +368,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23); /// ``` pub const fn hour(self) -> u8 { - self.time.hour() + self.0.hour() } /// Get the minute within the hour. @@ -382,7 +381,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59); /// ``` pub const fn minute(self) -> u8 { - self.time.minute() + self.0.minute() } /// Get the second within the minute. @@ -395,7 +394,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59); /// ``` pub const fn second(self) -> u8 { - self.time.second() + self.0.second() } /// Get the milliseconds within the second. @@ -408,7 +407,7 @@ impl PrimitiveDateTime { /// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999); /// ``` pub const fn millisecond(self) -> u16 { - self.time.millisecond() + self.0.millisecond() } /// Get the microseconds within the second. @@ -424,7 +423,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn microsecond(self) -> u32 { - self.time.microsecond() + self.0.microsecond() } /// Get the nanoseconds within the second. @@ -440,7 +439,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn nanosecond(self) -> u32 { - self.time.nanosecond() + self.0.nanosecond() } // endregion time getters @@ -464,10 +463,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime { - OffsetDateTime { - local_datetime: self, - offset, - } + OffsetDateTime(self.0.assume_offset(offset)) } /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an @@ -481,7 +477,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn assume_utc(self) -> OffsetDateTime { - self.assume_offset(UtcOffset::UTC) + OffsetDateTime(self.0.assume_utc()) } // endregion attach offset @@ -503,17 +499,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option<Self> { - 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, - }) + Some(Self(const_try_opt!(self.0.checked_add(duration)))) } /// Computes `self - duration`, returning `None` if an overflow occurred. @@ -533,17 +519,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn checked_sub(self, duration: Duration) -> Option<Self> { - 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, - }) + Some(Self(const_try_opt!(self.0.checked_sub(duration)))) } // endregion: checked arithmetic @@ -569,13 +545,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn saturating_add(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_add(duration) { - datetime - } else if duration.is_negative() { - Self::MIN - } else { - Self::MAX - } + Self(self.0.saturating_add(duration)) } /// Computes `self - duration`, saturating value on overflow. @@ -599,13 +569,7 @@ impl PrimitiveDateTime { /// ); /// ``` pub const fn saturating_sub(self, duration: Duration) -> Self { - if let Some(datetime) = self.checked_sub(duration) { - datetime - } else if duration.is_negative() { - Self::MAX - } else { - Self::MIN - } + Self(self.0.saturating_sub(duration)) } // endregion: saturating arithmetic } @@ -624,7 +588,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_time(self, time: Time) -> Self { - self.date.with_time(time) + Self(self.0.replace_time(time)) } /// Replace the date, preserving the time. @@ -638,7 +602,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_date(self, date: Date) -> Self { - date.with_time(self.time) + Self(self.0.replace_date(date)) } /// Replace the year. The month and day will be unchanged. @@ -654,7 +618,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { - Ok(const_try!(self.date.replace_year(year)).with_time(self.time)) + Ok(Self(const_try!(self.0.replace_year(year)))) } /// Replace the month of the year. @@ -670,7 +634,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { - Ok(const_try!(self.date.replace_month(month)).with_time(self.time)) + Ok(Self(const_try!(self.0.replace_month(month)))) } /// Replace the day of the month. @@ -686,7 +650,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { - Ok(const_try!(self.date.replace_day(day)).with_time(self.time)) + Ok(Self(const_try!(self.0.replace_day(day)))) } /// Replace the clock hour. @@ -701,9 +665,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_hour(hour)))) + Ok(Self(const_try!(self.0.replace_hour(hour)))) } /// Replace the minutes within the hour. @@ -718,9 +680,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_minute(minute)))) + Ok(Self(const_try!(self.0.replace_minute(minute)))) } /// Replace the seconds within the minute. @@ -735,9 +695,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_second(second)))) + Ok(Self(const_try!(self.0.replace_second(second)))) } /// Replace the milliseconds within the second. @@ -755,9 +713,7 @@ impl PrimitiveDateTime { self, millisecond: u16, ) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_millisecond(millisecond)))) + Ok(Self(const_try!(self.0.replace_millisecond(millisecond)))) } /// Replace the microseconds within the second. @@ -775,9 +731,7 @@ impl PrimitiveDateTime { self, microsecond: u32, ) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_microsecond(microsecond)))) + Ok(Self(const_try!(self.0.replace_microsecond(microsecond)))) } /// Replace the nanoseconds within the second. @@ -792,9 +746,7 @@ impl PrimitiveDateTime { /// ``` #[must_use = "This method does not mutate the original `PrimitiveDateTime`."] pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { - Ok(self - .date() - .with_time(const_try!(self.time.replace_nanosecond(nanosecond)))) + Ok(Self(const_try!(self.0.replace_nanosecond(nanosecond)))) } } // endregion replacement @@ -809,7 +761,7 @@ impl PrimitiveDateTime { output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result<usize, error::Format> { - format.format_into(output, Some(self.date), Some(self.time), None) + self.0.format_into(output, format) } /// Format the `PrimitiveDateTime` using the provided [format @@ -826,7 +778,7 @@ impl PrimitiveDateTime { /// # Ok::<_, time::Error>(()) /// ``` pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { - format.format(Some(self.date), Some(self.time), None) + self.0.format(format) } } @@ -849,13 +801,13 @@ impl PrimitiveDateTime { input: &str, description: &(impl Parsable + ?Sized), ) -> Result<Self, error::Parse> { - description.parse_date_time(input.as_bytes()) + Inner::parse(input, description).map(Self) } } impl fmt::Display for PrimitiveDateTime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} {}", self.date, self.time) + self.0.fmt(f) } } @@ -871,8 +823,7 @@ impl Add<Duration> for PrimitiveDateTime { type Output = Self; fn add(self, duration: Duration) -> Self::Output { - self.checked_add(duration) - .expect("resulting value is out of range") + Self(self.0.add(duration)) } } @@ -880,29 +831,27 @@ impl Add<StdDuration> for PrimitiveDateTime { type Output = Self; 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, - } + Self(self.0.add(duration)) } } -impl_add_assign!(PrimitiveDateTime: Duration, StdDuration); +impl AddAssign<Duration> for PrimitiveDateTime { + fn add_assign(&mut self, duration: Duration) { + self.0.add_assign(duration); + } +} + +impl AddAssign<StdDuration> for PrimitiveDateTime { + fn add_assign(&mut self, duration: StdDuration) { + self.0.add_assign(duration); + } +} impl Sub<Duration> for PrimitiveDateTime { type Output = Self; fn sub(self, duration: Duration) -> Self::Output { - self.checked_sub(duration) - .expect("resulting value is out of range") + Self(self.0.sub(duration)) } } @@ -910,28 +859,27 @@ impl Sub<StdDuration> for PrimitiveDateTime { type Output = Self; 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, - } + Self(self.0.sub(duration)) + } +} + +impl SubAssign<Duration> for PrimitiveDateTime { + fn sub_assign(&mut self, duration: Duration) { + self.0.sub_assign(duration); } } -impl_sub_assign!(PrimitiveDateTime: Duration, StdDuration); +impl SubAssign<StdDuration> for PrimitiveDateTime { + fn sub_assign(&mut self, duration: StdDuration) { + self.0.sub_assign(duration); + } +} impl Sub for PrimitiveDateTime { type Output = Duration; fn sub(self, rhs: Self) -> Self::Output { - (self.date - rhs.date) + (self.time - rhs.time) + self.0.sub(rhs.0) } } // endregion trait impls diff --git a/vendor/time/src/quickcheck.rs b/vendor/time/src/quickcheck.rs index 707f3e0a9..2304dc89e 100644 --- a/vendor/time/src/quickcheck.rs +++ b/vendor/time/src/quickcheck.rs @@ -38,6 +38,7 @@ use alloc::boxed::Box; use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen}; +use crate::date_time::{DateTime, MaybeOffset}; use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; /// Obtain an arbitrary value between the minimum and maximum inclusive. @@ -118,15 +119,11 @@ impl Arbitrary for Time { impl Arbitrary for PrimitiveDateTime { fn arbitrary(g: &mut Gen) -> Self { - Self::new(<_>::arbitrary(g), <_>::arbitrary(g)) + Self(<_>::arbitrary(g)) } fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { - Box::new( - (self.date, self.time) - .shrink() - .map(|(date, time)| Self { date, time }), - ) + Box::new(self.0.shrink().map(Self)) } } @@ -151,15 +148,28 @@ impl Arbitrary for UtcOffset { impl Arbitrary for OffsetDateTime { fn arbitrary(g: &mut Gen) -> Self { - let datetime = PrimitiveDateTime::arbitrary(g); - datetime.assume_offset(<_>::arbitrary(g)) + Self(<_>::arbitrary(g)) + } + + fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { + Box::new(self.0.shrink().map(Self)) + } +} + +impl<O: MaybeOffset + 'static> Arbitrary for DateTime<O> { + fn arbitrary(g: &mut Gen) -> Self { + Self { + date: <_>::arbitrary(g), + time: <_>::arbitrary(g), + offset: <_>::arbitrary(g), + } } fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { Box::new( - (self.local_datetime, self.offset) + (self.date, self.time, self.offset) .shrink() - .map(|(local_datetime, offset)| local_datetime.assume_offset(offset)), + .map(|(date, time, offset)| Self { date, time, offset }), ) } } diff --git a/vendor/time/src/serde/iso8601.rs b/vendor/time/src/serde/iso8601.rs index 75deb62f1..f9e8a20f1 100644 --- a/vendor/time/src/serde/iso8601.rs +++ b/vendor/time/src/serde/iso8601.rs @@ -40,7 +40,7 @@ pub fn serialize<S: Serializer>( /// Deserialize an [`OffsetDateTime`] from its ISO 8601 representation. #[cfg(feature = "parsing")] pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> { - deserializer.deserialize_any(Visitor::<Iso8601<SERDE_CONFIG>>(PhantomData)) + deserializer.deserialize_str(Visitor::<Iso8601<SERDE_CONFIG>>(PhantomData)) } /// Use the well-known ISO 8601 format when serializing and deserializing an diff --git a/vendor/time/src/serde/mod.rs b/vendor/time/src/serde/mod.rs index e9f6d4394..248356e1a 100644 --- a/vendor/time/src/serde/mod.rs +++ b/vendor/time/src/serde/mod.rs @@ -108,10 +108,10 @@ impl Serialize for Date { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { #[cfg(feature = "serde-human-readable")] if serializer.is_human_readable() { - return serializer.serialize_str(&match self.format(&DATE_FORMAT) { - Ok(s) => s, - Err(_) => return Err(S::Error::custom("failed formatting `Date`")), + guard!(let Ok(s) = self.format(&DATE_FORMAT) else { + return Err(S::Error::custom("failed formatting `Date`")); }); + return serializer.serialize_str(&s); } (self.year(), self.ordinal()).serialize(serializer) @@ -171,10 +171,10 @@ impl Serialize for OffsetDateTime { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { #[cfg(feature = "serde-human-readable")] if serializer.is_human_readable() { - return serializer.serialize_str(&match self.format(&OFFSET_DATE_TIME_FORMAT) { - Ok(s) => s, - Err(_) => return Err(S::Error::custom("failed formatting `OffsetDateTime`")), + guard!(let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else { + return Err(S::Error::custom("failed formatting `OffsetDateTime`")); }); + return serializer.serialize_str(&s); } ( @@ -184,9 +184,9 @@ impl Serialize for OffsetDateTime { self.minute(), self.second(), self.nanosecond(), - self.offset.whole_hours(), - self.offset.minutes_past_hour(), - self.offset.seconds_past_minute(), + self.offset().whole_hours(), + self.offset().minutes_past_hour(), + self.offset().seconds_past_minute(), ) .serialize(serializer) } @@ -216,10 +216,10 @@ impl Serialize for PrimitiveDateTime { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { #[cfg(feature = "serde-human-readable")] if serializer.is_human_readable() { - return serializer.serialize_str(&match self.format(&PRIMITIVE_DATE_TIME_FORMAT) { - Ok(s) => s, - Err(_) => return Err(<S::Error>::custom("failed formatting `PrimitiveDateTime`")), + guard!(let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else { + return Err(S::Error::custom("failed formatting `PrimitiveDateTime`")); }); + return serializer.serialize_str(&s); } ( @@ -262,10 +262,10 @@ impl Serialize for Time { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { #[cfg(feature = "serde-human-readable")] if serializer.is_human_readable() { - return serializer.serialize_str(&match self.format(&TIME_FORMAT) { - Ok(s) => s, - Err(_) => return Err(S::Error::custom("failed formatting `Time`")), + guard!(let Ok(s) = self.format(&TIME_FORMAT) else { + return Err(S::Error::custom("failed formatting `Time`")); }); + return serializer.serialize_str(&s); } (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer) @@ -298,10 +298,10 @@ impl Serialize for UtcOffset { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { #[cfg(feature = "serde-human-readable")] if serializer.is_human_readable() { - return serializer.serialize_str(&match self.format(&UTC_OFFSET_FORMAT) { - Ok(s) => s, - Err(_) => return Err(S::Error::custom("failed formatting `UtcOffset`")), + guard!(let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else { + return Err(S::Error::custom("failed formatting `UtcOffset`")); }); + return serializer.serialize_str(&s); } ( diff --git a/vendor/time/src/shim.rs b/vendor/time/src/shim.rs new file mode 100644 index 000000000..b9ac97a16 --- /dev/null +++ b/vendor/time/src/shim.rs @@ -0,0 +1,180 @@ +//! Macro for simulating let-else on older compilers. +//! +//! This module and its macros will be removed once the MSRV is 1.65 (NET 2023-05-03). + +#![allow(unused_macros)] +#![allow(clippy::missing_docs_in_private_items)] + +// The following code is copyright 2016 Alex Burka. Available under the MIT OR Apache-2.0 license. +// Some adaptations have been made to the original code. + +pub(crate) enum LetElseBodyMustDiverge {} + +#[allow(clippy::missing_docs_in_private_items)] +macro_rules! __guard_output { + ((($($imms:ident)*) ($($muts:ident)*)), + [($($pattern:tt)*) ($rhs:expr) ($diverge:expr)]) => { + __guard_impl!(@as_stmt + let ($($imms,)* $(mut $muts,)*) = { + #[allow(unused_mut)] + match $rhs { + $($pattern)* => { + ($($imms,)* $($muts,)*) + }, + _ => { + let _: $crate::shim::LetElseBodyMustDiverge = $diverge; + }, + } + } + ) + }; +} + +macro_rules! __guard_impl { + // 0. cast a series of token trees to a statement + (@as_stmt $s:stmt) => { $s }; + + // 1. output stage + (@collect () -> $($rest:tt)*) => { + __guard_output!($($rest)*) + }; + + + // 2. identifier collection stage + // The pattern is scanned destructively. Anything that looks like a capture (including + // false positives, like un-namespaced/empty structs or enum variants) is copied into the + // appropriate identifier list. Irrelevant symbols are discarded. The scanning descends + // recursively into bracketed structures. + + // unwrap brackets and prepend their contents to the pattern remainder, in case there are captures inside + (@collect (($($inside:tt)*) $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru) + }; + (@collect ({$($inside:tt)*} $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru) + }; + (@collect ([$($inside:tt)*] $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru) + }; + + // discard irrelevant symbols + (@collect (, $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + (@collect (.. $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + (@collect (@ $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + (@collect (_ $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + (@collect (& $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + + // ignore generic parameters + (@collect (:: <$($generic:tt),*> $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + // a path can't be a capture, and a path can't end with ::, so the ident after :: is never a capture + (@collect (:: $pathend:ident $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + + // alternative patterns may be given with | as long as the same captures (including type) appear on each side + // due to this property, if we see a | we've already parsed all the captures and can simply stop + (@collect (| $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect () -> $idents, $thru) // discard the rest of the pattern, proceed to output stage + }; + + // throw away some identifiers that do not represent captures + + // an ident followed by a colon is the name of a structure member + (@collect ($id:ident: $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + // paths do not represent captures + (@collect ($pathcomp:ident :: $pathend:ident $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> $idents, $thru) + }; + // an ident followed by parentheses is the name of a tuple-like struct or enum variant + // (unwrap the parens to parse the contents) + (@collect ($id:ident ($($inside:tt)*) $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru) + }; + // an ident followed by curly braces is the name of a struct or struct-like enum variant + // (unwrap the braces to parse the contents) + (@collect ($id:ident {$($inside:tt)*} $($tail:tt)*) -> $idents:tt, $thru:tt) => { + __guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru) + }; + + // actually identifier collection happens here! + + // capture by mutable reference! + (@collect (ref mut $id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru) + }; + // capture by immutable reference! + (@collect (ref $id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru) + }; + // capture by move into mutable binding! + (@collect (mut $id:ident $($tail:tt)*) -> ($imms:tt ($($muts:ident)*)), $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> ($imms ($($muts)* $id)), $thru) + }; + // capture by move into an immutable binding! + (@collect ($id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => { + __guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru) + }; + + // 3. splitting (for new syntax) + + // done with pattern (and it's LPED=X) + (@split (else { $($diverge:tt)* } = $($tail:tt)*) -> ($pat:tt)) => { + __guard_impl!(@collect $pat -> (() ()), [$pat ($($tail)*) ({ $($diverge)* })]) + }; + + // done with pattern (and it's LP=XED) + (@split (= $($tail:tt)*) -> ($pat:tt)) => { + __guard_impl!(@split expr ($($tail)*) -> ($pat ())) + }; + + // found a token in the pattern + (@split ($head:tt $($tail:tt)*) -> (($($pat:tt)*))) => { + __guard_impl!(@split ($($tail)*) -> (($($pat)* $head))) + }; + + // found an "else DIVERGE" in the expr + (@split expr (else { $($tail:tt)* }) -> ($pat:tt $expr:tt)) => { + __guard_impl!(@collect $pat -> (() ()), [$pat $expr ({ $($tail)* })]) + }; + + // found an else in the expr with more stuff after it + (@split expr (else { $($body:tt)* } $($tail:tt)*) -> ($pat:tt ($($expr:tt)*))) => { + __guard_impl!(@split expr ($($tail)*) -> ($pat ($($expr)* else { $($body)* }))) + }; + + // found another token in the expr + (@split expr ($head:tt $($tail:tt)*) -> ($pat:tt ($($expr:tt)*))) => { + __guard_impl!(@split expr ($($tail)*) -> ($pat ($($expr)* $head))) + }; + + // 4. entry points + + // new syntax + (let $($tail:tt)*) => { + __guard_impl!(@split ($($tail)*) -> (())) + // | | | + // | | ^ pattern + // | ^ tail to be split into "PAT = EXPR else DIVERGE" + // ^ first pass will do the parsing + }; +} + +macro_rules! guard { + ($($input:tt)*) => { + __guard_impl!($($input)*) + }; +} diff --git a/vendor/time/src/sys/local_offset_at/mod.rs b/vendor/time/src/sys/local_offset_at/mod.rs index f0bc4be3c..3367ebb55 100644 --- a/vendor/time/src/sys/local_offset_at/mod.rs +++ b/vendor/time/src/sys/local_offset_at/mod.rs @@ -6,7 +6,7 @@ #[cfg_attr(target_family = "unix", path = "unix.rs")] #[cfg_attr( all( - target_arch = "wasm32", + target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi")), feature = "wasm-bindgen" ), @@ -19,5 +19,10 @@ use crate::{OffsetDateTime, UtcOffset}; /// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is /// returned. pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { - imp::local_offset_at(datetime) + // miri does not support tzset() + if cfg!(miri) { + None + } else { + imp::local_offset_at(datetime) + } } diff --git a/vendor/time/src/sys/local_offset_at/unix.rs b/vendor/time/src/sys/local_offset_at/unix.rs index 6e849892d..f4a808932 100644 --- a/vendor/time/src/sys/local_offset_at/unix.rs +++ b/vendor/time/src/sys/local_offset_at/unix.rs @@ -2,8 +2,26 @@ use core::mem::MaybeUninit; +use crate::util::local_offset::{self, Soundness}; use crate::{OffsetDateTime, UtcOffset}; +/// Whether the operating system has a thread-safe environment. This allows bypassing the check for +/// if the process is multi-threaded. +// This is the same value as `cfg!(target_os = "x")`. +// Use byte-strings to work around current limitations of const eval. +const OS_HAS_THREAD_SAFE_ENVIRONMENT: bool = match std::env::consts::OS.as_bytes() { + // https://github.com/illumos/illumos-gate/blob/0fb96ba1f1ce26ff8b286f8f928769a6afcb00a6/usr/src/lib/libc/port/gen/getenv.c + b"illumos" + // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/getenv.c + // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/setenv.c + | b"netbsd" + // https://github.com/apple-oss-distributions/Libc/blob/d526593760f0f79dfaeb8b96c3c8a42c791156ff/stdlib/FreeBSD/getenv.c + // https://github.com/apple-oss-distributions/Libc/blob/d526593760f0f79dfaeb8b96c3c8a42c791156ff/stdlib/FreeBSD/setenv.c + | b"macos" + => true, + _ => false, +}; + /// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error. /// /// # Safety @@ -59,65 +77,33 @@ unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> { target_os = "netbsd", target_os = "haiku", ))] -fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { - let seconds: i32 = tm.tm_gmtoff.try_into().ok()?; - UtcOffset::from_hms( - (seconds / 3_600) as _, - ((seconds / 60) % 60) as _, - (seconds % 60) as _, - ) - .ok() +fn tm_to_offset(_unix_timestamp: i64, tm: libc::tm) -> Option<UtcOffset> { + let seconds = tm.tm_gmtoff.try_into().ok()?; + UtcOffset::from_whole_seconds(seconds).ok() } /// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. -#[cfg(all( - not(unsound_local_offset), - not(any( - target_os = "redox", - target_os = "linux", - target_os = "l4re", - target_os = "android", - target_os = "emscripten", - target_os = "macos", - target_os = "ios", - target_os = "watchos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd", - target_os = "haiku", - )) -))] -#[allow(unused_variables, clippy::missing_const_for_fn)] -fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { - None -} - -/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. -// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such -// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is -// that daylight saving time does not start on the same date every year, nor are the rules for -// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to -// every other year, which is not always the case. -#[cfg(all( - unsound_local_offset, - not(any( - target_os = "redox", - target_os = "linux", - target_os = "l4re", - target_os = "android", - target_os = "emscripten", - target_os = "macos", - target_os = "ios", - target_os = "watchos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd", - target_os = "haiku", - )) -))] -fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { +/// +/// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. The +/// reason for this is that daylight saving time does not start on the same date every year, nor are +/// the rules for daylight saving time the same for every year. This implementation assumes 1970 is +/// equivalent to every other year, which is not always the case. +#[cfg(not(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", +)))] +fn tm_to_offset(unix_timestamp: i64, tm: libc::tm) -> Option<UtcOffset> { use crate::Date; let mut tm = tm; @@ -138,32 +124,34 @@ fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { .assume_utc() .unix_timestamp(); - let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp()) - .try_into() - .ok()?; + let diff_secs = (local_timestamp - unix_timestamp).try_into().ok()?; - UtcOffset::from_hms( - (diff_secs / 3_600) as _, - ((diff_secs / 60) % 60) as _, - (diff_secs % 60) as _, - ) - .ok() + UtcOffset::from_whole_seconds(diff_secs).ok() } /// Obtain the system's UTC offset. pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { - // Ensure that the process is single-threaded unless the user has explicitly opted out of this - // check. This is to prevent issues with the environment being mutated by a different thread in - // the process while execution of this function is taking place, which can cause a segmentation - // fault by dereferencing a dangling pointer. + // Continue to obtaining the UTC offset if and only if the call is sound or the user has + // explicitly opted out of soundness. + // + // Soundness can be guaranteed either by knowledge of the operating system or knowledge that the + // process is single-threaded. If the process is single-threaded, then the environment cannot + // be mutated by a different thread in the process while execution of this function is taking + // place, which can cause a segmentation fault by dereferencing a dangling pointer. + // // If the `num_threads` crate is incapable of determining the number of running threads, then // we conservatively return `None` to avoid a soundness bug. - if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) { - return None; - } - // Safety: We have just confirmed that the process is single-threaded or the user has explicitly - // opted out of soundness. - let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?; - tm_to_offset(tm) + if OS_HAS_THREAD_SAFE_ENVIRONMENT + || local_offset::get_soundness() == Soundness::Unsound + || num_threads::is_single_threaded() == Some(true) + { + let unix_timestamp = datetime.unix_timestamp(); + // Safety: We have just confirmed that the process is single-threaded or the user has + // explicitly opted out of soundness. + let tm = unsafe { timestamp_to_tm(unix_timestamp) }?; + tm_to_offset(unix_timestamp, tm) + } else { + None + } } diff --git a/vendor/time/src/sys/local_offset_at/windows.rs b/vendor/time/src/sys/local_offset_at/windows.rs index fed01bf3a..69b422314 100644 --- a/vendor/time/src/sys/local_offset_at/windows.rs +++ b/vendor/time/src/sys/local_offset_at/windows.rs @@ -101,14 +101,9 @@ pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { let ft_system = systemtime_to_filetime(&systime_utc)?; let ft_local = systemtime_to_filetime(&systime_local)?; - let diff_secs: i32 = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system)) + let diff_secs = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system)) .try_into() .ok()?; - UtcOffset::from_hms( - (diff_secs / 3_600) as _, - ((diff_secs / 60) % 60) as _, - (diff_secs % 60) as _, - ) - .ok() + UtcOffset::from_whole_seconds(diff_secs).ok() } diff --git a/vendor/time/src/tests.rs b/vendor/time/src/tests.rs index 66c977d91..030c473be 100644 --- a/vendor/time/src/tests.rs +++ b/vendor/time/src/tests.rs @@ -14,7 +14,7 @@ feature = "serde", ))] #![allow( - clippy::let_underscore_drop, + let_underscore_drop, clippy::clone_on_copy, clippy::cognitive_complexity, clippy::std_instead_of_core diff --git a/vendor/time/src/util.rs b/vendor/time/src/util.rs index 11330501c..b41068384 100644 --- a/vendor/time/src/util.rs +++ b/vendor/time/src/util.rs @@ -29,3 +29,61 @@ pub const fn days_in_year_month(year: i32, month: Month) -> u8 { February => 28, } } + +#[cfg(feature = "local-offset")] +/// Utility functions relating to the local UTC offset. +pub mod local_offset { + use core::sync::atomic::{AtomicBool, Ordering}; + + /// Whether obtaining the local UTC offset is required to be sound. + static LOCAL_OFFSET_IS_SOUND: AtomicBool = AtomicBool::new(true); + + /// The soundness of obtaining the local UTC offset. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum Soundness { + /// Obtaining the local UTC offset is required to be sound. Undefined behavior will never + /// occur. This is the default. + Sound, + /// Obtaining the local UTC offset is allowed to invoke undefined behavior. Setting this + /// value is strongly discouraged. To do so, you must comply with the safety requirements + /// of [`time::local_offset::set_soundness`](set_soundness). + Unsound, + } + + /// Set whether obtaining the local UTC offset is allowed to invoke undefined behavior. + /// + /// # Safety + /// + /// If this method is called with [`Soundness::Sound`], the call is always sound. If this method + /// is called with [`Soundness::Unsound`], the following conditions apply. + /// + /// - If the operating system is not Unix-like, the call is sound. + /// - If the process is single-threaded, the call is sound. + /// - If the process is multi-threaded, no other thread may mutate the environment in any way at + /// the same time a call to a method that obtains the local UTC offset. This includes adding, + /// removing, or modifying an environment variable. + /// + /// Note that you must not only verify this safety condition for your code, but for **all** code + /// that will be included in the final binary. Notably, it applies to both direct and transitive + /// dependencies and to both Rust and non-Rust code. For this reason it is not possible to + /// soundly pass [`Soundness::Unsound`] to this method if you are writing a library that may + /// used by others. + /// + /// The following methods currently obtain the local UTC offset: + /// + /// - [`OffsetDateTime::now_local`](crate::OffsetDateTime::now_local) + /// - [`UtcOffset::local_offset_at`](crate::UtcOffset::local_offset_at) + /// - [`UtcOffset::current_local_offset`](crate::UtcOffset::current_local_offset) + pub unsafe fn set_soundness(soundness: Soundness) { + LOCAL_OFFSET_IS_SOUND.store(soundness == Soundness::Sound, Ordering::SeqCst); + } + + /// Obtains the soundness of obtaining the local UTC offset. If it is [`Soundness::Unsound`], + /// it is allowed to invoke undefined behavior when obtaining the local UTC offset. + pub fn get_soundness() -> Soundness { + match LOCAL_OFFSET_IS_SOUND.load(Ordering::SeqCst) { + false => Soundness::Unsound, + true => Soundness::Sound, + } + } +} |