diff options
Diffstat (limited to 'vendor/time/src/utc_offset.rs')
| -rw-r--r-- | vendor/time/src/utc_offset.rs | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/vendor/time/src/utc_offset.rs b/vendor/time/src/utc_offset.rs new file mode 100644 index 00000000..73bcf2d1 --- /dev/null +++ b/vendor/time/src/utc_offset.rs @@ -0,0 +1,464 @@ +//! The [`UtcOffset`] struct and its associated `impl`s. + +#[cfg(feature = "formatting")] +use alloc::string::String; +use core::fmt; +use core::ops::Neg; +#[cfg(feature = "formatting")] +use std::io; + +use deranged::{RangedI32, RangedI8}; +use powerfmt::ext::FormatterExt; +use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; + +use crate::convert::*; +use crate::error; +#[cfg(feature = "formatting")] +use crate::formatting::Formattable; +use crate::internal_macros::ensure_ranged; +#[cfg(feature = "parsing")] +use crate::parsing::Parsable; +#[cfg(feature = "local-offset")] +use crate::sys::local_offset_at; +#[cfg(feature = "local-offset")] +use crate::OffsetDateTime; + +/// The type of the `hours` field of `UtcOffset`. +type Hours = RangedI8<-25, 25>; +/// The type of the `minutes` field of `UtcOffset`. +type Minutes = RangedI8<{ -(Minute::per(Hour) as i8 - 1) }, { Minute::per(Hour) as i8 - 1 }>; +/// The type of the `seconds` field of `UtcOffset`. +type Seconds = RangedI8<{ -(Second::per(Minute) as i8 - 1) }, { Second::per(Minute) as i8 - 1 }>; +/// The type capable of storing the range of whole seconds that a `UtcOffset` can encompass. +type WholeSeconds = RangedI32< + { + Hours::MIN.get() as i32 * Second::per(Hour) as i32 + + Minutes::MIN.get() as i32 * Second::per(Minute) as i32 + + Seconds::MIN.get() as i32 + }, + { + Hours::MAX.get() as i32 * Second::per(Hour) as i32 + + Minutes::MAX.get() as i32 * Second::per(Minute) as i32 + + Seconds::MAX.get() as i32 + }, +>; + +/// An offset from UTC. +/// +/// This struct can store values up to ±25:59:59. If you need support outside this range, please +/// file an issue with your use case. +// All three components _must_ have the same sign. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UtcOffset { + hours: Hours, + minutes: Minutes, + seconds: Seconds, +} + +impl UtcOffset { + /// A `UtcOffset` that is UTC. + /// + /// ```rust + /// # use time::UtcOffset; + /// # use time_macros::offset; + /// assert_eq!(UtcOffset::UTC, offset!(UTC)); + /// ``` + pub const UTC: Self = Self::from_whole_seconds_ranged(WholeSeconds::new_static::<0>()); + + /// Create a `UtcOffset` representing an offset of the hours, minutes, and seconds provided, the + /// validity of which must be guaranteed by the caller. All three parameters must have the same + /// sign. + /// + /// # Safety + /// + /// - Hours must be in the range `-25..=25`. + /// - Minutes must be in the range `-59..=59`. + /// - Seconds must be in the range `-59..=59`. + /// + /// While the signs of the parameters are required to match to avoid bugs, this is not a safety + /// invariant. + #[doc(hidden)] + pub const unsafe fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self { + // Safety: The caller must uphold the safety invariants. + unsafe { + Self::from_hms_ranged_unchecked( + Hours::new_unchecked(hours), + Minutes::new_unchecked(minutes), + Seconds::new_unchecked(seconds), + ) + } + } + + /// Create a `UtcOffset` representing an offset by the number of hours, minutes, and seconds + /// provided. + /// + /// The sign of all three components should match. If they do not, all smaller components will + /// have their signs flipped. + /// + /// ```rust + /// # use time::UtcOffset; + /// assert_eq!(UtcOffset::from_hms(1, 2, 3)?.as_hms(), (1, 2, 3)); + /// assert_eq!(UtcOffset::from_hms(1, -2, -3)?.as_hms(), (1, 2, 3)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub const fn from_hms( + hours: i8, + minutes: i8, + seconds: i8, + ) -> Result<Self, error::ComponentRange> { + Ok(Self::from_hms_ranged( + ensure_ranged!(Hours: hours), + ensure_ranged!(Minutes: minutes), + ensure_ranged!(Seconds: seconds), + )) + } + + /// Create a `UtcOffset` representing an offset of the hours, minutes, and seconds provided. All + /// three parameters must have the same sign. + /// + /// While the signs of the parameters are required to match, this is not a safety invariant. + pub(crate) const fn from_hms_ranged_unchecked( + hours: Hours, + minutes: Minutes, + seconds: Seconds, + ) -> Self { + if hours.get() < 0 { + debug_assert!(minutes.get() <= 0); + debug_assert!(seconds.get() <= 0); + } else if hours.get() > 0 { + debug_assert!(minutes.get() >= 0); + debug_assert!(seconds.get() >= 0); + } + if minutes.get() < 0 { + debug_assert!(seconds.get() <= 0); + } else if minutes.get() > 0 { + debug_assert!(seconds.get() >= 0); + } + + Self { + hours, + minutes, + seconds, + } + } + + /// Create a `UtcOffset` representing an offset by the number of hours, minutes, and seconds + /// provided. + /// + /// The sign of all three components should match. If they do not, all smaller components will + /// have their signs flipped. + pub(crate) const fn from_hms_ranged( + hours: Hours, + mut minutes: Minutes, + mut seconds: Seconds, + ) -> Self { + if (hours.get() > 0 && minutes.get() < 0) || (hours.get() < 0 && minutes.get() > 0) { + minutes = minutes.neg(); + } + if (hours.get() > 0 && seconds.get() < 0) + || (hours.get() < 0 && seconds.get() > 0) + || (minutes.get() > 0 && seconds.get() < 0) + || (minutes.get() < 0 && seconds.get() > 0) + { + seconds = seconds.neg(); + } + + Self { + hours, + minutes, + seconds, + } + } + + /// Create a `UtcOffset` representing an offset by the number of seconds provided. + /// + /// ```rust + /// # use time::UtcOffset; + /// assert_eq!(UtcOffset::from_whole_seconds(3_723)?.as_hms(), (1, 2, 3)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub const fn from_whole_seconds(seconds: i32) -> Result<Self, error::ComponentRange> { + Ok(Self::from_whole_seconds_ranged( + ensure_ranged!(WholeSeconds: seconds), + )) + } + + /// Create a `UtcOffset` representing an offset by the number of seconds provided. + // ignore because the function is crate-private + /// ```rust,ignore + /// # use time::UtcOffset; + /// # use deranged::RangedI32; + /// assert_eq!( + /// UtcOffset::from_whole_seconds_ranged(RangedI32::new_static::<3_723>()).as_hms(), + /// (1, 2, 3) + /// ); + /// # Ok::<_, time::Error>(()) + /// ``` + pub(crate) const fn from_whole_seconds_ranged(seconds: WholeSeconds) -> Self { + // Safety: The type of `seconds` guarantees that all values are in range. + unsafe { + Self::__from_hms_unchecked( + (seconds.get() / Second::per(Hour) as i32) as i8, + ((seconds.get() % Second::per(Hour) as i32) / Minute::per(Hour) as i32) as i8, + (seconds.get() % Second::per(Minute) as i32) as i8, + ) + } + } + + /// Obtain the UTC offset as its hours, minutes, and seconds. The sign of all three components + /// will always match. A positive value indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).as_hms(), (1, 2, 3)); + /// assert_eq!(offset!(-1:02:03).as_hms(), (-1, -2, -3)); + /// ``` + pub const fn as_hms(self) -> (i8, i8, i8) { + (self.hours.get(), self.minutes.get(), self.seconds.get()) + } + + /// Obtain the UTC offset as its hours, minutes, and seconds. The sign of all three components + /// will always match. A positive value indicates an offset to the east; a negative to the west. + #[cfg(feature = "quickcheck")] + pub(crate) const fn as_hms_ranged(self) -> (Hours, Minutes, Seconds) { + (self.hours, self.minutes, self.seconds) + } + + /// Obtain the number of whole hours the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_hours(), 1); + /// assert_eq!(offset!(-1:02:03).whole_hours(), -1); + /// ``` + pub const fn whole_hours(self) -> i8 { + self.hours.get() + } + + /// Obtain the number of whole minutes the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_minutes(), 62); + /// assert_eq!(offset!(-1:02:03).whole_minutes(), -62); + /// ``` + pub const fn whole_minutes(self) -> i16 { + self.hours.get() as i16 * Minute::per(Hour) as i16 + self.minutes.get() as i16 + } + + /// Obtain the number of minutes past the hour the offset is from UTC. A positive value + /// indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).minutes_past_hour(), 2); + /// assert_eq!(offset!(-1:02:03).minutes_past_hour(), -2); + /// ``` + pub const fn minutes_past_hour(self) -> i8 { + self.minutes.get() + } + + /// Obtain the number of whole seconds the offset is from UTC. A positive value indicates an + /// offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).whole_seconds(), 3723); + /// assert_eq!(offset!(-1:02:03).whole_seconds(), -3723); + /// ``` + // This may be useful for anyone manually implementing arithmetic, as it + // would let them construct a `Duration` directly. + pub const fn whole_seconds(self) -> i32 { + self.hours.get() as i32 * Second::per(Hour) as i32 + + self.minutes.get() as i32 * Second::per(Minute) as i32 + + self.seconds.get() as i32 + } + + /// Obtain the number of seconds past the minute the offset is from UTC. A positive value + /// indicates an offset to the east; a negative to the west. + /// + /// ```rust + /// # use time_macros::offset; + /// assert_eq!(offset!(+1:02:03).seconds_past_minute(), 3); + /// assert_eq!(offset!(-1:02:03).seconds_past_minute(), -3); + /// ``` + pub const fn seconds_past_minute(self) -> i8 { + self.seconds.get() + } + + /// Check if the offset is exactly UTC. + /// + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(!offset!(+1:02:03).is_utc()); + /// assert!(!offset!(-1:02:03).is_utc()); + /// assert!(offset!(UTC).is_utc()); + /// ``` + pub const fn is_utc(self) -> bool { + self.hours.get() == 0 && self.minutes.get() == 0 && self.seconds.get() == 0 + } + + /// Check if the offset is positive, or east of UTC. + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(offset!(+1:02:03).is_positive()); + /// assert!(!offset!(-1:02:03).is_positive()); + /// assert!(!offset!(UTC).is_positive()); + /// ``` + pub const fn is_positive(self) -> bool { + self.hours.get() > 0 || self.minutes.get() > 0 || self.seconds.get() > 0 + } + + /// Check if the offset is negative, or west of UTC. + /// + /// ```rust + /// # use time_macros::offset; + /// assert!(!offset!(+1:02:03).is_negative()); + /// assert!(offset!(-1:02:03).is_negative()); + /// assert!(!offset!(UTC).is_negative()); + /// ``` + pub const fn is_negative(self) -> bool { + self.hours.get() < 0 || self.minutes.get() < 0 || self.seconds.get() < 0 + } + + /// Attempt to obtain the system's UTC offset at a known moment in time. If the offset cannot be + /// determined, an error is returned. + /// + /// ```rust + /// # use time::{UtcOffset, OffsetDateTime}; + /// let local_offset = UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH); + /// # if false { + /// assert!(local_offset.is_ok()); + /// # } + /// ``` + #[cfg(feature = "local-offset")] + pub fn local_offset_at(datetime: OffsetDateTime) -> Result<Self, error::IndeterminateOffset> { + local_offset_at(datetime).ok_or(error::IndeterminateOffset) + } + + /// Attempt to obtain the system's current UTC offset. If the offset cannot be determined, an + /// error is returned. + /// + /// ```rust + /// # use time::UtcOffset; + /// let local_offset = UtcOffset::current_local_offset(); + /// # if false { + /// assert!(local_offset.is_ok()); + /// # } + /// ``` + #[cfg(feature = "local-offset")] + pub fn current_local_offset() -> Result<Self, error::IndeterminateOffset> { + let now = OffsetDateTime::now_utc(); + local_offset_at(now).ok_or(error::IndeterminateOffset) + } +} + +#[cfg(feature = "formatting")] +impl UtcOffset { + /// Format the `UtcOffset` using the provided [format description](crate::format_description). + pub fn format_into( + self, + output: &mut (impl io::Write + ?Sized), + format: &(impl Formattable + ?Sized), + ) -> Result<usize, error::Format> { + format.format_into(output, None, None, Some(self)) + } + + /// Format the `UtcOffset` using the provided [format description](crate::format_description). + /// + /// ```rust + /// # use time::format_description; + /// # use time_macros::offset; + /// let format = format_description::parse("[offset_hour sign:mandatory]:[offset_minute]")?; + /// assert_eq!(offset!(+1).format(&format)?, "+01:00"); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { + format.format(None, None, Some(self)) + } +} + +#[cfg(feature = "parsing")] +impl UtcOffset { + /// Parse a `UtcOffset` from the input using the provided [format + /// description](crate::format_description). + /// + /// ```rust + /// # use time::UtcOffset; + /// # use time_macros::{offset, format_description}; + /// let format = format_description!("[offset_hour]:[offset_minute]"); + /// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42)); + /// # Ok::<_, time::Error>(()) + /// ``` + pub fn parse( + input: &str, + description: &(impl Parsable + ?Sized), + ) -> Result<Self, error::Parse> { + description.parse_offset(input.as_bytes()) + } +} + +mod private { + #[non_exhaustive] + #[derive(Debug, Clone, Copy)] + pub struct UtcOffsetMetadata; +} +use private::UtcOffsetMetadata; + +impl SmartDisplay for UtcOffset { + type Metadata = UtcOffsetMetadata; + + fn metadata(&self, _: FormatterOptions) -> Metadata<Self> { + let sign = if self.is_negative() { '-' } else { '+' }; + let width = smart_display::padded_width_of!( + sign, + self.hours.abs() => width(2), + ":", + self.minutes.abs() => width(2), + ":", + self.seconds.abs() => width(2), + ); + Metadata::new(width, self, UtcOffsetMetadata) + } + + fn fmt_with_metadata( + &self, + f: &mut fmt::Formatter<'_>, + metadata: Metadata<Self>, + ) -> fmt::Result { + f.pad_with_width( + metadata.unpadded_width(), + format_args!( + "{}{:02}:{:02}:{:02}", + if self.is_negative() { '-' } else { '+' }, + self.hours.abs(), + self.minutes.abs(), + self.seconds.abs(), + ), + ) + } +} + +impl fmt::Display for UtcOffset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + SmartDisplay::fmt(self, f) + } +} + +impl fmt::Debug for UtcOffset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl Neg for UtcOffset { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::from_hms_ranged(self.hours.neg(), self.minutes.neg(), self.seconds.neg()) + } +} |
