summaryrefslogtreecommitdiffstats
path: root/third_party/rust/time/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/time/src
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--third_party/rust/time/src/date.rs1050
-rw-r--r--third_party/rust/time/src/duration.rs1139
-rw-r--r--third_party/rust/time/src/error/component_range.rs92
-rw-r--r--third_party/rust/time/src/error/conversion_range.rs36
-rw-r--r--third_party/rust/time/src/error/different_variant.rs34
-rw-r--r--third_party/rust/time/src/error/format.rs92
-rw-r--r--third_party/rust/time/src/error/indeterminate_offset.rs35
-rw-r--r--third_party/rust/time/src/error/invalid_format_description.rs80
-rw-r--r--third_party/rust/time/src/error/invalid_variant.rs34
-rw-r--r--third_party/rust/time/src/error/mod.rs112
-rw-r--r--third_party/rust/time/src/error/parse.rs97
-rw-r--r--third_party/rust/time/src/error/parse_from_description.rs47
-rw-r--r--third_party/rust/time/src/error/try_from_parsed.rs71
-rw-r--r--third_party/rust/time/src/ext.rs279
-rw-r--r--third_party/rust/time/src/format_description/borrowed_format_item.rs106
-rw-r--r--third_party/rust/time/src/format_description/component.rs37
-rw-r--r--third_party/rust/time/src/format_description/mod.rs34
-rw-r--r--third_party/rust/time/src/format_description/modifier.rs355
-rw-r--r--third_party/rust/time/src/format_description/owned_format_item.rs162
-rw-r--r--third_party/rust/time/src/format_description/parse/ast.rs278
-rw-r--r--third_party/rust/time/src/format_description/parse/format_item.rs386
-rw-r--r--third_party/rust/time/src/format_description/parse/lexer.rs159
-rw-r--r--third_party/rust/time/src/format_description/parse/mod.rs193
-rw-r--r--third_party/rust/time/src/format_description/well_known/iso8601.rs233
-rw-r--r--third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs249
-rw-r--r--third_party/rust/time/src/format_description/well_known/rfc2822.rs30
-rw-r--r--third_party/rust/time/src/format_description/well_known/rfc3339.rs30
-rw-r--r--third_party/rust/time/src/formatting/formattable.rs304
-rw-r--r--third_party/rust/time/src/formatting/iso8601.rs139
-rw-r--r--third_party/rust/time/src/formatting/mod.rs506
-rw-r--r--third_party/rust/time/src/instant.rs262
-rw-r--r--third_party/rust/time/src/lib.rs357
-rw-r--r--third_party/rust/time/src/macros.rs132
-rw-r--r--third_party/rust/time/src/month.rs164
-rw-r--r--third_party/rust/time/src/offset_date_time.rs1398
-rw-r--r--third_party/rust/time/src/parsing/combinator/mod.rs192
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs173
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/mod.rs10
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs13
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs115
-rw-r--r--third_party/rust/time/src/parsing/component.rs296
-rw-r--r--third_party/rust/time/src/parsing/iso8601.rs308
-rw-r--r--third_party/rust/time/src/parsing/mod.rs50
-rw-r--r--third_party/rust/time/src/parsing/parsable.rs754
-rw-r--r--third_party/rust/time/src/parsing/parsed.rs759
-rw-r--r--third_party/rust/time/src/parsing/shim.rs50
-rw-r--r--third_party/rust/time/src/primitive_date_time.rs937
-rw-r--r--third_party/rust/time/src/quickcheck.rs220
-rw-r--r--third_party/rust/time/src/rand.rs99
-rw-r--r--third_party/rust/time/src/serde/iso8601.rs77
-rw-r--r--third_party/rust/time/src/serde/mod.rs375
-rw-r--r--third_party/rust/time/src/serde/rfc2822.rs72
-rw-r--r--third_party/rust/time/src/serde/rfc3339.rs72
-rw-r--r--third_party/rust/time/src/serde/timestamp.rs60
-rw-r--r--third_party/rust/time/src/serde/visitor.rs316
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/imp.rs8
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/mod.rs23
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/unix.rs169
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/wasm_js.rs12
-rw-r--r--third_party/rust/time/src/sys/local_offset_at/windows.rs114
-rw-r--r--third_party/rust/time/src/sys/mod.rs9
-rw-r--r--third_party/rust/time/src/tests.rs113
-rw-r--r--third_party/rust/time/src/time.rs761
-rw-r--r--third_party/rust/time/src/utc_offset.rs346
-rw-r--r--third_party/rust/time/src/util.rs31
-rw-r--r--third_party/rust/time/src/weekday.rs148
66 files changed, 15394 insertions, 0 deletions
diff --git a/third_party/rust/time/src/date.rs b/third_party/rust/time/src/date.rs
new file mode 100644
index 0000000000..b3023e0c05
--- /dev/null
+++ b/third_party/rust/time/src/date.rs
@@ -0,0 +1,1050 @@
+//! The [`Date`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year};
+use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday};
+
+/// The minimum valid year.
+pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") {
+ -999_999
+} else {
+ -9999
+};
+/// The maximum valid year.
+pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates") {
+ 999_999
+} else {
+ 9999
+};
+
+/// Date in the proleptic Gregorian calendar.
+///
+/// By default, years between ±9999 inclusive are representable. This can be expanded to ±999,999
+/// inclusive by enabling the `large-dates` crate feature. Doing so has performance implications
+/// and introduces some ambiguities when parsing.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Date {
+ /// Bitpacked field containing both the year and ordinal.
+ // | xx | xxxxxxxxxxxxxxxxxxxxx | xxxxxxxxx |
+ // | 2 bits | 21 bits | 9 bits |
+ // | unassigned | year | ordinal |
+ // The year is 15 bits when `large-dates` is not enabled.
+ value: i32,
+}
+
+impl Date {
+ /// The minimum valid `Date`.
+ ///
+ /// The value of this may vary depending on the feature flags enabled.
+ pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1);
+
+ /// The maximum valid `Date`.
+ ///
+ /// The value of this may vary depending on the feature flags enabled.
+ pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR));
+
+ // region: constructors
+ /// Construct a `Date` from the year and ordinal values, the validity of which must be
+ /// guaranteed by the caller.
+ #[doc(hidden)]
+ pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
+ debug_assert!(year >= MIN_YEAR);
+ debug_assert!(year <= MAX_YEAR);
+ debug_assert!(ordinal != 0);
+ debug_assert!(ordinal <= days_in_year(year));
+
+ Self {
+ value: (year << 9) | ordinal as i32,
+ }
+ }
+
+ /// Attempt to create a `Date` from the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::{Date, Month};
+ /// assert!(Date::from_calendar_date(2019, Month::January, 1).is_ok());
+ /// assert!(Date::from_calendar_date(2019, Month::December, 31).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::{Date, Month};
+ /// assert!(Date::from_calendar_date(2019, Month::February, 29).is_err()); // 2019 isn't a leap year.
+ /// ```
+ pub const fn from_calendar_date(
+ year: i32,
+ month: Month,
+ day: u8,
+ ) -> Result<Self, error::ComponentRange> {
+ /// Cumulative days through the beginning of a month in both common and leap years.
+ const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [
+ [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+ [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335],
+ ];
+
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month));
+
+ Ok(Self::__from_ordinal_date_unchecked(
+ year,
+ DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ + day as u16,
+ ))
+ }
+
+ /// Attempt to create a `Date` from the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// assert!(Date::from_ordinal_date(2019, 1).is_ok());
+ /// assert!(Date::from_ordinal_date(2019, 365).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year.
+ /// ```
+ pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year));
+ Ok(Self::__from_ordinal_date_unchecked(year, ordinal))
+ }
+
+ /// Attempt to create a `Date` from the ISO year, week, and weekday.
+ ///
+ /// ```rust
+ /// # use time::{Date, Weekday::*};
+ /// assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok());
+ /// assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok());
+ /// assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::{Date, Weekday::*};
+ /// assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks.
+ /// ```
+ pub const fn from_iso_week_date(
+ year: i32,
+ week: u8,
+ weekday: Weekday,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+ ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year));
+
+ let adj_year = year - 1;
+ let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
+ + div_floor!(adj_year, 400);
+ let jan_4 = match (raw % 7) as i8 {
+ -6 | 1 => 8,
+ -5 | 2 => 9,
+ -4 | 3 => 10,
+ -3 | 4 => 4,
+ -2 | 5 => 5,
+ -1 | 6 => 6,
+ _ => 7,
+ };
+ let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4;
+
+ Ok(if ordinal <= 0 {
+ Self::__from_ordinal_date_unchecked(
+ year - 1,
+ (ordinal as u16).wrapping_add(days_in_year(year - 1)),
+ )
+ } else if ordinal > days_in_year(year) as i16 {
+ Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
+ } else {
+ Self::__from_ordinal_date_unchecked(year, ordinal as _)
+ })
+ }
+
+ /// Create a `Date` from the Julian day.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24)));
+ /// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01)));
+ /// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01)));
+ /// assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31)));
+ /// ```
+ #[doc(alias = "from_julian_date")]
+ pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(
+ julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day()
+ );
+ Ok(Self::from_julian_day_unchecked(julian_day))
+ }
+
+ /// Create a `Date` from the Julian day.
+ ///
+ /// This does not check the validity of the provided Julian day, and as such may result in an
+ /// internally invalid value.
+ #[doc(alias = "from_julian_date_unchecked")]
+ pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self {
+ debug_assert!(julian_day >= Self::MIN.to_julian_day());
+ debug_assert!(julian_day <= Self::MAX.to_julian_day());
+
+ // To avoid a potential overflow, the value may need to be widened for some arithmetic.
+
+ let z = julian_day - 1_721_119;
+ let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 {
+ let g = 100 * z as i64 - 25;
+ let a = (g / 3_652_425) as i32;
+ let b = a - a / 4;
+ let year = div_floor!(100 * b as i64 + g, 36525) as i32;
+ let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _;
+ (year, ordinal)
+ } else {
+ let g = 100 * z - 25;
+ let a = g / 3_652_425;
+ let b = a - a / 4;
+ let year = div_floor!(100 * b + g, 36525);
+ let ordinal = (b + z - div_floor!(36525 * year, 100)) as _;
+ (year, ordinal)
+ };
+
+ if is_leap_year(year) {
+ ordinal += 60;
+ cascade!(ordinal in 1..367 => year);
+ } else {
+ ordinal += 59;
+ cascade!(ordinal in 1..366 => year);
+ }
+
+ Self::__from_ordinal_date_unchecked(year, ordinal)
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the year of the date.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).year(), 2019);
+ /// assert_eq!(date!(2019 - 12 - 31).year(), 2019);
+ /// assert_eq!(date!(2020 - 01 - 01).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.value >> 9
+ }
+
+ /// Get the month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).month(), Month::January);
+ /// assert_eq!(date!(2019 - 12 - 31).month(), Month::December);
+ /// ```
+ pub const fn month(self) -> Month {
+ self.month_day().0
+ }
+
+ /// Get the day of the month.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).day(), 1);
+ /// assert_eq!(date!(2019 - 12 - 31).day(), 31);
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.month_day().1
+ }
+
+ /// Get the month and day. This is more efficient than fetching the components individually.
+ // For whatever reason, rustc has difficulty optimizing this function. It's significantly faster
+ // to write the statements out by hand.
+ pub(crate) const fn month_day(self) -> (Month, u8) {
+ /// The number of days up to and including the given month. Common years
+ /// are first, followed by leap years.
+ const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 11]; 2] = [
+ [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+ [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335],
+ ];
+
+ let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(self.year()) as usize];
+ let ordinal = self.ordinal();
+
+ if ordinal > days[10] {
+ (Month::December, (ordinal - days[10]) as _)
+ } else if ordinal > days[9] {
+ (Month::November, (ordinal - days[9]) as _)
+ } else if ordinal > days[8] {
+ (Month::October, (ordinal - days[8]) as _)
+ } else if ordinal > days[7] {
+ (Month::September, (ordinal - days[7]) as _)
+ } else if ordinal > days[6] {
+ (Month::August, (ordinal - days[6]) as _)
+ } else if ordinal > days[5] {
+ (Month::July, (ordinal - days[5]) as _)
+ } else if ordinal > days[4] {
+ (Month::June, (ordinal - days[4]) as _)
+ } else if ordinal > days[3] {
+ (Month::May, (ordinal - days[3]) as _)
+ } else if ordinal > days[2] {
+ (Month::April, (ordinal - days[2]) as _)
+ } else if ordinal > days[1] {
+ (Month::March, (ordinal - days[1]) as _)
+ } else if ordinal > days[0] {
+ (Month::February, (ordinal - days[0]) as _)
+ } else {
+ (Month::January, ordinal as _)
+ }
+ }
+
+ /// Get the day of the year.
+ ///
+ /// The returned value will always be in the range `1..=366` (`1..=365` for common years).
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1);
+ /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365);
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ (self.value & 0x1FF) as _
+ }
+
+ /// Get the ISO 8601 year and week number.
+ pub(crate) const fn iso_year_week(self) -> (i32, u8) {
+ let (year, ordinal) = self.to_ordinal_date();
+
+ match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ {
+ 0 => (year - 1, weeks_in_year(year - 1)),
+ 53 if weeks_in_year(year) == 52 => (year + 1, 1),
+ week => (year, week),
+ }
+ }
+
+ /// Get the ISO week number.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1);
+ /// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40);
+ /// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1);
+ /// assert_eq!(date!(2020 - 12 - 31).iso_week(), 53);
+ /// assert_eq!(date!(2021 - 01 - 01).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.iso_year_week().1
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52);
+ /// assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0);
+ /// ```
+ pub const fn sunday_based_week(self) -> u8 {
+ ((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as _
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0);
+ /// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52);
+ /// assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0);
+ /// ```
+ pub const fn monday_based_week(self) -> u8 {
+ ((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as _
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 01).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ let (month, day) = self.month_day();
+ (self.year(), month, day)
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1));
+ /// ```
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ (self.year(), self.ordinal())
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday));
+ /// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday));
+ /// assert_eq!(
+ /// date!(2020 - 01 - 01).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday));
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ let (year, ordinal) = self.to_ordinal_date();
+ let weekday = self.weekday();
+
+ match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ {
+ 0 => (year - 1, weeks_in_year(year - 1), weekday),
+ 53 if weeks_in_year(year) == 52 => (year + 1, 1, weekday),
+ week => (year, week, weekday),
+ }
+ }
+
+ /// Get the weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::date;
+ /// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday);
+ /// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 04 - 01).weekday(), Monday);
+ /// assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday);
+ /// assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday);
+ /// assert_eq!(date!(2019 - 07 - 01).weekday(), Monday);
+ /// assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday);
+ /// assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday);
+ /// assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday);
+ /// assert_eq!(date!(2019 - 11 - 01).weekday(), Friday);
+ /// assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ match self.to_julian_day() % 7 {
+ -6 | 1 => Weekday::Tuesday,
+ -5 | 2 => Weekday::Wednesday,
+ -4 | 3 => Weekday::Thursday,
+ -3 | 4 => Weekday::Friday,
+ -2 | 5 => Weekday::Saturday,
+ -1 | 6 => Weekday::Sunday,
+ val => {
+ debug_assert!(val == 0);
+ Weekday::Monday
+ }
+ }
+ }
+
+ /// Get the next calendar date.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 01).next_day(),
+ /// Some(date!(2019 - 01 - 02))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 01 - 31).next_day(),
+ /// Some(date!(2019 - 02 - 01))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 12 - 31).next_day(),
+ /// Some(date!(2020 - 01 - 01))
+ /// );
+ /// assert_eq!(Date::MAX.next_day(), None);
+ /// ```
+ pub const fn next_day(self) -> Option<Self> {
+ if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) {
+ if self.value == Self::MAX.value {
+ None
+ } else {
+ Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1))
+ }
+ } else {
+ Some(Self {
+ value: self.value + 1,
+ })
+ }
+ }
+
+ /// Get the previous calendar date.
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2019 - 01 - 02).previous_day(),
+ /// Some(date!(2019 - 01 - 01))
+ /// );
+ /// assert_eq!(
+ /// date!(2019 - 02 - 01).previous_day(),
+ /// Some(date!(2019 - 01 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 01 - 01).previous_day(),
+ /// Some(date!(2019 - 12 - 31))
+ /// );
+ /// assert_eq!(Date::MIN.previous_day(), None);
+ /// ```
+ pub const fn previous_day(self) -> Option<Self> {
+ if self.ordinal() != 1 {
+ Some(Self {
+ value: self.value - 1,
+ })
+ } else if self.value == Self::MIN.value {
+ None
+ } else {
+ Some(Self::__from_ordinal_date_unchecked(
+ self.year() - 1,
+ days_in_year(self.year() - 1),
+ ))
+ }
+ }
+
+ /// Get the Julian day for the date.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0);
+ /// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545);
+ /// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485);
+ /// assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849);
+ /// ```
+ pub const fn to_julian_day(self) -> i32 {
+ let year = self.year() - 1;
+ let ordinal = self.ordinal() as i32;
+
+ ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100)
+ + div_floor!(year, 400)
+ + 1_721_425
+ }
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_add(1.days()), None);
+ /// assert_eq!(Date::MIN.checked_add((-2).days()), None);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(2.days()),
+ /// Some(date!(2021 - 01 - 02))
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX));
+ /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN));
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(23.hours()),
+ /// Some(date!(2020 - 12 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_add(47.hours()),
+ /// Some(date!(2021 - 01 - 01))
+ /// );
+ /// ```
+ pub const fn checked_add(self, duration: Duration) -> Option<Self> {
+ let whole_days = duration.whole_days();
+ if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 {
+ return None;
+ }
+
+ let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _));
+ if let Ok(date) = Self::from_julian_day(julian_day) {
+ Some(date)
+ } else {
+ None
+ }
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_sub((-2).days()), None);
+ /// assert_eq!(Date::MIN.checked_sub(1.days()), None);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(2.days()),
+ /// Some(date!(2020 - 12 - 29))
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX));
+ /// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN));
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(23.hours()),
+ /// Some(date!(2020 - 12 - 31))
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).checked_sub(47.hours()),
+ /// Some(date!(2020 - 12 - 30))
+ /// );
+ /// ```
+ pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
+ let whole_days = duration.whole_days();
+ if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 {
+ return None;
+ }
+
+ let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _));
+ if let Ok(date) = Self::from_julian_day(julian_day) {
+ Some(date)
+ } else {
+ None
+ }
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```rust
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX);
+ /// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(2.days()),
+ /// date!(2021 - 01 - 02)
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(23.hours()),
+ /// date!(2020 - 12 - 31)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_add(47.hours()),
+ /// date!(2021 - 01 - 01)
+ /// );
+ /// ```
+ 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 {
+ debug_assert!(duration.is_positive());
+ Self::MAX
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::date;
+ /// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX);
+ /// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN);
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(2.days()),
+ /// date!(2020 - 12 - 29)
+ /// );
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This function only takes whole days into account.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(23.hours()),
+ /// date!(2020 - 12 - 31)
+ /// );
+ /// assert_eq!(
+ /// date!(2020 - 12 - 31).saturating_sub(47.hours()),
+ /// date!(2020 - 12 - 30)
+ /// );
+ /// ```
+ 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 {
+ debug_assert!(duration.is_positive());
+ Self::MIN
+ }
+ }
+ // region: saturating arithmetic
+
+ // region: replacement
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_year(2019),
+ /// Ok(date!(2019 - 02 - 18))
+ /// );
+ /// assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year
+ /// assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR);
+
+ let ordinal = self.ordinal();
+
+ // Dates in January and February are unaffected by leap years.
+ if ordinal <= 59 {
+ return Ok(Self::__from_ordinal_date_unchecked(year, ordinal));
+ }
+
+ match (is_leap_year(self.year()), is_leap_year(year)) {
+ (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)),
+ // February 29 does not exist in common years.
+ (true, false) if ordinal == 60 => Err(error::ComponentRange {
+ name: "day",
+ value: 29,
+ minimum: 1,
+ maximum: 28,
+ conditional_range: true,
+ }),
+ // We're going from a common year to a leap year. Shift dates in March and later by
+ // one day.
+ (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)),
+ // We're going from a leap year to a common year. Shift dates in January and
+ // February by one day.
+ (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)),
+ }
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_month(Month::January),
+ /// Ok(date!(2022 - 01 - 18))
+ /// );
+ /// assert!(
+ /// date!(2022 - 01 - 30)
+ /// .replace_month(Month::February)
+ /// .is_err()
+ /// ); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> {
+ let (year, _, day) = self.to_calendar_date();
+ Self::from_calendar_date(year, month, day)
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert_eq!(
+ /// date!(2022 - 02 - 18).replace_day(1),
+ /// Ok(date!(2022 - 02 - 01))
+ /// );
+ /// assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day
+ /// assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[must_use = "This method does not mutate the original `Date`."]
+ pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> {
+ // Days 1-28 are present in every month, so we can skip checking.
+ if day == 0 || day >= 29 {
+ ensure_value_in_range!(
+ day conditionally in 1 => days_in_year_month(self.year(), self.month())
+ );
+ }
+
+ Ok(Self::__from_ordinal_date_unchecked(
+ self.year(),
+ (self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
+ ))
+ }
+ // endregion replacement
+}
+
+// region: attach time
+/// Methods to add a [`Time`] component, resulting in a [`PrimitiveDateTime`].
+impl Date {
+ /// Create a [`PrimitiveDateTime`] using the existing date. The [`Time`] component will be set
+ /// to midnight.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime};
+ /// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00));
+ /// ```
+ pub const fn midnight(self) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(self, Time::MIDNIGHT)
+ }
+
+ /// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`].
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime, time};
+ /// assert_eq!(
+ /// date!(1970-01-01).with_time(time!(0:00)),
+ /// datetime!(1970-01-01 0:00),
+ /// );
+ /// ```
+ pub const fn with_time(self, time: Time) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(self, time)
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms(hour, minute, second)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_milli(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ millisecond: u16,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_milli(hour, minute, second, millisecond)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_micro(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ microsecond: u32,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_micro(hour, minute, second, microsecond)),
+ ))
+ }
+
+ /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
+ ///
+ /// ```rust
+ /// # use time_macros::date;
+ /// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok());
+ /// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err());
+ /// ```
+ pub const fn with_hms_nano(
+ self,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Result<PrimitiveDateTime, error::ComponentRange> {
+ Ok(PrimitiveDateTime::new(
+ self,
+ const_try!(Time::from_hms_nano(hour, minute, second, nanosecond)),
+ ))
+ }
+}
+// endregion attach time
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl Date {
+ /// Format the `Date` using the provided [format description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, error::Format> {
+ format.format_into(output, Some(self), None, None)
+ }
+
+ /// Format the `Date` using the provided [format description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::{format_description};
+ /// # use time_macros::date;
+ /// let format = format_description::parse("[year]-[month]-[day]")?;
+ /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02");
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(Some(self), None, None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl Date {
+ /// Parse a `Date` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::Date;
+ /// # use time_macros::{date, format_description};
+ /// let format = format_description!("[year]-[month]-[day]");
+ /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_date(input.as_bytes())
+ }
+}
+
+impl fmt::Display for Date {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 {
+ write!(
+ f,
+ "{:+}-{:02}-{:02}",
+ self.year(),
+ self.month() as u8,
+ self.day()
+ )
+ } else {
+ write!(
+ f,
+ "{:0width$}-{:02}-{:02}",
+ self.year(),
+ self.month() as u8,
+ self.day(),
+ width = 4 + (self.year() < 0) as usize
+ )
+ }
+ }
+}
+
+impl fmt::Debug for Date {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl Add<Duration> for Date {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ self.checked_add(duration)
+ .expect("overflow adding duration to date")
+ }
+}
+
+impl Add<StdDuration> for Date {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ Self::from_julian_day(self.to_julian_day() + (duration.as_secs() / 86_400) as i32)
+ .expect("overflow adding duration to date")
+ }
+}
+
+impl_add_assign!(Date: Duration, StdDuration);
+
+impl Sub<Duration> for Date {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ self.checked_sub(duration)
+ .expect("overflow subtracting duration from date")
+ }
+}
+
+impl Sub<StdDuration> for Date {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ Self::from_julian_day(self.to_julian_day() - (duration.as_secs() / 86_400) as i32)
+ .expect("overflow subtracting duration from date")
+ }
+}
+
+impl_sub_assign!(Date: Duration, StdDuration);
+
+impl Sub for Date {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Self::Output {
+ Duration::days((self.to_julian_day() - other.to_julian_day()) as _)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/duration.rs b/third_party/rust/time/src/duration.rs
new file mode 100644
index 0000000000..f8d916f451
--- /dev/null
+++ b/third_party/rust/time/src/duration.rs
@@ -0,0 +1,1139 @@
+//! The [`Duration`] struct and its associated `impl`s.
+
+use core::cmp::Ordering;
+use core::fmt;
+use core::iter::Sum;
+use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
+use core::time::Duration as StdDuration;
+
+use crate::error;
+#[cfg(feature = "std")]
+use crate::Instant;
+
+/// By explicitly inserting this enum where padding is expected, the compiler is able to better
+/// perform niche value optimization.
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub(crate) enum Padding {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Optimize,
+}
+
+impl Default for Padding {
+ fn default() -> Self {
+ Self::Optimize
+ }
+}
+
+/// A span of time with nanosecond precision.
+///
+/// Each `Duration` is composed of a whole number of seconds and a fractional part represented in
+/// nanoseconds.
+///
+/// This implementation allows for negative durations, unlike [`core::time::Duration`].
+#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Duration {
+ /// Number of whole seconds.
+ seconds: i64,
+ /// Number of nanoseconds within the second. The sign always matches the `seconds` field.
+ nanoseconds: i32, // always -10^9 < nanoseconds < 10^9
+ #[allow(clippy::missing_docs_in_private_items)]
+ padding: Padding,
+}
+
+impl fmt::Debug for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Duration")
+ .field("seconds", &self.seconds)
+ .field("nanoseconds", &self.nanoseconds)
+ .finish()
+ }
+}
+
+impl Duration {
+ // region: constants
+ /// Equivalent to `0.seconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::ZERO, 0.seconds());
+ /// ```
+ pub const ZERO: Self = Self::seconds(0);
+
+ /// Equivalent to `1.nanoseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::NANOSECOND, 1.nanoseconds());
+ /// ```
+ pub const NANOSECOND: Self = Self::nanoseconds(1);
+
+ /// Equivalent to `1.microseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MICROSECOND, 1.microseconds());
+ /// ```
+ pub const MICROSECOND: Self = Self::microseconds(1);
+
+ /// Equivalent to `1.milliseconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MILLISECOND, 1.milliseconds());
+ /// ```
+ pub const MILLISECOND: Self = Self::milliseconds(1);
+
+ /// Equivalent to `1.seconds()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::SECOND, 1.seconds());
+ /// ```
+ pub const SECOND: Self = Self::seconds(1);
+
+ /// Equivalent to `1.minutes()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::MINUTE, 1.minutes());
+ /// ```
+ pub const MINUTE: Self = Self::minutes(1);
+
+ /// Equivalent to `1.hours()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::HOUR, 1.hours());
+ /// ```
+ pub const HOUR: Self = Self::hours(1);
+
+ /// Equivalent to `1.days()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::DAY, 1.days());
+ /// ```
+ pub const DAY: Self = Self::days(1);
+
+ /// Equivalent to `1.weeks()`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::WEEK, 1.weeks());
+ /// ```
+ pub const WEEK: Self = Self::weeks(1);
+
+ /// The minimum possible duration. Adding any negative duration to this will cause an overflow.
+ pub const MIN: Self = Self::new_unchecked(i64::MIN, -999_999_999);
+
+ /// The maximum possible duration. Adding any positive duration to this will cause an overflow.
+ pub const MAX: Self = Self::new_unchecked(i64::MAX, 999_999_999);
+ // endregion constants
+
+ // region: is_{sign}
+ /// Check if a duration is exactly zero.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!(0.seconds().is_zero());
+ /// assert!(!1.nanoseconds().is_zero());
+ /// ```
+ pub const fn is_zero(self) -> bool {
+ self.seconds == 0 && self.nanoseconds == 0
+ }
+
+ /// Check if a duration is negative.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!((-1).seconds().is_negative());
+ /// assert!(!0.seconds().is_negative());
+ /// assert!(!1.seconds().is_negative());
+ /// ```
+ pub const fn is_negative(self) -> bool {
+ self.seconds < 0 || self.nanoseconds < 0
+ }
+
+ /// Check if a duration is positive.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert!(1.seconds().is_positive());
+ /// assert!(!0.seconds().is_positive());
+ /// assert!(!(-1).seconds().is_positive());
+ /// ```
+ pub const fn is_positive(self) -> bool {
+ self.seconds > 0 || self.nanoseconds > 0
+ }
+ // endregion is_{sign}
+
+ // region: abs
+ /// Get the absolute value of the duration.
+ ///
+ /// This method saturates the returned value if it would otherwise overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().abs(), 1.seconds());
+ /// assert_eq!(0.seconds().abs(), 0.seconds());
+ /// assert_eq!((-1).seconds().abs(), 1.seconds());
+ /// ```
+ pub const fn abs(self) -> Self {
+ Self::new_unchecked(self.seconds.saturating_abs(), self.nanoseconds.abs())
+ }
+
+ /// Convert the existing `Duration` to a `std::time::Duration` and its sign. This returns a
+ /// [`std::time::Duration`] and does not saturate the returned value (unlike [`Duration::abs`]).
+ ///
+ /// ```rust
+ /// # use time::ext::{NumericalDuration, NumericalStdDuration};
+ /// assert_eq!(1.seconds().unsigned_abs(), 1.std_seconds());
+ /// assert_eq!(0.seconds().unsigned_abs(), 0.std_seconds());
+ /// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds());
+ /// ```
+ pub const fn unsigned_abs(self) -> StdDuration {
+ StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs())
+ }
+ // endregion abs
+
+ // region: constructors
+ /// Create a new `Duration` without checking the validity of the components.
+ pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
+ if seconds < 0 {
+ debug_assert!(nanoseconds <= 0);
+ debug_assert!(nanoseconds > -1_000_000_000);
+ } else if seconds > 0 {
+ debug_assert!(nanoseconds >= 0);
+ debug_assert!(nanoseconds < 1_000_000_000);
+ } else {
+ debug_assert!(nanoseconds.unsigned_abs() < 1_000_000_000);
+ }
+
+ Self {
+ seconds,
+ nanoseconds,
+ padding: Padding::Optimize,
+ }
+ }
+
+ /// Create a new `Duration` with the provided seconds and nanoseconds. If nanoseconds is at
+ /// least ±10<sup>9</sup>, it will wrap to the number of seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::new(1, 0), 1.seconds());
+ /// assert_eq!(Duration::new(-1, 0), (-1).seconds());
+ /// assert_eq!(Duration::new(1, 2_000_000_000), 3.seconds());
+ /// ```
+ pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
+ seconds = expect_opt!(
+ seconds.checked_add(nanoseconds as i64 / 1_000_000_000),
+ "overflow constructing `time::Duration`"
+ );
+ nanoseconds %= 1_000_000_000;
+
+ if seconds > 0 && nanoseconds < 0 {
+ // `seconds` cannot overflow here because it is positive.
+ seconds -= 1;
+ nanoseconds += 1_000_000_000;
+ } else if seconds < 0 && nanoseconds > 0 {
+ // `seconds` cannot overflow here because it is negative.
+ seconds += 1;
+ nanoseconds -= 1_000_000_000;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Create a new `Duration` with the given number of weeks. Equivalent to
+ /// `Duration::seconds(weeks * 604_800)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::weeks(1), 604_800.seconds());
+ /// ```
+ pub const fn weeks(weeks: i64) -> Self {
+ Self::seconds(expect_opt!(
+ weeks.checked_mul(604_800),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of days. Equivalent to
+ /// `Duration::seconds(days * 86_400)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::days(1), 86_400.seconds());
+ /// ```
+ pub const fn days(days: i64) -> Self {
+ Self::seconds(expect_opt!(
+ days.checked_mul(86_400),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of hours. Equivalent to
+ /// `Duration::seconds(hours * 3_600)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::hours(1), 3_600.seconds());
+ /// ```
+ pub const fn hours(hours: i64) -> Self {
+ Self::seconds(expect_opt!(
+ hours.checked_mul(3_600),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of minutes. Equivalent to
+ /// `Duration::seconds(minutes * 60)`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::minutes(1), 60.seconds());
+ /// ```
+ pub const fn minutes(minutes: i64) -> Self {
+ Self::seconds(expect_opt!(
+ minutes.checked_mul(60),
+ "overflow constructing `time::Duration`"
+ ))
+ }
+
+ /// Create a new `Duration` with the given number of seconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds(1), 1_000.milliseconds());
+ /// ```
+ pub const fn seconds(seconds: i64) -> Self {
+ Self::new_unchecked(seconds, 0)
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds represented as `f64`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds_f64(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds());
+ /// ```
+ pub fn seconds_f64(seconds: f64) -> Self {
+ if seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
+ crate::expect_failed("overflow constructing `time::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 _)
+ }
+
+ /// Creates a new `Duration` from the specified number of seconds represented as `f32`.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::seconds_f32(0.5), 0.5.seconds());
+ /// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds());
+ /// ```
+ pub fn seconds_f32(seconds: f32) -> Self {
+ if seconds > i64::MAX as f32 || seconds < i64::MIN as f32 {
+ crate::expect_failed("overflow constructing `time::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 _)
+ }
+
+ /// Create a new `Duration` with the given number of milliseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::milliseconds(1), 1_000.microseconds());
+ /// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds());
+ /// ```
+ pub const fn milliseconds(milliseconds: i64) -> Self {
+ Self::new_unchecked(
+ milliseconds / 1_000,
+ ((milliseconds % 1_000) * 1_000_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of microseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::microseconds(1), 1_000.nanoseconds());
+ /// assert_eq!(Duration::microseconds(-1), (-1_000).nanoseconds());
+ /// ```
+ pub const fn microseconds(microseconds: i64) -> Self {
+ Self::new_unchecked(
+ microseconds / 1_000_000,
+ ((microseconds % 1_000_000) * 1_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of nanoseconds.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(Duration::nanoseconds(1), 1.microseconds() / 1_000);
+ /// assert_eq!(Duration::nanoseconds(-1), (-1).microseconds() / 1_000);
+ /// ```
+ pub const fn nanoseconds(nanoseconds: i64) -> Self {
+ Self::new_unchecked(
+ nanoseconds / 1_000_000_000,
+ (nanoseconds % 1_000_000_000) as _,
+ )
+ }
+
+ /// Create a new `Duration` with the given number of nanoseconds.
+ ///
+ /// As the input range cannot be fully mapped to the output, this should only be used where it's
+ /// known to result in a valid value.
+ pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self {
+ let seconds = nanoseconds / 1_000_000_000;
+ let nanoseconds = nanoseconds % 1_000_000_000;
+
+ if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 {
+ crate::expect_failed("overflow constructing `time::Duration`");
+ }
+
+ Self::new_unchecked(seconds as _, nanoseconds as _)
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the number of whole weeks in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.weeks().whole_weeks(), 1);
+ /// assert_eq!((-1).weeks().whole_weeks(), -1);
+ /// assert_eq!(6.days().whole_weeks(), 0);
+ /// assert_eq!((-6).days().whole_weeks(), 0);
+ /// ```
+ pub const fn whole_weeks(self) -> i64 {
+ self.whole_seconds() / 604_800
+ }
+
+ /// Get the number of whole days in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.days().whole_days(), 1);
+ /// assert_eq!((-1).days().whole_days(), -1);
+ /// assert_eq!(23.hours().whole_days(), 0);
+ /// assert_eq!((-23).hours().whole_days(), 0);
+ /// ```
+ pub const fn whole_days(self) -> i64 {
+ self.whole_seconds() / 86_400
+ }
+
+ /// Get the number of whole hours in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.hours().whole_hours(), 1);
+ /// assert_eq!((-1).hours().whole_hours(), -1);
+ /// assert_eq!(59.minutes().whole_hours(), 0);
+ /// assert_eq!((-59).minutes().whole_hours(), 0);
+ /// ```
+ pub const fn whole_hours(self) -> i64 {
+ self.whole_seconds() / 3_600
+ }
+
+ /// Get the number of whole minutes in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.minutes().whole_minutes(), 1);
+ /// assert_eq!((-1).minutes().whole_minutes(), -1);
+ /// assert_eq!(59.seconds().whole_minutes(), 0);
+ /// assert_eq!((-59).seconds().whole_minutes(), 0);
+ /// ```
+ pub const fn whole_minutes(self) -> i64 {
+ self.whole_seconds() / 60
+ }
+
+ /// Get the number of whole seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().whole_seconds(), 1);
+ /// assert_eq!((-1).seconds().whole_seconds(), -1);
+ /// assert_eq!(1.minutes().whole_seconds(), 60);
+ /// assert_eq!((-1).minutes().whole_seconds(), -60);
+ /// ```
+ pub const fn whole_seconds(self) -> i64 {
+ self.seconds
+ }
+
+ /// Get the number of fractional seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.5.seconds().as_seconds_f64(), 1.5);
+ /// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5);
+ /// ```
+ pub fn as_seconds_f64(self) -> f64 {
+ self.seconds as f64 + self.nanoseconds as f64 / 1_000_000_000.
+ }
+
+ /// Get the number of fractional seconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.5.seconds().as_seconds_f32(), 1.5);
+ /// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5);
+ /// ```
+ pub fn as_seconds_f32(self) -> f32 {
+ self.seconds as f32 + self.nanoseconds as f32 / 1_000_000_000.
+ }
+
+ /// Get the number of whole milliseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.seconds().whole_milliseconds(), 1_000);
+ /// assert_eq!((-1).seconds().whole_milliseconds(), -1_000);
+ /// assert_eq!(1.milliseconds().whole_milliseconds(), 1);
+ /// assert_eq!((-1).milliseconds().whole_milliseconds(), -1);
+ /// ```
+ pub const fn whole_milliseconds(self) -> i128 {
+ self.seconds as i128 * 1_000 + self.nanoseconds as i128 / 1_000_000
+ }
+
+ /// Get the number of milliseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000..1_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.4.seconds().subsec_milliseconds(), 400);
+ /// assert_eq!((-1.4).seconds().subsec_milliseconds(), -400);
+ /// ```
+ // Allow the lint, as the value is guaranteed to be less than 1000.
+ pub const fn subsec_milliseconds(self) -> i16 {
+ (self.nanoseconds / 1_000_000) as _
+ }
+
+ /// Get the number of whole microseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.milliseconds().whole_microseconds(), 1_000);
+ /// assert_eq!((-1).milliseconds().whole_microseconds(), -1_000);
+ /// assert_eq!(1.microseconds().whole_microseconds(), 1);
+ /// assert_eq!((-1).microseconds().whole_microseconds(), -1);
+ /// ```
+ pub const fn whole_microseconds(self) -> i128 {
+ self.seconds as i128 * 1_000_000 + self.nanoseconds as i128 / 1_000
+ }
+
+ /// Get the number of microseconds past the number of whole seconds.
+ ///
+ /// Always in the range `-1_000_000..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.0004.seconds().subsec_microseconds(), 400);
+ /// assert_eq!((-1.0004).seconds().subsec_microseconds(), -400);
+ /// ```
+ pub const fn subsec_microseconds(self) -> i32 {
+ self.nanoseconds / 1_000
+ }
+
+ /// Get the number of nanoseconds in the duration.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.microseconds().whole_nanoseconds(), 1_000);
+ /// assert_eq!((-1).microseconds().whole_nanoseconds(), -1_000);
+ /// assert_eq!(1.nanoseconds().whole_nanoseconds(), 1);
+ /// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1);
+ /// ```
+ pub const fn whole_nanoseconds(self) -> i128 {
+ self.seconds as i128 * 1_000_000_000 + self.nanoseconds as i128
+ }
+
+ /// Get the number of nanoseconds past the number of whole seconds.
+ ///
+ /// The returned value will always be in the range `-1_000_000_000..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(1.000_000_400.seconds().subsec_nanoseconds(), 400);
+ /// assert_eq!((-1.000_000_400).seconds().subsec_nanoseconds(), -400);
+ /// ```
+ pub const fn subsec_nanoseconds(self) -> i32 {
+ self.nanoseconds
+ }
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_add(5.seconds()), Some(10.seconds()));
+ /// assert_eq!(Duration::MAX.checked_add(1.nanoseconds()), None);
+ /// assert_eq!((-5).seconds().checked_add(5.seconds()), Some(0.seconds()));
+ /// ```
+ pub const fn checked_add(self, rhs: Self) -> Option<Self> {
+ let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds));
+ let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_sub(1));
+ }
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self - rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_sub(5.seconds()), Some(Duration::ZERO));
+ /// assert_eq!(Duration::MIN.checked_sub(1.nanoseconds()), None);
+ /// assert_eq!(5.seconds().checked_sub(10.seconds()), Some((-5).seconds()));
+ /// ```
+ pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
+ let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds));
+ let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_add(1));
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = const_try_opt!(seconds.checked_sub(1));
+ }
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self * rhs`, returning `None` if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().checked_mul(2), Some(10.seconds()));
+ /// assert_eq!(5.seconds().checked_mul(-2), Some((-10).seconds()));
+ /// assert_eq!(5.seconds().checked_mul(0), Some(0.seconds()));
+ /// assert_eq!(Duration::MAX.checked_mul(2), None);
+ /// assert_eq!(Duration::MIN.checked_mul(2), None);
+ /// ```
+ pub const fn checked_mul(self, rhs: i32) -> Option<Self> {
+ // Multiply nanoseconds as i64, because it cannot overflow that way.
+ let total_nanos = self.nanoseconds as i64 * rhs as i64;
+ let extra_secs = total_nanos / 1_000_000_000;
+ let nanoseconds = (total_nanos % 1_000_000_000) as _;
+ let seconds = const_try_opt!(
+ const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs)
+ );
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+
+ /// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// assert_eq!(10.seconds().checked_div(2), Some(5.seconds()));
+ /// assert_eq!(10.seconds().checked_div(-2), Some((-5).seconds()));
+ /// assert_eq!(1.seconds().checked_div(0), None);
+ /// ```
+ pub const fn checked_div(self, rhs: i32) -> Option<Self> {
+ let seconds = const_try_opt!(self.seconds.checked_div(rhs as i64));
+ let carry = self.seconds - seconds * (rhs as i64);
+ let extra_nanos = const_try_opt!((carry * 1_000_000_000).checked_div(rhs as i64));
+ let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32);
+
+ Some(Self::new_unchecked(seconds, nanoseconds))
+ }
+ // endregion checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_add(5.seconds()), 10.seconds());
+ /// assert_eq!(Duration::MAX.saturating_add(1.nanoseconds()), Duration::MAX);
+ /// assert_eq!(
+ /// Duration::MIN.saturating_add((-1).nanoseconds()),
+ /// Duration::MIN
+ /// );
+ /// assert_eq!((-5).seconds().saturating_add(5.seconds()), Duration::ZERO);
+ /// ```
+ pub const fn saturating_add(self, rhs: Self) -> Self {
+ let (mut seconds, overflow) = self.seconds.overflowing_add(rhs.seconds);
+ if overflow {
+ if self.seconds > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = match seconds.checked_sub(1) {
+ Some(seconds) => seconds,
+ None => return Self::MIN,
+ };
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Computes `self - rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_sub(5.seconds()), Duration::ZERO);
+ /// assert_eq!(Duration::MIN.saturating_sub(1.nanoseconds()), Duration::MIN);
+ /// assert_eq!(
+ /// Duration::MAX.saturating_sub((-1).nanoseconds()),
+ /// Duration::MAX
+ /// );
+ /// assert_eq!(5.seconds().saturating_sub(10.seconds()), (-5).seconds());
+ /// ```
+ pub const fn saturating_sub(self, rhs: Self) -> Self {
+ let (mut seconds, overflow) = self.seconds.overflowing_sub(rhs.seconds);
+ if overflow {
+ if self.seconds > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
+
+ if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
+ nanoseconds -= 1_000_000_000;
+ seconds = match seconds.checked_add(1) {
+ Some(seconds) => seconds,
+ None => return Self::MAX,
+ };
+ } else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
+ nanoseconds += 1_000_000_000;
+ seconds = match seconds.checked_sub(1) {
+ Some(seconds) => seconds,
+ None => return Self::MIN,
+ };
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+
+ /// Computes `self * rhs`, saturating if an overflow occurred.
+ ///
+ /// ```rust
+ /// # use time::{Duration, ext::NumericalDuration};
+ /// assert_eq!(5.seconds().saturating_mul(2), 10.seconds());
+ /// assert_eq!(5.seconds().saturating_mul(-2), (-10).seconds());
+ /// assert_eq!(5.seconds().saturating_mul(0), Duration::ZERO);
+ /// assert_eq!(Duration::MAX.saturating_mul(2), Duration::MAX);
+ /// assert_eq!(Duration::MIN.saturating_mul(2), Duration::MIN);
+ /// assert_eq!(Duration::MAX.saturating_mul(-2), Duration::MIN);
+ /// assert_eq!(Duration::MIN.saturating_mul(-2), Duration::MAX);
+ /// ```
+ pub const fn saturating_mul(self, rhs: i32) -> Self {
+ // Multiply nanoseconds as i64, because it cannot overflow that way.
+ let total_nanos = self.nanoseconds as i64 * rhs as i64;
+ let extra_secs = total_nanos / 1_000_000_000;
+ let nanoseconds = (total_nanos % 1_000_000_000) as _;
+ let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _);
+ if overflow1 {
+ if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+ let (seconds, overflow2) = seconds.overflowing_add(extra_secs);
+ if overflow2 {
+ if self.seconds > 0 && rhs > 0 {
+ return Self::MAX;
+ }
+ return Self::MIN;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }
+ // endregion saturating arithmetic
+
+ /// Runs a closure, returning the duration of time it took to run. The return value of the
+ /// closure is provided in the second part of the tuple.
+ #[cfg(feature = "std")]
+ pub fn time_fn<T>(f: impl FnOnce() -> T) -> (Self, T) {
+ let start = Instant::now();
+ let return_value = f();
+ let end = Instant::now();
+
+ (end - start, return_value)
+ }
+}
+
+// region: trait impls
+/// The format returned by this implementation is not stable and must not be relied upon.
+///
+/// By default this produces an exact, full-precision printout of the duration.
+/// For a concise, rounded printout instead, you can use the `.N` format specifier:
+///
+/// ```
+/// # use time::Duration;
+/// #
+/// let duration = Duration::new(123456, 789011223);
+/// println!("{duration:.3}");
+/// ```
+///
+/// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60
+/// seconds.
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.is_negative() {
+ f.write_str("-")?;
+ }
+
+ if let Some(_precision) = f.precision() {
+ // Concise, rounded representation.
+
+ if self.is_zero() {
+ // Write a zero value with the requested precision.
+ return (0.).fmt(f).and_then(|_| f.write_str("s"));
+ }
+
+ /// Format the first item that produces a value greater than 1 and then break.
+ macro_rules! item {
+ ($name:literal, $value:expr) => {
+ let value = $value;
+ if value >= 1.0 {
+ return value.fmt(f).and_then(|_| f.write_str($name));
+ }
+ };
+ }
+
+ // Even if this produces a de-normal float, because we're rounding we don't really care.
+ let seconds = self.unsigned_abs().as_secs_f64();
+
+ item!("d", seconds / 86_400.);
+ item!("h", seconds / 3_600.);
+ item!("m", seconds / 60.);
+ item!("s", seconds);
+ item!("ms", seconds * 1_000.);
+ item!("µs", seconds * 1_000_000.);
+ item!("ns", seconds * 1_000_000_000.);
+ } else {
+ // Precise, but verbose representation.
+
+ if self.is_zero() {
+ return f.write_str("0s");
+ }
+
+ /// Format a single item.
+ macro_rules! item {
+ ($name:literal, $value:expr) => {
+ match $value {
+ 0 => Ok(()),
+ value => value.fmt(f).and_then(|_| f.write_str($name)),
+ }
+ };
+ }
+
+ let seconds = self.seconds.unsigned_abs();
+ let nanoseconds = self.nanoseconds.unsigned_abs();
+
+ item!("d", seconds / 86_400)?;
+ item!("h", seconds / 3_600 % 24)?;
+ item!("m", seconds / 60 % 60)?;
+ item!("s", seconds % 60)?;
+ item!("ms", nanoseconds / 1_000_000)?;
+ item!("µs", nanoseconds / 1_000 % 1_000)?;
+ item!("ns", nanoseconds % 1_000)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl TryFrom<StdDuration> for Duration {
+ type Error = error::ConversionRange;
+
+ fn try_from(original: StdDuration) -> Result<Self, error::ConversionRange> {
+ Ok(Self::new(
+ original
+ .as_secs()
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ original.subsec_nanos() as _,
+ ))
+ }
+}
+
+impl TryFrom<Duration> for StdDuration {
+ type Error = error::ConversionRange;
+
+ fn try_from(duration: Duration) -> Result<Self, error::ConversionRange> {
+ Ok(Self::new(
+ duration
+ .seconds
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ duration
+ .nanoseconds
+ .try_into()
+ .map_err(|_| error::ConversionRange)?,
+ ))
+ }
+}
+
+impl Add for Duration {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ self.checked_add(rhs)
+ .expect("overflow when adding durations")
+ }
+}
+
+impl Add<StdDuration> for Duration {
+ type Output = Self;
+
+ fn add(self, std_duration: StdDuration) -> Self::Output {
+ self + Self::try_from(std_duration)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ }
+}
+
+impl Add<Duration> for StdDuration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ rhs + self
+ }
+}
+
+impl_add_assign!(Duration: Self, StdDuration);
+
+impl AddAssign<Duration> for StdDuration {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = (*self + rhs).try_into().expect(
+ "Cannot represent a resulting duration in std. Try `let x = x + rhs;`, which will \
+ change the type.",
+ );
+ }
+}
+
+impl Neg for Duration {
+ type Output = Self;
+
+ fn neg(self) -> Self::Output {
+ Self::new_unchecked(-self.seconds, -self.nanoseconds)
+ }
+}
+
+impl Sub for Duration {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ self.checked_sub(rhs)
+ .expect("overflow when subtracting durations")
+ }
+}
+
+impl Sub<StdDuration> for Duration {
+ type Output = Self;
+
+ fn sub(self, rhs: StdDuration) -> Self::Output {
+ self - Self::try_from(rhs)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ }
+}
+
+impl Sub<Duration> for StdDuration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ Duration::try_from(self)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`")
+ - rhs
+ }
+}
+
+impl_sub_assign!(Duration: Self, StdDuration);
+
+impl SubAssign<Duration> for StdDuration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = (*self - rhs).try_into().expect(
+ "Cannot represent a resulting duration in std. Try `let x = x - rhs;`, which will \
+ change the type.",
+ );
+ }
+}
+
+/// Implement `Mul` (reflexively) and `Div` for `Duration` for various types.
+macro_rules! duration_mul_div_int {
+ ($($type:ty),+) => {$(
+ impl Mul<$type> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: $type) -> Self::Output {
+ Self::nanoseconds_i128(
+ self.whole_nanoseconds()
+ .checked_mul(rhs as _)
+ .expect("overflow when multiplying duration")
+ )
+ }
+ }
+
+ impl Mul<Duration> for $type {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+ }
+
+ impl Div<$type> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: $type) -> Self::Output {
+ Self::nanoseconds_i128(self.whole_nanoseconds() / rhs as i128)
+ }
+ }
+ )+};
+}
+duration_mul_div_int![i8, i16, i32, u8, u16, u32];
+
+impl Mul<f32> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: f32) -> Self::Output {
+ Self::seconds_f32(self.as_seconds_f32() * rhs)
+ }
+}
+
+impl Mul<Duration> for f32 {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+}
+
+impl Mul<f64> for Duration {
+ type Output = Self;
+
+ fn mul(self, rhs: f64) -> Self::Output {
+ Self::seconds_f64(self.as_seconds_f64() * rhs)
+ }
+}
+
+impl Mul<Duration> for f64 {
+ type Output = Duration;
+
+ fn mul(self, rhs: Duration) -> Self::Output {
+ rhs * self
+ }
+}
+
+impl_mul_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64);
+
+impl Div<f32> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: f32) -> Self::Output {
+ Self::seconds_f32(self.as_seconds_f32() / rhs)
+ }
+}
+
+impl Div<f64> for Duration {
+ type Output = Self;
+
+ fn div(self, rhs: f64) -> Self::Output {
+ Self::seconds_f64(self.as_seconds_f64() / rhs)
+ }
+}
+
+impl_div_assign!(Duration: i8, i16, i32, u8, u16, u32, f32, f64);
+
+impl Div for Duration {
+ type Output = f64;
+
+ fn div(self, rhs: Self) -> Self::Output {
+ self.as_seconds_f64() / rhs.as_seconds_f64()
+ }
+}
+
+impl Div<StdDuration> for Duration {
+ type Output = f64;
+
+ fn div(self, rhs: StdDuration) -> Self::Output {
+ self.as_seconds_f64() / rhs.as_secs_f64()
+ }
+}
+
+impl Div<Duration> for StdDuration {
+ type Output = f64;
+
+ fn div(self, rhs: Duration) -> Self::Output {
+ self.as_secs_f64() / rhs.as_seconds_f64()
+ }
+}
+
+impl PartialEq<StdDuration> for Duration {
+ fn eq(&self, rhs: &StdDuration) -> bool {
+ Ok(*self) == Self::try_from(*rhs)
+ }
+}
+
+impl PartialEq<Duration> for StdDuration {
+ fn eq(&self, rhs: &Duration) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialOrd<StdDuration> for Duration {
+ fn partial_cmp(&self, rhs: &StdDuration) -> Option<Ordering> {
+ if rhs.as_secs() > i64::MAX as _ {
+ return Some(Ordering::Less);
+ }
+
+ Some(
+ self.seconds
+ .cmp(&(rhs.as_secs() as _))
+ .then_with(|| self.nanoseconds.cmp(&(rhs.subsec_nanos() as _))),
+ )
+ }
+}
+
+impl PartialOrd<Duration> for StdDuration {
+ fn partial_cmp(&self, rhs: &Duration) -> Option<Ordering> {
+ rhs.partial_cmp(self).map(Ordering::reverse)
+ }
+}
+
+impl Sum for Duration {
+ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+ iter.reduce(|a, b| a + b).unwrap_or_default()
+ }
+}
+
+impl<'a> Sum<&'a Self> for Duration {
+ fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
+ iter.copied().sum()
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/error/component_range.rs b/third_party/rust/time/src/error/component_range.rs
new file mode 100644
index 0000000000..f399356f4d
--- /dev/null
+++ b/third_party/rust/time/src/error/component_range.rs
@@ -0,0 +1,92 @@
+//! Component range error
+
+use core::fmt;
+
+use crate::error;
+
+/// An error type indicating that a component provided to a method was out of range, causing a
+/// failure.
+// i64 is the narrowest type fitting all use cases. This eliminates the need for a type parameter.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ComponentRange {
+ /// Name of the component.
+ pub(crate) name: &'static str,
+ /// Minimum allowed value, inclusive.
+ pub(crate) minimum: i64,
+ /// Maximum allowed value, inclusive.
+ pub(crate) maximum: i64,
+ /// Value that was provided.
+ pub(crate) value: i64,
+ /// The minimum and/or maximum value is conditional on the value of other
+ /// parameters.
+ pub(crate) conditional_range: bool,
+}
+
+impl ComponentRange {
+ /// Obtain the name of the component whose value was out of range.
+ pub const fn name(self) -> &'static str {
+ self.name
+ }
+
+ /// Whether the value's permitted range is conditional, i.e. whether an input with this
+ /// value could have succeeded if the values of other components were different.
+ pub const fn is_conditional(self) -> bool {
+ self.conditional_range
+ }
+}
+
+impl fmt::Display for ComponentRange {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{} must be in the range {}..={}",
+ self.name, self.minimum, self.maximum
+ )?;
+
+ if self.conditional_range {
+ f.write_str(", given values of other parameters")?;
+ }
+
+ Ok(())
+ }
+}
+
+impl From<ComponentRange> for crate::Error {
+ fn from(original: ComponentRange) -> Self {
+ Self::ComponentRange(original)
+ }
+}
+
+impl TryFrom<crate::Error> for ComponentRange {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ComponentRange(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+/// **This trait implementation is deprecated and will be removed in a future breaking release.**
+#[cfg(feature = "serde")]
+impl serde::de::Expected for ComponentRange {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "a value in the range {}..={}",
+ self.minimum, self.maximum
+ )
+ }
+}
+
+#[cfg(feature = "serde")]
+impl ComponentRange {
+ /// Convert the error to a deserialization error.
+ pub(crate) fn into_de_error<E: serde::de::Error>(self) -> E {
+ E::invalid_value(serde::de::Unexpected::Signed(self.value), &self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ComponentRange {}
diff --git a/third_party/rust/time/src/error/conversion_range.rs b/third_party/rust/time/src/error/conversion_range.rs
new file mode 100644
index 0000000000..d6d9243e13
--- /dev/null
+++ b/third_party/rust/time/src/error/conversion_range.rs
@@ -0,0 +1,36 @@
+//! Conversion range error
+
+use core::fmt;
+
+use crate::error;
+
+/// An error type indicating that a conversion failed because the target type could not store the
+/// initial value.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ConversionRange;
+
+impl fmt::Display for ConversionRange {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Source value is out of range for the target type")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ConversionRange {}
+
+impl From<ConversionRange> for crate::Error {
+ fn from(err: ConversionRange) -> Self {
+ Self::ConversionRange(err)
+ }
+}
+
+impl TryFrom<crate::Error> for ConversionRange {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ConversionRange(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/different_variant.rs b/third_party/rust/time/src/error/different_variant.rs
new file mode 100644
index 0000000000..22e21cb0c0
--- /dev/null
+++ b/third_party/rust/time/src/error/different_variant.rs
@@ -0,0 +1,34 @@
+//! Different variant error
+
+use core::fmt;
+
+/// An error type indicating that a [`TryFrom`](core::convert::TryFrom) call failed because the
+/// original value was of a different variant.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DifferentVariant;
+
+impl fmt::Display for DifferentVariant {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "value was of a different variant than required")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for DifferentVariant {}
+
+impl From<DifferentVariant> for crate::Error {
+ fn from(err: DifferentVariant) -> Self {
+ Self::DifferentVariant(err)
+ }
+}
+
+impl TryFrom<crate::Error> for DifferentVariant {
+ type Error = Self;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::DifferentVariant(err) => Ok(err),
+ _ => Err(Self),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/format.rs b/third_party/rust/time/src/error/format.rs
new file mode 100644
index 0000000000..94d134363d
--- /dev/null
+++ b/third_party/rust/time/src/error/format.rs
@@ -0,0 +1,92 @@
+//! Error formatting a struct
+
+use core::fmt;
+use std::io;
+
+use crate::error;
+
+/// An error occurred when formatting.
+#[non_exhaustive]
+#[allow(missing_copy_implementations)]
+#[derive(Debug)]
+pub enum Format {
+ /// The type being formatted does not contain sufficient information to format a component.
+ #[non_exhaustive]
+ InsufficientTypeInformation,
+ /// The component named has a value that cannot be formatted into the requested format.
+ ///
+ /// This variant is only returned when using well-known formats.
+ InvalidComponent(&'static str),
+ /// A value of `std::io::Error` was returned internally.
+ StdIo(io::Error),
+}
+
+impl fmt::Display for Format {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InsufficientTypeInformation => f.write_str(
+ "The type being formatted does not contain sufficient information to format a \
+ component.",
+ ),
+ Self::InvalidComponent(component) => write!(
+ f,
+ "The {component} component cannot be formatted into the requested format."
+ ),
+ Self::StdIo(err) => err.fmt(f),
+ }
+ }
+}
+
+impl From<io::Error> for Format {
+ fn from(err: io::Error) -> Self {
+ Self::StdIo(err)
+ }
+}
+
+impl TryFrom<Format> for io::Error {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: Format) -> Result<Self, Self::Error> {
+ match err {
+ Format::StdIo(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Format {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match *self {
+ Self::InsufficientTypeInformation | Self::InvalidComponent(_) => None,
+ Self::StdIo(ref err) => Some(err),
+ }
+ }
+}
+
+impl From<Format> for crate::Error {
+ fn from(original: Format) -> Self {
+ Self::Format(original)
+ }
+}
+
+impl TryFrom<crate::Error> for Format {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::Format(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl Format {
+ /// Obtain an error type for the serializer.
+ #[doc(hidden)] // Exposed only for the `declare_format_string` macro
+ pub fn into_invalid_serde_value<S: serde::Serializer>(self) -> S::Error {
+ use serde::ser::Error;
+ S::Error::custom(self)
+ }
+}
diff --git a/third_party/rust/time/src/error/indeterminate_offset.rs b/third_party/rust/time/src/error/indeterminate_offset.rs
new file mode 100644
index 0000000000..d25d4164ec
--- /dev/null
+++ b/third_party/rust/time/src/error/indeterminate_offset.rs
@@ -0,0 +1,35 @@
+//! Indeterminate offset
+
+use core::fmt;
+
+use crate::error;
+
+/// The system's UTC offset could not be determined at the given datetime.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct IndeterminateOffset;
+
+impl fmt::Display for IndeterminateOffset {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("The system's UTC offset could not be determined")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for IndeterminateOffset {}
+
+impl From<IndeterminateOffset> for crate::Error {
+ fn from(err: IndeterminateOffset) -> Self {
+ Self::IndeterminateOffset(err)
+ }
+}
+
+impl TryFrom<crate::Error> for IndeterminateOffset {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::IndeterminateOffset(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/invalid_format_description.rs b/third_party/rust/time/src/error/invalid_format_description.rs
new file mode 100644
index 0000000000..29c46edb16
--- /dev/null
+++ b/third_party/rust/time/src/error/invalid_format_description.rs
@@ -0,0 +1,80 @@
+//! Invalid format description
+
+use alloc::string::String;
+use core::fmt;
+
+use crate::error;
+
+/// The format description provided was not valid.
+#[non_exhaustive]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum InvalidFormatDescription {
+ /// There was a bracket pair that was opened but not closed.
+ #[non_exhaustive]
+ UnclosedOpeningBracket {
+ /// The zero-based index of the opening bracket.
+ index: usize,
+ },
+ /// A component name is not valid.
+ #[non_exhaustive]
+ InvalidComponentName {
+ /// The name of the invalid component name.
+ name: String,
+ /// The zero-based index the component name starts at.
+ index: usize,
+ },
+ /// A modifier is not valid.
+ #[non_exhaustive]
+ InvalidModifier {
+ /// The value of the invalid modifier.
+ value: String,
+ /// The zero-based index the modifier starts at.
+ index: usize,
+ },
+ /// A component name is missing.
+ #[non_exhaustive]
+ MissingComponentName {
+ /// The zero-based index where the component name should start.
+ index: usize,
+ },
+}
+
+impl From<InvalidFormatDescription> for crate::Error {
+ fn from(original: InvalidFormatDescription) -> Self {
+ Self::InvalidFormatDescription(original)
+ }
+}
+
+impl TryFrom<crate::Error> for InvalidFormatDescription {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::InvalidFormatDescription(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl fmt::Display for InvalidFormatDescription {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use InvalidFormatDescription::*;
+ match self {
+ UnclosedOpeningBracket { index } => {
+ write!(f, "unclosed opening bracket at byte index {index}")
+ }
+ InvalidComponentName { name, index } => {
+ write!(f, "invalid component name `{name}` at byte index {index}")
+ }
+ InvalidModifier { value, index } => {
+ write!(f, "invalid modifier `{value}` at byte index {index}")
+ }
+ MissingComponentName { index } => {
+ write!(f, "missing component name at byte index {index}")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidFormatDescription {}
diff --git a/third_party/rust/time/src/error/invalid_variant.rs b/third_party/rust/time/src/error/invalid_variant.rs
new file mode 100644
index 0000000000..396a749a29
--- /dev/null
+++ b/third_party/rust/time/src/error/invalid_variant.rs
@@ -0,0 +1,34 @@
+//! Invalid variant error
+
+use core::fmt;
+
+/// An error type indicating that a [`FromStr`](core::str::FromStr) call failed because the value
+/// was not a valid variant.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct InvalidVariant;
+
+impl fmt::Display for InvalidVariant {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "value was not a valid variant")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidVariant {}
+
+impl From<InvalidVariant> for crate::Error {
+ fn from(err: InvalidVariant) -> Self {
+ Self::InvalidVariant(err)
+ }
+}
+
+impl TryFrom<crate::Error> for InvalidVariant {
+ type Error = crate::error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::InvalidVariant(err) => Ok(err),
+ _ => Err(crate::error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/mod.rs b/third_party/rust/time/src/error/mod.rs
new file mode 100644
index 0000000000..346b89f748
--- /dev/null
+++ b/third_party/rust/time/src/error/mod.rs
@@ -0,0 +1,112 @@
+//! Various error types returned by methods in the time crate.
+
+mod component_range;
+mod conversion_range;
+mod different_variant;
+#[cfg(feature = "formatting")]
+mod format;
+#[cfg(feature = "local-offset")]
+mod indeterminate_offset;
+#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+mod invalid_format_description;
+mod invalid_variant;
+#[cfg(feature = "parsing")]
+mod parse;
+#[cfg(feature = "parsing")]
+mod parse_from_description;
+#[cfg(feature = "parsing")]
+mod try_from_parsed;
+
+use core::fmt;
+
+pub use component_range::ComponentRange;
+pub use conversion_range::ConversionRange;
+pub use different_variant::DifferentVariant;
+#[cfg(feature = "formatting")]
+pub use format::Format;
+#[cfg(feature = "local-offset")]
+pub use indeterminate_offset::IndeterminateOffset;
+#[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+pub use invalid_format_description::InvalidFormatDescription;
+pub use invalid_variant::InvalidVariant;
+#[cfg(feature = "parsing")]
+pub use parse::Parse;
+#[cfg(feature = "parsing")]
+pub use parse_from_description::ParseFromDescription;
+#[cfg(feature = "parsing")]
+pub use try_from_parsed::TryFromParsed;
+
+/// A unified error type for anything returned by a method in the time crate.
+///
+/// This can be used when you either don't know or don't care about the exact error returned.
+/// `Result<_, time::Error>` (or its alias `time::Result<_>`) will work in these situations.
+#[allow(missing_copy_implementations, variant_size_differences)]
+#[allow(clippy::missing_docs_in_private_items)] // variants only
+#[non_exhaustive]
+#[derive(Debug)]
+pub enum Error {
+ ConversionRange(ConversionRange),
+ ComponentRange(ComponentRange),
+ #[cfg(feature = "local-offset")]
+ IndeterminateOffset(IndeterminateOffset),
+ #[cfg(feature = "formatting")]
+ Format(Format),
+ #[cfg(feature = "parsing")]
+ ParseFromDescription(ParseFromDescription),
+ #[cfg(feature = "parsing")]
+ #[non_exhaustive]
+ UnexpectedTrailingCharacters,
+ #[cfg(feature = "parsing")]
+ TryFromParsed(TryFromParsed),
+ #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+ InvalidFormatDescription(InvalidFormatDescription),
+ DifferentVariant(DifferentVariant),
+ InvalidVariant(InvalidVariant),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::ConversionRange(e) => e.fmt(f),
+ Self::ComponentRange(e) => e.fmt(f),
+ #[cfg(feature = "local-offset")]
+ Self::IndeterminateOffset(e) => e.fmt(f),
+ #[cfg(feature = "formatting")]
+ Self::Format(e) => e.fmt(f),
+ #[cfg(feature = "parsing")]
+ Self::ParseFromDescription(e) => e.fmt(f),
+ #[cfg(feature = "parsing")]
+ Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
+ #[cfg(feature = "parsing")]
+ Self::TryFromParsed(e) => e.fmt(f),
+ #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+ Self::InvalidFormatDescription(e) => e.fmt(f),
+ Self::DifferentVariant(e) => e.fmt(f),
+ Self::InvalidVariant(e) => e.fmt(f),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::ConversionRange(err) => Some(err),
+ Self::ComponentRange(err) => Some(err),
+ #[cfg(feature = "local-offset")]
+ Self::IndeterminateOffset(err) => Some(err),
+ #[cfg(feature = "formatting")]
+ Self::Format(err) => Some(err),
+ #[cfg(feature = "parsing")]
+ Self::ParseFromDescription(err) => Some(err),
+ #[cfg(feature = "parsing")]
+ Self::UnexpectedTrailingCharacters => None,
+ #[cfg(feature = "parsing")]
+ Self::TryFromParsed(err) => Some(err),
+ #[cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc"))]
+ Self::InvalidFormatDescription(err) => Some(err),
+ Self::DifferentVariant(err) => Some(err),
+ Self::InvalidVariant(err) => Some(err),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/parse.rs b/third_party/rust/time/src/error/parse.rs
new file mode 100644
index 0000000000..b90ac74e73
--- /dev/null
+++ b/third_party/rust/time/src/error/parse.rs
@@ -0,0 +1,97 @@
+//! Error that occurred at some stage of parsing
+
+use core::fmt;
+
+use crate::error::{self, ParseFromDescription, TryFromParsed};
+
+/// An error that occurred at some stage of parsing.
+#[allow(variant_size_differences)]
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Parse {
+ #[allow(clippy::missing_docs_in_private_items)]
+ TryFromParsed(TryFromParsed),
+ #[allow(clippy::missing_docs_in_private_items)]
+ ParseFromDescription(ParseFromDescription),
+ /// The input should have ended, but there were characters remaining.
+ #[non_exhaustive]
+ UnexpectedTrailingCharacters,
+}
+
+impl fmt::Display for Parse {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::TryFromParsed(err) => err.fmt(f),
+ Self::ParseFromDescription(err) => err.fmt(f),
+ Self::UnexpectedTrailingCharacters => f.write_str("unexpected trailing characters"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Parse {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::TryFromParsed(err) => Some(err),
+ Self::ParseFromDescription(err) => Some(err),
+ Self::UnexpectedTrailingCharacters => None,
+ }
+ }
+}
+
+impl From<TryFromParsed> for Parse {
+ fn from(err: TryFromParsed) -> Self {
+ Self::TryFromParsed(err)
+ }
+}
+
+impl TryFrom<Parse> for TryFromParsed {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: Parse) -> Result<Self, Self::Error> {
+ match err {
+ Parse::TryFromParsed(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl From<ParseFromDescription> for Parse {
+ fn from(err: ParseFromDescription) -> Self {
+ Self::ParseFromDescription(err)
+ }
+}
+
+impl TryFrom<Parse> for ParseFromDescription {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: Parse) -> Result<Self, Self::Error> {
+ match err {
+ Parse::ParseFromDescription(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl From<Parse> for crate::Error {
+ fn from(err: Parse) -> Self {
+ match err {
+ Parse::TryFromParsed(err) => Self::TryFromParsed(err),
+ Parse::ParseFromDescription(err) => Self::ParseFromDescription(err),
+ Parse::UnexpectedTrailingCharacters => Self::UnexpectedTrailingCharacters,
+ }
+ }
+}
+
+impl TryFrom<crate::Error> for Parse {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ParseFromDescription(err) => Ok(Self::ParseFromDescription(err)),
+ crate::Error::UnexpectedTrailingCharacters => Ok(Self::UnexpectedTrailingCharacters),
+ crate::Error::TryFromParsed(err) => Ok(Self::TryFromParsed(err)),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/parse_from_description.rs b/third_party/rust/time/src/error/parse_from_description.rs
new file mode 100644
index 0000000000..772f10d173
--- /dev/null
+++ b/third_party/rust/time/src/error/parse_from_description.rs
@@ -0,0 +1,47 @@
+//! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct
+
+use core::fmt;
+
+use crate::error;
+
+/// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ParseFromDescription {
+ /// A string literal was not what was expected.
+ #[non_exhaustive]
+ InvalidLiteral,
+ /// A dynamic component was not valid.
+ InvalidComponent(&'static str),
+}
+
+impl fmt::Display for ParseFromDescription {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InvalidLiteral => f.write_str("a character literal was not valid"),
+ Self::InvalidComponent(name) => {
+ write!(f, "the '{name}' component could not be parsed")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseFromDescription {}
+
+impl From<ParseFromDescription> for crate::Error {
+ fn from(original: ParseFromDescription) -> Self {
+ Self::ParseFromDescription(original)
+ }
+}
+
+impl TryFrom<crate::Error> for ParseFromDescription {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::ParseFromDescription(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/error/try_from_parsed.rs b/third_party/rust/time/src/error/try_from_parsed.rs
new file mode 100644
index 0000000000..da8e6947a3
--- /dev/null
+++ b/third_party/rust/time/src/error/try_from_parsed.rs
@@ -0,0 +1,71 @@
+//! Error converting a [`Parsed`](crate::parsing::Parsed) struct to another type
+
+use core::fmt;
+
+use crate::error;
+
+/// An error that occurred when converting a [`Parsed`](crate::parsing::Parsed) to another type.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TryFromParsed {
+ /// The [`Parsed`](crate::parsing::Parsed) did not include enough information to construct the
+ /// type.
+ InsufficientInformation,
+ /// Some component contained an invalid value for the type.
+ ComponentRange(error::ComponentRange),
+}
+
+impl fmt::Display for TryFromParsed {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InsufficientInformation => f.write_str(
+ "the `Parsed` struct did not include enough information to construct the type",
+ ),
+ Self::ComponentRange(err) => err.fmt(f),
+ }
+ }
+}
+
+impl From<error::ComponentRange> for TryFromParsed {
+ fn from(v: error::ComponentRange) -> Self {
+ Self::ComponentRange(v)
+ }
+}
+
+impl TryFrom<TryFromParsed> for error::ComponentRange {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: TryFromParsed) -> Result<Self, Self::Error> {
+ match err {
+ TryFromParsed::ComponentRange(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for TryFromParsed {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::InsufficientInformation => None,
+ Self::ComponentRange(err) => Some(err),
+ }
+ }
+}
+
+impl From<TryFromParsed> for crate::Error {
+ fn from(original: TryFromParsed) -> Self {
+ Self::TryFromParsed(original)
+ }
+}
+
+impl TryFrom<crate::Error> for TryFromParsed {
+ type Error = error::DifferentVariant;
+
+ fn try_from(err: crate::Error) -> Result<Self, Self::Error> {
+ match err {
+ crate::Error::TryFromParsed(err) => Ok(err),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/ext.rs b/third_party/rust/time/src/ext.rs
new file mode 100644
index 0000000000..5a1393d86f
--- /dev/null
+++ b/third_party/rust/time/src/ext.rs
@@ -0,0 +1,279 @@
+//! Extension traits.
+
+use core::time::Duration as StdDuration;
+
+use crate::Duration;
+
+/// Sealed trait to prevent downstream implementations.
+mod sealed {
+ /// A trait that cannot be implemented by downstream users.
+ pub trait Sealed {}
+ impl Sealed for i64 {}
+ impl Sealed for u64 {}
+ impl Sealed for f64 {}
+}
+
+// region: NumericalDuration
+/// Create [`Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`Duration`]s.
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!(5.nanoseconds(), Duration::nanoseconds(5));
+/// assert_eq!(5.microseconds(), Duration::microseconds(5));
+/// assert_eq!(5.milliseconds(), Duration::milliseconds(5));
+/// assert_eq!(5.seconds(), Duration::seconds(5));
+/// assert_eq!(5.minutes(), Duration::minutes(5));
+/// assert_eq!(5.hours(), Duration::hours(5));
+/// assert_eq!(5.days(), Duration::days(5));
+/// assert_eq!(5.weeks(), Duration::weeks(5));
+/// ```
+///
+/// Signed integers work as well!
+///
+/// ```rust
+/// # use time::{Duration, ext::NumericalDuration};
+/// assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5));
+/// assert_eq!((-5).microseconds(), Duration::microseconds(-5));
+/// assert_eq!((-5).milliseconds(), Duration::milliseconds(-5));
+/// assert_eq!((-5).seconds(), Duration::seconds(-5));
+/// assert_eq!((-5).minutes(), Duration::minutes(-5));
+/// assert_eq!((-5).hours(), Duration::hours(-5));
+/// assert_eq!((-5).days(), Duration::days(-5));
+/// assert_eq!((-5).weeks(), Duration::weeks(-5));
+/// ```
+///
+/// Just like any other [`Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalDuration;
+/// assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds());
+/// assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds());
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalDuration: sealed::Sealed {
+ /// Create a [`Duration`] from the number of nanoseconds.
+ fn nanoseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of microseconds.
+ fn microseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of milliseconds.
+ fn milliseconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of seconds.
+ fn seconds(self) -> Duration;
+ /// Create a [`Duration`] from the number of minutes.
+ fn minutes(self) -> Duration;
+ /// Create a [`Duration`] from the number of hours.
+ fn hours(self) -> Duration;
+ /// Create a [`Duration`] from the number of days.
+ fn days(self) -> Duration;
+ /// Create a [`Duration`] from the number of weeks.
+ fn weeks(self) -> Duration;
+}
+
+impl NumericalDuration for i64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::microseconds(self)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::milliseconds(self)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::seconds(self)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::minutes(self)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::hours(self)
+ }
+
+ fn days(self) -> Duration {
+ Duration::days(self)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::weeks(self)
+ }
+}
+
+impl NumericalDuration for f64 {
+ fn nanoseconds(self) -> Duration {
+ Duration::nanoseconds(self as _)
+ }
+
+ fn microseconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000.) as _)
+ }
+
+ fn milliseconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000_000.) as _)
+ }
+
+ fn seconds(self) -> Duration {
+ Duration::nanoseconds((self * 1_000_000_000.) as _)
+ }
+
+ fn minutes(self) -> Duration {
+ Duration::nanoseconds((self * 60_000_000_000.) as _)
+ }
+
+ fn hours(self) -> Duration {
+ Duration::nanoseconds((self * 3_600_000_000_000.) as _)
+ }
+
+ fn days(self) -> Duration {
+ Duration::nanoseconds((self * 86_400_000_000_000.) as _)
+ }
+
+ fn weeks(self) -> Duration {
+ Duration::nanoseconds((self * 604_800_000_000_000.) as _)
+ }
+}
+// endregion NumericalDuration
+
+// region: NumericalStdDuration
+/// Create [`std::time::Duration`]s from numeric literals.
+///
+/// # Examples
+///
+/// Basic construction of [`std::time::Duration`]s.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// # use core::time::Duration;
+/// assert_eq!(5.std_nanoseconds(), Duration::from_nanos(5));
+/// assert_eq!(5.std_microseconds(), Duration::from_micros(5));
+/// assert_eq!(5.std_milliseconds(), Duration::from_millis(5));
+/// assert_eq!(5.std_seconds(), Duration::from_secs(5));
+/// assert_eq!(5.std_minutes(), Duration::from_secs(5 * 60));
+/// assert_eq!(5.std_hours(), Duration::from_secs(5 * 3_600));
+/// assert_eq!(5.std_days(), Duration::from_secs(5 * 86_400));
+/// assert_eq!(5.std_weeks(), Duration::from_secs(5 * 604_800));
+/// ```
+///
+/// Just like any other [`std::time::Duration`], they can be added, subtracted, etc.
+///
+/// ```rust
+/// # use time::ext::NumericalStdDuration;
+/// assert_eq!(
+/// 2.std_seconds() + 500.std_milliseconds(),
+/// 2_500.std_milliseconds()
+/// );
+/// assert_eq!(
+/// 2.std_seconds() - 500.std_milliseconds(),
+/// 1_500.std_milliseconds()
+/// );
+/// ```
+///
+/// When called on floating point values, any remainder of the floating point value will be
+/// truncated. Keep in mind that floating point numbers are inherently imprecise and have limited
+/// capacity.
+pub trait NumericalStdDuration: sealed::Sealed {
+ /// Create a [`std::time::Duration`] from the number of nanoseconds.
+ fn std_nanoseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of microseconds.
+ fn std_microseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of milliseconds.
+ fn std_milliseconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of seconds.
+ fn std_seconds(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of minutes.
+ fn std_minutes(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of hours.
+ fn std_hours(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of days.
+ fn std_days(self) -> StdDuration;
+ /// Create a [`std::time::Duration`] from the number of weeks.
+ fn std_weeks(self) -> StdDuration;
+}
+
+impl NumericalStdDuration for u64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ StdDuration::from_nanos(self)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ StdDuration::from_micros(self)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ StdDuration::from_millis(self)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ StdDuration::from_secs(self)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ StdDuration::from_secs(self * 60)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ StdDuration::from_secs(self * 3_600)
+ }
+
+ fn std_days(self) -> StdDuration {
+ StdDuration::from_secs(self * 86_400)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ StdDuration::from_secs(self * 604_800)
+ }
+}
+
+impl NumericalStdDuration for f64 {
+ fn std_nanoseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos(self as _)
+ }
+
+ fn std_microseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000.) as _)
+ }
+
+ fn std_milliseconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000_000.) as _)
+ }
+
+ fn std_seconds(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 1_000_000_000.) as _)
+ }
+
+ fn std_minutes(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 60_000_000_000.) as _)
+ }
+
+ fn std_hours(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 3_600_000_000_000.) as _)
+ }
+
+ fn std_days(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 86_400_000_000_000.) as _)
+ }
+
+ fn std_weeks(self) -> StdDuration {
+ assert!(self >= 0.);
+ StdDuration::from_nanos((self * 604_800_000_000_000.) as _)
+ }
+}
+// endregion NumericalStdDuration
diff --git a/third_party/rust/time/src/format_description/borrowed_format_item.rs b/third_party/rust/time/src/format_description/borrowed_format_item.rs
new file mode 100644
index 0000000000..425b902878
--- /dev/null
+++ b/third_party/rust/time/src/format_description/borrowed_format_item.rs
@@ -0,0 +1,106 @@
+//! A format item with borrowed data.
+
+#[cfg(feature = "alloc")]
+use alloc::string::String;
+#[cfg(feature = "alloc")]
+use core::fmt;
+
+use crate::error;
+use crate::format_description::Component;
+
+/// A complete description of how to format and parse a type.
+#[non_exhaustive]
+#[cfg_attr(not(feature = "alloc"), derive(Debug))]
+#[derive(Clone, PartialEq, Eq)]
+pub enum BorrowedFormatItem<'a> {
+ /// Bytes that are formatted as-is.
+ ///
+ /// **Note**: If you call the `format` method that returns a `String`, these bytes will be
+ /// passed through `String::from_utf8_lossy`.
+ Literal(&'a [u8]),
+ /// A minimal representation of a single non-literal item.
+ Component(Component),
+ /// A series of literals or components that collectively form a partial or complete
+ /// description.
+ Compound(&'a [Self]),
+ /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
+ /// will be no effect on the resulting `struct`.
+ ///
+ /// This variant has no effect on formatting, as the value is guaranteed to be present.
+ Optional(&'a Self),
+ /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
+ /// formatting, the first element of the slice is used. An empty slice is a no-op when
+ /// formatting or parsing.
+ First(&'a [Self]),
+}
+
+#[cfg(feature = "alloc")]
+impl fmt::Debug for BorrowedFormatItem<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
+ Self::Component(component) => component.fmt(f),
+ Self::Compound(compound) => compound.fmt(f),
+ Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
+ Self::First(items) => f.debug_tuple("First").field(items).finish(),
+ }
+ }
+}
+
+impl From<Component> for BorrowedFormatItem<'_> {
+ fn from(component: Component) -> Self {
+ Self::Component(component)
+ }
+}
+
+impl TryFrom<BorrowedFormatItem<'_>> for Component {
+ type Error = error::DifferentVariant;
+
+ fn try_from(value: BorrowedFormatItem<'_>) -> Result<Self, Self::Error> {
+ match value {
+ BorrowedFormatItem::Component(component) => Ok(component),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl<'a> From<&'a [BorrowedFormatItem<'_>]> for BorrowedFormatItem<'a> {
+ fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self {
+ Self::Compound(items)
+ }
+}
+
+impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] {
+ type Error = error::DifferentVariant;
+
+ fn try_from(value: BorrowedFormatItem<'a>) -> Result<Self, Self::Error> {
+ match value {
+ BorrowedFormatItem::Compound(items) => Ok(items),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl PartialEq<Component> for BorrowedFormatItem<'_> {
+ fn eq(&self, rhs: &Component) -> bool {
+ matches!(self, Self::Component(component) if component == rhs)
+ }
+}
+
+impl PartialEq<BorrowedFormatItem<'_>> for Component {
+ fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialEq<&[Self]> for BorrowedFormatItem<'_> {
+ fn eq(&self, rhs: &&[Self]) -> bool {
+ matches!(self, Self::Compound(compound) if compound == rhs)
+ }
+}
+
+impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] {
+ fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
+ rhs == self
+ }
+}
diff --git a/third_party/rust/time/src/format_description/component.rs b/third_party/rust/time/src/format_description/component.rs
new file mode 100644
index 0000000000..68d162e260
--- /dev/null
+++ b/third_party/rust/time/src/format_description/component.rs
@@ -0,0 +1,37 @@
+//! Part of a format description.
+
+use crate::format_description::modifier;
+
+/// A component of a larger format description.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Component {
+ /// Day of the month.
+ Day(modifier::Day),
+ /// Month of the year.
+ Month(modifier::Month),
+ /// Ordinal day of the year.
+ Ordinal(modifier::Ordinal),
+ /// Day of the week.
+ Weekday(modifier::Weekday),
+ /// Week within the year.
+ WeekNumber(modifier::WeekNumber),
+ /// Year of the date.
+ Year(modifier::Year),
+ /// Hour of the day.
+ Hour(modifier::Hour),
+ /// Minute within the hour.
+ Minute(modifier::Minute),
+ /// AM/PM part of the time.
+ Period(modifier::Period),
+ /// Second within the minute.
+ Second(modifier::Second),
+ /// Subsecond within the second.
+ Subsecond(modifier::Subsecond),
+ /// Hour of the UTC offset.
+ OffsetHour(modifier::OffsetHour),
+ /// Minute within the hour of the UTC offset.
+ OffsetMinute(modifier::OffsetMinute),
+ /// Second within the minute of the UTC offset.
+ OffsetSecond(modifier::OffsetSecond),
+}
diff --git a/third_party/rust/time/src/format_description/mod.rs b/third_party/rust/time/src/format_description/mod.rs
new file mode 100644
index 0000000000..7712288e74
--- /dev/null
+++ b/third_party/rust/time/src/format_description/mod.rs
@@ -0,0 +1,34 @@
+//! Description of how types should be formatted and parsed.
+//!
+//! 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.
+
+mod borrowed_format_item;
+mod component;
+pub mod modifier;
+#[cfg(feature = "alloc")]
+mod owned_format_item;
+#[cfg(feature = "alloc")]
+mod parse;
+
+pub use borrowed_format_item::BorrowedFormatItem as FormatItem;
+#[cfg(feature = "alloc")]
+pub use owned_format_item::OwnedFormatItem;
+
+pub use self::component::Component;
+#[cfg(feature = "alloc")]
+pub use self::parse::{parse, parse_owned};
+
+/// Well-known formats, typically standards.
+pub mod well_known {
+ pub mod iso8601;
+ mod rfc2822;
+ mod rfc3339;
+
+ #[doc(inline)]
+ pub use iso8601::Iso8601;
+ pub use rfc2822::Rfc2822;
+ pub use rfc3339::Rfc3339;
+}
diff --git a/third_party/rust/time/src/format_description/modifier.rs b/third_party/rust/time/src/format_description/modifier.rs
new file mode 100644
index 0000000000..e01c18fda4
--- /dev/null
+++ b/third_party/rust/time/src/format_description/modifier.rs
@@ -0,0 +1,355 @@
+//! Various modifiers for components.
+
+// region: date modifiers
+/// Day of the month.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Day {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The representation of a month.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum MonthRepr {
+ /// The number of the month (January is 1, December is 12).
+ Numerical,
+ /// The long form of the month name (e.g. "January").
+ Long,
+ /// The short form of the month name (e.g. "Jan").
+ Short,
+}
+
+/// Month of the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Month {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What form of representation should be used?
+ pub repr: MonthRepr,
+ /// Is the value case sensitive when parsing?
+ pub case_sensitive: bool,
+}
+
+/// Ordinal day of the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Ordinal {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The representation used for the day of the week.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WeekdayRepr {
+ /// The short form of the weekday (e.g. "Mon").
+ Short,
+ /// The long form of the weekday (e.g. "Monday").
+ Long,
+ /// A numerical representation using Sunday as the first day of the week.
+ ///
+ /// Sunday is either 0 or 1, depending on the other modifier's value.
+ Sunday,
+ /// A numerical representation using Monday as the first day of the week.
+ ///
+ /// Monday is either 0 or 1, depending on the other modifier's value.
+ Monday,
+}
+
+/// Day of the week.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Weekday {
+ /// What form of representation should be used?
+ pub repr: WeekdayRepr,
+ /// When using a numerical representation, should it be zero or one-indexed?
+ pub one_indexed: bool,
+ /// Is the value case sensitive when parsing?
+ pub case_sensitive: bool,
+}
+
+/// The representation used for the week number.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WeekNumberRepr {
+ /// Week 1 is the week that contains January 4.
+ Iso,
+ /// Week 1 begins on the first Sunday of the calendar year.
+ Sunday,
+ /// Week 1 begins on the first Monday of the calendar year.
+ Monday,
+}
+
+/// Week within the year.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct WeekNumber {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What kind of representation should be used?
+ pub repr: WeekNumberRepr,
+}
+
+/// The representation used for a year value.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum YearRepr {
+ /// The full value of the year.
+ Full,
+ /// Only the last two digits of the year.
+ LastTwo,
+}
+
+/// Year of the date.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Year {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// What kind of representation should be used?
+ pub repr: YearRepr,
+ /// Whether the value is based on the ISO week number or the Gregorian calendar.
+ pub iso_week_based: bool,
+ /// Whether the `+` sign is present when a positive year contains fewer than five digits.
+ pub sign_is_mandatory: bool,
+}
+// endregion date modifiers
+
+// region: time modifiers
+/// Hour of the day.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Hour {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+ /// Is the hour displayed using a 12 or 24-hour clock?
+ pub is_12_hour_clock: bool,
+}
+
+/// Minute within the hour.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Minute {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// AM/PM part of the time.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Period {
+ /// Is the period uppercase or lowercase?
+ pub is_uppercase: bool,
+ /// Is the value case sensitive when parsing?
+ ///
+ /// Note that when `false`, the `is_uppercase` field has no effect on parsing behavior.
+ pub case_sensitive: bool,
+}
+
+/// Second within the minute.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Second {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// The number of digits present in a subsecond representation.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SubsecondDigits {
+ /// Exactly one digit.
+ One,
+ /// Exactly two digits.
+ Two,
+ /// Exactly three digits.
+ Three,
+ /// Exactly four digits.
+ Four,
+ /// Exactly five digits.
+ Five,
+ /// Exactly six digits.
+ Six,
+ /// Exactly seven digits.
+ Seven,
+ /// Exactly eight digits.
+ Eight,
+ /// Exactly nine digits.
+ Nine,
+ /// Any number of digits (up to nine) that is at least one. When formatting, the minimum digits
+ /// necessary will be used.
+ OneOrMore,
+}
+
+/// Subsecond within the second.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Subsecond {
+ /// How many digits are present in the component?
+ pub digits: SubsecondDigits,
+}
+// endregion time modifiers
+
+// region: offset modifiers
+/// Hour of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetHour {
+ /// Whether the `+` sign is present on positive values.
+ pub sign_is_mandatory: bool,
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// Minute within the hour of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetMinute {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+
+/// Second within the minute of the UTC offset.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OffsetSecond {
+ /// The padding to obtain the minimum width.
+ pub padding: Padding,
+}
+// endregion offset modifiers
+
+/// Type of padding to ensure a minimum width.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Padding {
+ /// A space character (` `) should be used as padding.
+ Space,
+ /// A zero character (`0`) should be used as padding.
+ Zero,
+ /// There is no padding. This can result in a width below the otherwise minimum number of
+ /// characters.
+ None,
+}
+
+/// Generate the provided code if and only if `pub` is present.
+macro_rules! if_pub {
+ (pub $(#[$attr:meta])*; $($x:tt)*) => {
+ $(#[$attr])*
+ ///
+ /// This function exists since [`Default::default()`] cannot be used in a `const` context.
+ /// It may be removed once that becomes possible. As the [`Default`] trait is in the
+ /// prelude, removing this function in the future will not cause any resolution failures for
+ /// the overwhelming majority of users; only users who use `#![no_implicit_prelude]` will be
+ /// affected. As such it will not be considered a breaking change.
+ $($x)*
+ };
+ ($($_:tt)*) => {};
+}
+
+/// Implement `Default` for the given type. This also generates an inherent implementation of a
+/// `default` method that is `const fn`, permitting the default value to be used in const contexts.
+// Every modifier should use this macro rather than a derived `Default`.
+macro_rules! impl_const_default {
+ ($($(#[$doc:meta])* $(@$pub:ident)? $type:ty => $default:expr;)*) => {$(
+ impl $type {
+ if_pub! {
+ $($pub)?
+ $(#[$doc])*;
+ pub const fn default() -> Self {
+ $default
+ }
+ }
+ }
+
+ $(#[$doc])*
+ impl Default for $type {
+ fn default() -> Self {
+ $default
+ }
+ }
+ )*};
+}
+
+impl_const_default! {
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Day => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the
+ /// [`Numerical`](Self::Numerical) representation.
+ MonthRepr => Self::Numerical;
+ /// Creates an instance of this type that indicates the value uses the
+ /// [`Numerical`](MonthRepr::Numerical) representation, is [padded with zeroes](Padding::Zero),
+ /// and is case-sensitive when parsing.
+ @pub Month => Self {
+ padding: Padding::Zero,
+ repr: MonthRepr::Numerical,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Ordinal => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the [`Long`](Self::Long) representation.
+ WeekdayRepr => Self::Long;
+ /// Creates a modifier that indicates the value uses the [`Long`](WeekdayRepr::Long)
+ /// representation and is case-sensitive when parsing. If the representation is changed to a
+ /// numerical one, the instance defaults to one-based indexing.
+ @pub Weekday => Self {
+ repr: WeekdayRepr::Long,
+ one_indexed: true,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates that the value uses the [`Iso`](Self::Iso) representation.
+ WeekNumberRepr => Self::Iso;
+ /// Creates a modifier that indicates that the value is [padded with zeroes](Padding::Zero)
+ /// and uses the [`Iso`](WeekNumberRepr::Iso) representation.
+ @pub WeekNumber => Self {
+ padding: Padding::Zero,
+ repr: WeekNumberRepr::Iso,
+ };
+ /// Creates a modifier that indicates the value uses the [`Full`](Self::Full) representation.
+ YearRepr => Self::Full;
+ /// Creates a modifier that indicates the value uses the [`Full`](YearRepr::Full)
+ /// representation, is [padded with zeroes](Padding::Zero), uses the Gregorian calendar as its
+ /// base, and only includes the year's sign if necessary.
+ @pub Year => Self {
+ padding: Padding::Zero,
+ repr: YearRepr::Full,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero) and
+ /// has the 24-hour representation.
+ @pub Hour => Self {
+ padding: Padding::Zero,
+ is_12_hour_clock: false,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Minute => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value uses the upper-case representation and is
+ /// case-sensitive when parsing.
+ @pub Period => Self {
+ is_uppercase: true,
+ case_sensitive: true,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub Second => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the stringified value contains [one or more
+ /// digits](Self::OneOrMore).
+ SubsecondDigits => Self::OneOrMore;
+ /// Creates a modifier that indicates the stringified value contains [one or more
+ /// digits](SubsecondDigits::OneOrMore).
+ @pub Subsecond => Self { digits: SubsecondDigits::OneOrMore };
+ /// Creates a modifier that indicates the value uses the `+` sign for all positive values
+ /// and is [padded with zeroes](Padding::Zero).
+ @pub OffsetHour => Self {
+ sign_is_mandatory: true,
+ padding: Padding::Zero,
+ };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub OffsetMinute => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero).
+ @pub OffsetSecond => Self { padding: Padding::Zero };
+ /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero).
+ Padding => Self::Zero;
+}
diff --git a/third_party/rust/time/src/format_description/owned_format_item.rs b/third_party/rust/time/src/format_description/owned_format_item.rs
new file mode 100644
index 0000000000..6f7f7c2337
--- /dev/null
+++ b/third_party/rust/time/src/format_description/owned_format_item.rs
@@ -0,0 +1,162 @@
+//! A format item with owned data.
+
+use alloc::boxed::Box;
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::fmt;
+
+use crate::error;
+use crate::format_description::{Component, FormatItem};
+
+/// A complete description of how to format and parse a type.
+#[non_exhaustive]
+#[derive(Clone, PartialEq, Eq)]
+pub enum OwnedFormatItem {
+ /// Bytes that are formatted as-is.
+ ///
+ /// **Note**: If you call the `format` method that returns a `String`, these bytes will be
+ /// passed through `String::from_utf8_lossy`.
+ Literal(Box<[u8]>),
+ /// A minimal representation of a single non-literal item.
+ Component(Component),
+ /// A series of literals or components that collectively form a partial or complete
+ /// description.
+ Compound(Box<[Self]>),
+ /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
+ /// will be no effect on the resulting `struct`.
+ ///
+ /// This variant has no effect on formatting, as the value is guaranteed to be present.
+ Optional(Box<Self>),
+ /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
+ /// formatting, the first element of the [`Vec`] is used. An empty [`Vec`] is a no-op when
+ /// formatting or parsing.
+ First(Box<[Self]>),
+}
+
+impl fmt::Debug for OwnedFormatItem {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
+ Self::Component(component) => component.fmt(f),
+ Self::Compound(compound) => compound.fmt(f),
+ Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
+ Self::First(items) => f.debug_tuple("First").field(items).finish(),
+ }
+ }
+}
+
+// region: conversions from FormatItem
+impl From<FormatItem<'_>> for OwnedFormatItem {
+ fn from(item: FormatItem<'_>) -> Self {
+ (&item).into()
+ }
+}
+
+impl From<&FormatItem<'_>> for OwnedFormatItem {
+ fn from(item: &FormatItem<'_>) -> Self {
+ match item {
+ FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
+ FormatItem::Component(component) => Self::Component(*component),
+ FormatItem::Compound(compound) => Self::Compound(
+ compound
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
+ FormatItem::First(items) => Self::First(
+ items
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ }
+ }
+}
+
+impl From<Vec<FormatItem<'_>>> for OwnedFormatItem {
+ fn from(items: Vec<FormatItem<'_>>) -> Self {
+ items.as_slice().into()
+ }
+}
+
+impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem {
+ fn from(items: &T) -> Self {
+ Self::Compound(
+ items
+ .as_ref()
+ .iter()
+ .cloned()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ )
+ }
+}
+// endregion conversions from FormatItem
+
+// region: from variants
+impl From<Component> for OwnedFormatItem {
+ fn from(component: Component) -> Self {
+ Self::Component(component)
+ }
+}
+
+impl TryFrom<OwnedFormatItem> for Component {
+ type Error = error::DifferentVariant;
+
+ fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
+ match value {
+ OwnedFormatItem::Component(component) => Ok(component),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+
+impl From<Vec<Self>> for OwnedFormatItem {
+ fn from(items: Vec<Self>) -> Self {
+ Self::Compound(items.into_boxed_slice())
+ }
+}
+
+impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> {
+ type Error = error::DifferentVariant;
+
+ fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
+ match value {
+ OwnedFormatItem::Compound(items) => Ok(items.into_vec()),
+ _ => Err(error::DifferentVariant),
+ }
+ }
+}
+// endregion from variants
+
+// region: equality
+impl PartialEq<Component> for OwnedFormatItem {
+ fn eq(&self, rhs: &Component) -> bool {
+ matches!(self, Self::Component(component) if component == rhs)
+ }
+}
+
+impl PartialEq<OwnedFormatItem> for Component {
+ fn eq(&self, rhs: &OwnedFormatItem) -> bool {
+ rhs == self
+ }
+}
+
+impl PartialEq<&[Self]> for OwnedFormatItem {
+ fn eq(&self, rhs: &&[Self]) -> bool {
+ matches!(self, Self::Compound(compound) if &&**compound == rhs)
+ }
+}
+
+impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] {
+ fn eq(&self, rhs: &OwnedFormatItem) -> bool {
+ rhs == self
+ }
+}
+// endregion equality
diff --git a/third_party/rust/time/src/format_description/parse/ast.rs b/third_party/rust/time/src/format_description/parse/ast.rs
new file mode 100644
index 0000000000..6977cc9cda
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/ast.rs
@@ -0,0 +1,278 @@
+//! AST for parsing format descriptions.
+
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::iter;
+use core::iter::Peekable;
+
+use super::{lexer, Error, Location, Span};
+
+/// 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,
+ },
+ /// A sequence of brackets. The first acts as the escape character.
+ EscapedBracket {
+ /// The first bracket.
+ _first: Location,
+ /// The second bracket.
+ _second: Location,
+ },
+ /// Part of a type, along with its modifiers.
+ Component {
+ /// Where the opening bracket was in the format string.
+ _opening_bracket: Location,
+ /// Whitespace between the opening bracket and name.
+ _leading_whitespace: Option<Whitespace<'a>>,
+ /// The name of the component.
+ name: Name<'a>,
+ /// The modifiers for the component.
+ modifiers: Vec<Modifier<'a>>,
+ /// Whitespace between the modifiers and closing bracket.
+ _trailing_whitespace: Option<Whitespace<'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 modifier for a component.
+pub(super) struct Modifier<'a> {
+ /// Whitespace preceding the modifier.
+ pub(super) _leading_whitespace: Whitespace<'a>,
+ /// The key of the modifier.
+ pub(super) key: Key<'a>,
+ /// Where the colon of the modifier was in the format string.
+ pub(super) _colon: 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,
+}
+
+/// 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<'a>(
+ tokens: impl Iterator<Item = lexer::Token<'a>>,
+) -> impl Iterator<Item = Result<Item<'a>, Error>> {
+ let mut tokens = tokens.peekable();
+ iter::from_fn(move || {
+ Some(match tokens.next()? {
+ lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }),
+ 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)
+ }
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } => unreachable!(
+ "internal error: 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`",
+ ),
+ })
+ })
+}
+
+/// Parse a component. This assumes that the opening bracket has already been consumed.
+fn parse_component<'a>(
+ opening_bracket: Location,
+ tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>,
+) -> 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
+ };
+
+ 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(),
+ );
+ return Err(Error {
+ _inner: span.error("expected component name"),
+ public: crate::error::InvalidFormatDescription::MissingComponentName {
+ index: span.start_byte(),
+ },
+ });
+ };
+
+ 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;
+ };
+
+ if let Some(&lexer::Token::ComponentPart {
+ kind: lexer::ComponentKind::NotWhitespace,
+ value,
+ span,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+
+ 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..];
+
+ 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(),
+ },
+ });
+ }
+
+ modifiers.push(Modifier {
+ _leading_whitespace: whitespace,
+ key: Key {
+ value: key,
+ span: span.subspan(..colon_index),
+ },
+ _colon: span.start.offset(colon_index),
+ value: Value {
+ value,
+ span: span.subspan(colon_index + 1..),
+ },
+ });
+ } else {
+ break Some(whitespace);
+ }
+ };
+
+ let closing_bracket = if let Some(&lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location,
+ }) = tokens.peek()
+ {
+ tokens.next(); // consume
+ location
+ } else {
+ return Err(Error {
+ _inner: opening_bracket.error("unclosed bracket"),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte,
+ },
+ });
+ };
+
+ Ok(Item::Component {
+ _opening_bracket: opening_bracket,
+ _leading_whitespace: leading_whitespace,
+ name,
+ modifiers,
+ _trailing_whitespace: trailing_whitespace,
+ _closing_bracket: closing_bracket,
+ })
+}
diff --git a/third_party/rust/time/src/format_description/parse/format_item.rs b/third_party/rust/time/src/format_description/parse/format_item.rs
new file mode 100644
index 0000000000..53146d5228
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/format_item.rs
@@ -0,0 +1,386 @@
+//! Typed, validated representation of a parsed format description.
+
+use alloc::string::String;
+
+use super::{ast, Error};
+
+/// Parse an AST iterator into a sequence of format items.
+pub(super) fn parse<'a>(
+ ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
+) -> impl Iterator<Item = Result<Item<'a>, Error>> {
+ ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
+}
+
+/// 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),
+}
+
+impl Item<'_> {
+ /// Parse an AST item into a format item.
+ pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
+ Ok(match ast_item {
+ ast::Item::Component {
+ _opening_bracket: _,
+ _leading_whitespace: _,
+ name,
+ modifiers,
+ _trailing_whitespace: _,
+ _closing_bracket: _,
+ } => Item::Component(component_from_ast(&name, &modifiers)?),
+ ast::Item::Literal { value, _span: _ } => Item::Literal(value),
+ ast::Item::EscapedBracket {
+ _first: _,
+ _second: _,
+ } => Item::Literal(b"["),
+ })
+ }
+}
+
+impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> {
+ fn from(item: Item<'a>) -> Self {
+ match item {
+ Item::Literal(literal) => Self::Literal(literal),
+ Item::Component(component) => Self::Component(component.into()),
+ }
+ }
+}
+
+impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
+ fn from(item: Item<'_>) -> Self {
+ match item {
+ Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
+ Item::Component(component) => Self::Component(component.into()),
+ }
+ }
+}
+
+/// Declare the `Component` struct.
+macro_rules! component_definition {
+ ($vis:vis enum $name:ident {
+ $($variant:ident = $parse_variant:literal {
+ $($field:ident = $parse_field:literal:
+ Option<$field_type:ty> => $target_field:ident),* $(,)?
+ }),* $(,)?
+ }) => {
+ $vis enum $name {
+ $($variant($variant),)*
+ }
+
+ $($vis struct $variant {
+ $($field: Option<$field_type>),*
+ })*
+
+ $(impl $variant {
+ /// Parse the component from the AST, given its modifiers.
+ fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> 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)?;
+ continue;
+ })*
+ return Err(Error {
+ _inner: 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(),
+ }
+ });
+ }
+
+ Ok(this)
+ }
+ })*
+
+ impl From<$name> for crate::format_description::Component {
+ fn from(component: $name) -> Self {
+ match component {$(
+ $name::$variant($variant { $($field),* }) => {
+ $crate::format_description::component::Component::$variant(
+ $crate::format_description::modifier::$variant {$(
+ $target_field: $field.unwrap_or_default().into()
+ ),*}
+ )
+ }
+ )*}
+ }
+ }
+
+ /// Parse a component from the AST, given its name and modifiers.
+ fn component_from_ast(
+ name: &ast::Name<'_>,
+ modifiers: &[ast::Modifier<'_>],
+ ) -> Result<Component, Error> {
+ $(if name.value.eq_ignore_ascii_case($parse_variant) {
+ return Ok(Component::$variant($variant::with_modifiers(&modifiers)?));
+ })*
+ Err(Error {
+ _inner: name.span.error("invalid component"),
+ public: crate::error::InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(name.value).into_owned(),
+ index: name.span.start_byte(),
+ },
+ })
+ }
+ }
+}
+
+// Keep in alphabetical order.
+component_definition! {
+ pub(super) enum Component {
+ Day = b"day" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Hour = b"hour" {
+ padding = b"padding": Option<Padding> => padding,
+ base = b"repr": Option<HourBase> => is_12_hour_clock,
+ },
+ Minute = b"minute" {
+ padding = b"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,
+ },
+ OffsetHour = b"offset_hour" {
+ sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory,
+ padding = b"padding": Option<Padding> => padding,
+ },
+ OffsetMinute = b"offset_minute" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ OffsetSecond = b"offset_second" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Ordinal = b"ordinal" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Period = b"period" {
+ case = b"case": Option<PeriodCase> => is_uppercase,
+ case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
+ },
+ Second = b"second" {
+ padding = b"padding": Option<Padding> => padding,
+ },
+ Subsecond = b"subsecond" {
+ digits = b"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,
+ },
+ WeekNumber = b"week_number" {
+ padding = b"padding": Option<Padding> => padding,
+ repr = b"repr": Option<WeekNumberRepr> => repr,
+ },
+ 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,
+ },
+ }
+}
+
+/// Get the target type for a given enum.
+macro_rules! target_ty {
+ ($name:ident $type:ty) => {
+ $type
+ };
+ ($name:ident) => {
+ $crate::format_description::modifier::$name
+ };
+}
+
+/// Get the target value for a given enum.
+macro_rules! target_value {
+ ($name:ident $variant:ident $value:expr) => {
+ $value
+ };
+ ($name:ident $variant:ident) => {
+ $crate::format_description::modifier::$name::$variant
+ };
+}
+
+// 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
+/// variant. The only significant change is that the string representation of the variant must be
+/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
+/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
+/// used when parsing the modifier. The value is not case sensitive.
+///
+/// If the type in the public API does not have the same name as the type in the internal
+/// representation, then the former must be specified in parenthesis after the internal name. For
+/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
+/// the public API.
+///
+/// By default, the internal variant name is assumed to be the same as the public variant name. If
+/// this is not the case, the qualified path to the variant must be specified in parenthesis after
+/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
+/// but is represented as `true` in the public API.
+macro_rules! modifier {
+ ($(
+ enum $name:ident $(($target_ty:ty))? {
+ $(
+ $(#[$attr:meta])?
+ $variant:ident $(($target_value:expr))? = $parse_variant:literal
+ ),* $(,)?
+ }
+ )+) => {$(
+ enum $name {
+ $($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) {
+ return Ok(Some(Self::$variant));
+ })*
+ Err(Error {
+ _inner: 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(),
+ },
+ })
+ }
+ }
+
+ impl From<$name> for target_ty!($name $($target_ty)?) {
+ fn from(modifier: $name) -> Self {
+ match modifier {
+ $($name::$variant => target_value!($name $variant $($target_value)?)),*
+ }
+ }
+ }
+ )+};
+}
+
+// Keep in alphabetical order.
+modifier! {
+ enum HourBase(bool) {
+ Twelve(true) = b"12",
+ #[default]
+ TwentyFour(false) = b"24",
+ }
+
+ enum MonthCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum MonthRepr {
+ #[default]
+ Numerical = b"numerical",
+ Long = b"long",
+ Short = b"short",
+ }
+
+ enum Padding {
+ Space = b"space",
+ #[default]
+ Zero = b"zero",
+ None = b"none",
+ }
+
+ enum PeriodCase(bool) {
+ Lower(false) = b"lower",
+ #[default]
+ Upper(true) = b"upper",
+ }
+
+ enum PeriodCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum SignBehavior(bool) {
+ #[default]
+ Automatic(false) = b"automatic",
+ Mandatory(true) = b"mandatory",
+ }
+
+ enum SubsecondDigits {
+ One = b"1",
+ Two = b"2",
+ Three = b"3",
+ Four = b"4",
+ Five = b"5",
+ Six = b"6",
+ Seven = b"7",
+ Eight = b"8",
+ Nine = b"9",
+ #[default]
+ OneOrMore = b"1+",
+ }
+
+ enum WeekNumberRepr {
+ #[default]
+ Iso = b"iso",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum WeekdayCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayOneIndexed(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayRepr {
+ Short = b"short",
+ #[default]
+ Long = b"long",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum YearBase(bool) {
+ #[default]
+ Calendar(false) = b"calendar",
+ IsoWeek(true) = b"iso_week",
+ }
+
+ enum YearRepr {
+ #[default]
+ Full = b"full",
+ LastTwo = b"last_two",
+ }
+}
diff --git a/third_party/rust/time/src/format_description/parse/lexer.rs b/third_party/rust/time/src/format_description/parse/lexer.rs
new file mode 100644
index 0000000000..e405ea8c85
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/lexer.rs
@@ -0,0 +1,159 @@
+//! Lexer for parsing format descriptions.
+
+use core::iter;
+
+use super::{Location, Span};
+
+/// 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,
+ },
+ /// 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.
+ kind: BracketKind,
+ /// Where the bracket was in the format string.
+ location: Location,
+ },
+ /// One part of a component. This could be its name, a modifier, or whitespace.
+ ComponentPart {
+ /// 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,
+ },
+}
+
+/// What type of bracket is present.
+pub(super) enum BracketKind {
+ /// An opening bracket: `[`
+ Opening,
+ /// A closing bracket: `]`
+ Closing,
+}
+
+/// Indicates whether the component is whitespace or not.
+pub(super) enum ComponentKind {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Whitespace,
+ #[allow(clippy::missing_docs_in_private_items)]
+ NotWhitespace,
+}
+
+/// 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;
+ let mut byte_pos = 0;
+
+ iter.map(move |byte| {
+ let location = Location {
+ line,
+ column,
+ byte: byte_pos,
+ };
+ column += 1;
+ 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<'_>> {
+ let mut depth: u8 = 0;
+ let mut iter = attach_location(input.iter().copied()).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,
+ });
+ }
+
+ Some(match iter.next()? {
+ (b'[', location) => {
+ if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') {
+ // escaped bracket
+ second_bracket_location = Some(second_location);
+ input = &input[2..];
+ } else {
+ // opening bracket
+ 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,
+ }
+ }
+ // literal
+ (_, start_location) if depth == 0 => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+
+ while let Some((_, location)) = iter.next_if(|&(byte, _)| 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),
+ }
+ }
+ // component part
+ (byte, start_location) => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+ 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()
+ }) {
+ end_location = location;
+ bytes += 1;
+ }
+
+ 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),
+ }
+ }
+ })
+ })
+}
diff --git a/third_party/rust/time/src/format_description/parse/mod.rs b/third_party/rust/time/src/format_description/parse/mod.rs
new file mode 100644
index 0000000000..c73a674494
--- /dev/null
+++ b/third_party/rust/time/src/format_description/parse/mod.rs
@@ -0,0 +1,193 @@
+//! Parser for format descriptions.
+
+use alloc::vec::Vec;
+use core::ops::{RangeFrom, RangeTo};
+
+mod ast;
+mod format_item;
+mod lexer;
+
+/// 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).
+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);
+ let format_items = format_item::parse(ast);
+ Ok(format_items
+ .map(|res| res.map(Into::into))
+ .collect::<Result<Vec<_>, _>>()?)
+}
+
+/// 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).
+///
+/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
+/// that there is no lifetime that needs to be handled.
+///
+/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
+pub fn parse_owned(
+ s: &str,
+) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> {
+ let lexed = lexer::lex(s.as_bytes());
+ let ast = ast::parse(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))
+}
+
+/// 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,
+}
+
+impl Location {
+ /// 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 {
+ Self {
+ line: self.line,
+ column: self.column + offset,
+ byte: self.byte + offset,
+ }
+ }
+
+ /// Create an error with the provided message at this location.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _span: Span {
+ start: self,
+ end: self,
+ },
+ }
+ }
+}
+
+/// A start and end point within a string.
+#[derive(Clone, Copy)]
+struct Span {
+ #[allow(clippy::missing_docs_in_private_items)]
+ start: Location,
+ #[allow(clippy::missing_docs_in_private_items)]
+ end: Location,
+}
+
+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 {
+ Self {
+ start: self.start,
+ end: self.start,
+ }
+ }
+
+ /// Obtain a `Span` pointing at the end of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_end(&self) -> Self {
+ Self {
+ start: self.end,
+ end: self.end,
+ }
+ }
+
+ /// Create an error with the provided message at this span.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _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;
+}
+
+impl Subspan for RangeFrom<usize> {
+ fn subspan(self, span: &Span) -> Span {
+ assert_eq!(span.start.line, span.end.line);
+
+ Span {
+ start: Location {
+ line: span.start.line,
+ column: span.start.column + self.start,
+ byte: span.start.byte + self.start,
+ },
+ end: span.end,
+ }
+ }
+}
+
+impl Subspan for RangeTo<usize> {
+ fn subspan(self, span: &Span) -> Span {
+ assert_eq!(span.start.line, span.end.line);
+
+ Span {
+ start: span.start,
+ end: Location {
+ line: span.start.line,
+ column: span.start.column + self.end - 1,
+ byte: span.start.byte + self.end - 1,
+ },
+ }
+ }
+}
+
+/// The internal error type.
+struct ErrorInner {
+ /// The message displayed to the user.
+ _message: &'static str,
+ /// Where the error originated.
+ _span: Span,
+}
+
+/// A complete error description.
+struct Error {
+ /// The internal error.
+ _inner: ErrorInner,
+ /// The error needed for interoperability with the rest of `time`.
+ public: crate::error::InvalidFormatDescription,
+}
+
+impl From<Error> for crate::error::InvalidFormatDescription {
+ fn from(error: Error) -> Self {
+ error.public
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/iso8601.rs b/third_party/rust/time/src/format_description/well_known/iso8601.rs
new file mode 100644
index 0000000000..f19181a926
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/iso8601.rs
@@ -0,0 +1,233 @@
+//! The format described in ISO 8601.
+
+mod adt_hack;
+
+use core::num::NonZeroU8;
+
+pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig};
+
+/// A configuration for [`Iso8601`] that only parses values.
+const PARSING_ONLY: EncodedConfig = Config {
+ formatted_components: FormattedComponents::None,
+ use_separators: false,
+ year_is_six_digits: false,
+ date_kind: DateKind::Calendar,
+ time_precision: TimePrecision::Hour {
+ decimal_digits: None,
+ },
+ offset_precision: OffsetPrecision::Hour,
+}
+.encode();
+
+/// The default configuration for [`Iso8601`].
+const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();
+
+/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html).
+///
+/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions.
+///
+/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`].
+/// Passing any other value is **unspecified behavior**.
+///
+/// Example: 1997-11-21T09:55:06.000000000-06:00
+///
+/// # Examples
+#[cfg_attr(feature = "formatting", doc = "```rust")]
+#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
+/// # use time::format_description::well_known::Iso8601;
+/// # use time_macros::datetime;
+/// assert_eq!(
+/// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?,
+/// "1997-11-12T09:55:06.000000000-06:00"
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct Iso8601<const CONFIG: EncodedConfig = DEFAULT_CONFIG>;
+
+impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Iso8601")
+ .field("config", &Config::decode(CONFIG))
+ .finish()
+ }
+}
+
+impl Iso8601<DEFAULT_CONFIG> {
+ /// An [`Iso8601`] with the default configuration.
+ ///
+ /// The following is the default behavior:
+ ///
+ /// - The configuration can be used for both formatting and parsing.
+ /// - The date, time, and UTC offset are all formatted.
+ /// - Separators (such as `-` and `:`) are included.
+ /// - The year contains four digits, such that the year must be between 0 and 9999.
+ /// - The date uses the calendar format.
+ /// - The time has precision to the second and nine decimal digits.
+ /// - The UTC offset has precision to the minute.
+ ///
+ /// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create
+ /// a custom configuration.
+ pub const DEFAULT: Self = Self;
+}
+
+impl Iso8601<PARSING_ONLY> {
+ /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
+ /// unspecified behavior.
+ pub const PARSING: Self = Self;
+}
+
+/// Which components to format.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FormattedComponents {
+ /// The configuration can only be used for parsing. Using this to format a value is
+ /// unspecified behavior.
+ None,
+ /// Format only the date.
+ Date,
+ /// Format only the time.
+ Time,
+ /// Format only the UTC offset.
+ Offset,
+ /// Format the date and time.
+ DateTime,
+ /// Format the date, time, and UTC offset.
+ DateTimeOffset,
+ /// Format the time and UTC offset.
+ TimeOffset,
+}
+
+/// Which format to use for the date.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DateKind {
+ /// Use the year-month-day format.
+ Calendar,
+ /// Use the year-week-weekday format.
+ Week,
+ /// Use the week-ordinal format.
+ Ordinal,
+}
+
+/// The precision and number of decimal digits present for the time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TimePrecision {
+ /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
+ /// specified number of decimal digits, if any.
+ Hour {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+ /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
+ /// number of decimal digits, if any.
+ Minute {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+ /// Format the hour, minute, and second. Nanoseconds will be represented with the specified
+ /// number of decimal digits, if any.
+ Second {
+ #[allow(clippy::missing_docs_in_private_items)]
+ decimal_digits: Option<NonZeroU8>,
+ },
+}
+
+/// The precision for the UTC offset.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OffsetPrecision {
+ /// Format only the offset hour. Requires the offset minute to be zero.
+ Hour,
+ /// Format both the offset hour and minute.
+ Minute,
+}
+
+/// Configuration for [`Iso8601`].
+// This is only used as a const generic, so there's no need to have a number of implementations on
+// it.
+#[allow(missing_copy_implementations)]
+#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this.
+#[derive(Debug)]
+pub struct Config {
+ /// Which components, if any, will be formatted.
+ pub(crate) formatted_components: FormattedComponents,
+ /// Whether the format contains separators (such as `-` or `:`).
+ pub(crate) use_separators: bool,
+ /// Whether the year is six digits.
+ pub(crate) year_is_six_digits: bool,
+ /// The format used for the date.
+ pub(crate) date_kind: DateKind,
+ /// The precision and number of decimal digits present for the time.
+ pub(crate) time_precision: TimePrecision,
+ /// The precision for the UTC offset.
+ pub(crate) offset_precision: OffsetPrecision,
+}
+
+impl Config {
+ /// A configuration for the [`Iso8601`] format.
+ ///
+ /// The following is the default behavior:
+ ///
+ /// - The configuration can be used for both formatting and parsing.
+ /// - The date, time, and UTC offset are all formatted.
+ /// - Separators (such as `-` and `:`) are included.
+ /// - The year contains four digits, such that the year must be between 0 and 9999.
+ /// - The date uses the calendar format.
+ /// - The time has precision to the second and nine decimal digits.
+ /// - The UTC offset has precision to the minute.
+ ///
+ /// If you need different behavior, use the setter methods on this struct.
+ pub const DEFAULT: Self = Self {
+ formatted_components: FormattedComponents::DateTimeOffset,
+ use_separators: true,
+ year_is_six_digits: false,
+ date_kind: DateKind::Calendar,
+ time_precision: TimePrecision::Second {
+ decimal_digits: NonZeroU8::new(9),
+ },
+ offset_precision: OffsetPrecision::Minute,
+ };
+
+ /// Set whether the format the date, time, and/or UTC offset.
+ pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
+ Self {
+ formatted_components,
+ ..self
+ }
+ }
+
+ /// Set whether the format contains separators (such as `-` or `:`).
+ pub const fn set_use_separators(self, use_separators: bool) -> Self {
+ Self {
+ use_separators,
+ ..self
+ }
+ }
+
+ /// Set whether the year is six digits.
+ pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self {
+ Self {
+ year_is_six_digits,
+ ..self
+ }
+ }
+
+ /// Set the format used for the date.
+ pub const fn set_date_kind(self, date_kind: DateKind) -> Self {
+ Self { date_kind, ..self }
+ }
+
+ /// Set the precision and number of decimal digits present for the time.
+ pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
+ Self {
+ time_precision,
+ ..self
+ }
+ }
+
+ /// Set the precision for the UTC offset.
+ pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
+ Self {
+ offset_precision,
+ ..self
+ }
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
new file mode 100644
index 0000000000..757a68b18f
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
@@ -0,0 +1,249 @@
+//! Hackery to work around not being able to use ADTs in const generics on stable.
+
+use core::num::NonZeroU8;
+
+#[cfg(feature = "formatting")]
+use super::Iso8601;
+use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision};
+
+// This provides a way to include `EncodedConfig` in documentation without displaying the type it is
+// aliased to.
+#[doc(hidden)]
+pub type DoNotRelyOnWhatThisIs = u128;
+
+/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`].
+///
+/// The type this is aliased to must not be relied upon. It can change in any release without
+/// notice.
+pub type EncodedConfig = DoNotRelyOnWhatThisIs;
+
+#[cfg(feature = "formatting")]
+impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
+ /// The user-provided configuration for the ISO 8601 format.
+ const CONFIG: Config = Config::decode(CONFIG);
+ /// Whether the date should be formatted.
+ pub(crate) const FORMAT_DATE: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Date | FC::DateTime | FC::DateTimeOffset
+ );
+ /// Whether the time should be formatted.
+ pub(crate) const FORMAT_TIME: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset
+ );
+ /// Whether the UTC offset should be formatted.
+ pub(crate) const FORMAT_OFFSET: bool = matches!(
+ Self::CONFIG.formatted_components,
+ FC::Offset | FC::DateTimeOffset | FC::TimeOffset
+ );
+ /// Whether the year is six digits.
+ pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits;
+ /// Whether the format contains separators (such as `-` or `:`).
+ pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators;
+ /// Which format to use for the date.
+ pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind;
+ /// The precision and number of decimal digits to use for the time.
+ pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision;
+ /// The precision for the UTC offset.
+ pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision;
+}
+
+impl Config {
+ /// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`].
+ ///
+ /// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any
+ /// other usage is unspecified behavior.
+ pub const fn encode(&self) -> EncodedConfig {
+ let mut bytes = [0; EncodedConfig::BITS as usize / 8];
+
+ bytes[0] = match self.formatted_components {
+ FC::None => 0,
+ FC::Date => 1,
+ FC::Time => 2,
+ FC::Offset => 3,
+ FC::DateTime => 4,
+ FC::DateTimeOffset => 5,
+ FC::TimeOffset => 6,
+ };
+ bytes[1] = self.use_separators as _;
+ bytes[2] = self.year_is_six_digits as _;
+ bytes[3] = match self.date_kind {
+ DateKind::Calendar => 0,
+ DateKind::Week => 1,
+ DateKind::Ordinal => 2,
+ };
+ bytes[4] = match self.time_precision {
+ TimePrecision::Hour { .. } => 0,
+ TimePrecision::Minute { .. } => 1,
+ TimePrecision::Second { .. } => 2,
+ };
+ bytes[5] = match self.time_precision {
+ TimePrecision::Hour { decimal_digits }
+ | TimePrecision::Minute { decimal_digits }
+ | TimePrecision::Second { decimal_digits } => match decimal_digits {
+ None => 0,
+ Some(decimal_digits) => decimal_digits.get(),
+ },
+ };
+ bytes[6] = match self.offset_precision {
+ OffsetPrecision::Hour => 0,
+ OffsetPrecision::Minute => 1,
+ };
+
+ EncodedConfig::from_be_bytes(bytes)
+ }
+
+ /// Decode the configuration. The configuration must have been generated from
+ /// [`Config::encode`].
+ pub(super) const fn decode(encoded: EncodedConfig) -> Self {
+ let bytes = encoded.to_be_bytes();
+
+ let formatted_components = match bytes[0] {
+ 0 => FC::None,
+ 1 => FC::Date,
+ 2 => FC::Time,
+ 3 => FC::Offset,
+ 4 => FC::DateTime,
+ 5 => FC::DateTimeOffset,
+ 6 => FC::TimeOffset,
+ _ => panic!("invalid configuration"),
+ };
+ let use_separators = match bytes[1] {
+ 0 => false,
+ 1 => true,
+ _ => panic!("invalid configuration"),
+ };
+ let year_is_six_digits = match bytes[2] {
+ 0 => false,
+ 1 => true,
+ _ => panic!("invalid configuration"),
+ };
+ let date_kind = match bytes[3] {
+ 0 => DateKind::Calendar,
+ 1 => DateKind::Week,
+ 2 => DateKind::Ordinal,
+ _ => panic!("invalid configuration"),
+ };
+ let time_precision = match bytes[4] {
+ 0 => TimePrecision::Hour {
+ decimal_digits: NonZeroU8::new(bytes[5]),
+ },
+ 1 => TimePrecision::Minute {
+ decimal_digits: NonZeroU8::new(bytes[5]),
+ },
+ 2 => TimePrecision::Second {
+ decimal_digits: NonZeroU8::new(bytes[5]),
+ },
+ _ => panic!("invalid configuration"),
+ };
+ let offset_precision = match bytes[6] {
+ 0 => OffsetPrecision::Hour,
+ 1 => OffsetPrecision::Minute,
+ _ => panic!("invalid configuration"),
+ };
+
+ // No `for` loops in `const fn`.
+ let mut idx = 7; // first unused byte
+ while idx < EncodedConfig::BITS as usize / 8 {
+ assert!(bytes[idx] == 0, "invalid configuration");
+ idx += 1;
+ }
+
+ Self {
+ formatted_components,
+ use_separators,
+ year_is_six_digits,
+ date_kind,
+ time_precision,
+ offset_precision,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! eq {
+ ($a:expr, $b:expr) => {{
+ let a = $a;
+ let b = $b;
+ a.formatted_components == b.formatted_components
+ && a.use_separators == b.use_separators
+ && a.year_is_six_digits == b.year_is_six_digits
+ && a.date_kind == b.date_kind
+ && a.time_precision == b.time_precision
+ && a.offset_precision == b.offset_precision
+ }};
+ }
+
+ #[test]
+ fn encoding_roundtrip() {
+ macro_rules! assert_roundtrip {
+ ($config:expr) => {
+ let config = $config;
+ let encoded = config.encode();
+ let decoded = Config::decode(encoded);
+ assert!(eq!(config, decoded));
+ };
+ }
+
+ assert_roundtrip!(Config::DEFAULT);
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset));
+ assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset));
+ assert_roundtrip!(Config::DEFAULT.set_use_separators(false));
+ assert_roundtrip!(Config::DEFAULT.set_use_separators(true));
+ assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false));
+ assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week));
+ assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
+ decimal_digits: None,
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
+ decimal_digits: NonZeroU8::new(1),
+ }));
+ assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour));
+ assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute));
+ }
+
+ macro_rules! assert_decode_fail {
+ ($encoding:expr) => {
+ assert!(
+ std::panic::catch_unwind(|| {
+ Config::decode($encoding);
+ })
+ .is_err()
+ );
+ };
+ }
+
+ #[test]
+ fn decode_fail() {
+ assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00);
+ assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00);
+ }
+}
diff --git a/third_party/rust/time/src/format_description/well_known/rfc2822.rs b/third_party/rust/time/src/format_description/well_known/rfc2822.rs
new file mode 100644
index 0000000000..3c890ab107
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/rfc2822.rs
@@ -0,0 +1,30 @@
+//! The format described in RFC 2822.
+
+/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3).
+///
+/// Example: Fri, 21 Nov 1997 09:55:06 -0600
+///
+/// # Examples
+#[cfg_attr(feature = "parsing", doc = "```rust")]
+#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
+/// # use time::{format_description::well_known::Rfc2822, OffsetDateTime};
+/// use time_macros::datetime;
+/// assert_eq!(
+/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?,
+/// datetime!(1993-06-12 13:25:19 +00:00)
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+///
+#[cfg_attr(feature = "formatting", doc = "```rust")]
+#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
+/// # use time::format_description::well_known::Rfc2822;
+/// # use time_macros::datetime;
+/// assert_eq!(
+/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?,
+/// "Fri, 21 Nov 1997 09:55:06 -0600"
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Rfc2822;
diff --git a/third_party/rust/time/src/format_description/well_known/rfc3339.rs b/third_party/rust/time/src/format_description/well_known/rfc3339.rs
new file mode 100644
index 0000000000..f0873cbac5
--- /dev/null
+++ b/third_party/rust/time/src/format_description/well_known/rfc3339.rs
@@ -0,0 +1,30 @@
+//! The format described in RFC 3339.
+
+/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6).
+///
+/// Format example: 1985-04-12T23:20:50.52Z
+///
+/// # Examples
+#[cfg_attr(feature = "parsing", doc = "```rust")]
+#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
+/// # use time::{format_description::well_known::Rfc3339, OffsetDateTime};
+/// # use time_macros::datetime;
+/// assert_eq!(
+/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?,
+/// datetime!(1985-04-12 23:20:50.52 +00:00)
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+///
+#[cfg_attr(feature = "formatting", doc = "```rust")]
+#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
+/// # use time::format_description::well_known::Rfc3339;
+/// # use time_macros::datetime;
+/// assert_eq!(
+/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?,
+/// "1985-04-12T23:20:50.52Z"
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Rfc3339;
diff --git a/third_party/rust/time/src/formatting/formattable.rs b/third_party/rust/time/src/formatting/formattable.rs
new file mode 100644
index 0000000000..7fee2fbeab
--- /dev/null
+++ b/third_party/rust/time/src/formatting/formattable.rs
@@ -0,0 +1,304 @@
+//! A trait that can be used to format an item from its components.
+
+use core::ops::Deref;
+use std::io;
+
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
+use crate::format_description::{FormatItem, OwnedFormatItem};
+use crate::formatting::{
+ format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
+};
+use crate::{error, Date, Time, UtcOffset};
+
+/// A type that can be formatted.
+#[cfg_attr(__time_03_docs, doc(notable_trait))]
+pub trait Formattable: sealed::Sealed {}
+impl Formattable for FormatItem<'_> {}
+impl Formattable for [FormatItem<'_>] {}
+impl Formattable for OwnedFormatItem {}
+impl Formattable for [OwnedFormatItem] {}
+impl Formattable for Rfc3339 {}
+impl Formattable for Rfc2822 {}
+impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
+impl<T: Deref> Formattable for T where T::Target: Formattable {}
+
+/// Seal the trait to prevent downstream users from implementing it.
+mod sealed {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Format the item using a format description, the intended output, and the various components.
+ pub trait Sealed {
+ /// Format the item into the provided output, returning the number of bytes written.
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format>;
+
+ /// Format the item directly to a `String`.
+ fn format(
+ &self,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<String, error::Format> {
+ let mut buf = Vec::new();
+ self.format_into(&mut buf, date, time, offset)?;
+ Ok(String::from_utf8_lossy(&buf).into_owned())
+ }
+ }
+}
+
+// region: custom formats
+impl<'a> sealed::Sealed for FormatItem<'a> {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ Ok(match *self {
+ Self::Literal(literal) => write(output, literal)?,
+ Self::Component(component) => format_component(output, component, date, time, offset)?,
+ Self::Compound(items) => items.format_into(output, date, time, offset)?,
+ Self::Optional(item) => item.format_into(output, date, time, offset)?,
+ Self::First(items) => match items {
+ [] => 0,
+ [item, ..] => item.format_into(output, date, time, offset)?,
+ },
+ })
+ }
+}
+
+impl<'a> sealed::Sealed for [FormatItem<'a>] {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+ for item in self.iter() {
+ bytes += item.format_into(output, date, time, offset)?;
+ }
+ Ok(bytes)
+ }
+}
+
+impl sealed::Sealed for OwnedFormatItem {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ match self {
+ Self::Literal(literal) => Ok(write(output, literal)?),
+ Self::Component(component) => format_component(output, *component, date, time, offset),
+ Self::Compound(items) => items.format_into(output, date, time, offset),
+ Self::Optional(item) => item.format_into(output, date, time, offset),
+ Self::First(items) => match &**items {
+ [] => Ok(0),
+ [item, ..] => item.format_into(output, date, time, offset),
+ },
+ }
+ }
+}
+
+impl sealed::Sealed for [OwnedFormatItem] {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+ for item in self.iter() {
+ bytes += item.format_into(output, date, time, offset)?;
+ }
+ Ok(bytes)
+ }
+}
+
+impl<T: Deref> sealed::Sealed for T
+where
+ T::Target: sealed::Sealed,
+{
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ self.deref().format_into(output, date, time, offset)
+ }
+}
+// endregion custom formats
+
+// region: well-known formats
+impl sealed::Sealed for Rfc2822 {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+
+ let mut bytes = 0;
+
+ let (year, month, day) = date.to_calendar_date();
+
+ if year < 1900 {
+ return Err(error::Format::InvalidComponent("year"));
+ }
+ if offset.seconds_past_minute() != 0 {
+ return Err(error::Format::InvalidComponent("offset_second"));
+ }
+
+ bytes += write(
+ output,
+ &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
+ )?;
+ bytes += write(output, b", ")?;
+ 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 += write(output, b" ")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
+ bytes += write(output, b":")?;
+ 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())?;
+
+ Ok(bytes)
+ }
+}
+
+impl sealed::Sealed for Rfc3339 {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+
+ let mut bytes = 0;
+
+ let year = date.year();
+
+ if !(0..10_000).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ }
+ if offset.seconds_past_minute() != 0 {
+ return Err(error::Format::InvalidComponent("offset_second"));
+ }
+
+ 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 += write(output, b"-")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, date.day())?;
+ bytes += write(output, b"T")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
+ bytes += write(output, b":")?;
+ bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
+ bytes += write(output, b":")?;
+ 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)
+ } else if (nanos / 10) % 10 != 0 {
+ format_number_pad_zero::<8, _, _>(output, nanos / 10)
+ } else if (nanos / 100) % 10 != 0 {
+ format_number_pad_zero::<7, _, _>(output, nanos / 100)
+ } else if (nanos / 1_000) % 10 != 0 {
+ 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)
+ } else if (nanos / 100_000) % 10 != 0 {
+ 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)
+ } else if (nanos / 10_000_000) % 10 != 0 {
+ format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
+ } else {
+ format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
+ }?;
+ }
+
+ if offset == UtcOffset::UTC {
+ bytes += write(output, b"Z")?;
+ return Ok(bytes);
+ }
+
+ bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
+ 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())?;
+
+ Ok(bytes)
+ }
+}
+
+impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
+ fn format_into(
+ &self,
+ output: &mut impl io::Write,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+ ) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+
+ if Self::FORMAT_DATE {
+ let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
+ 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)?;
+ }
+ if Self::FORMAT_OFFSET {
+ let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
+ bytes += iso8601::format_offset::<_, CONFIG>(output, offset)?;
+ }
+
+ if bytes == 0 {
+ // The only reason there would be no bytes written is if the format was only for
+ // parsing.
+ panic!("attempted to format a parsing-only format description");
+ }
+
+ Ok(bytes)
+ }
+}
+// endregion well-known formats
diff --git a/third_party/rust/time/src/formatting/iso8601.rs b/third_party/rust/time/src/formatting/iso8601.rs
new file mode 100644
index 0000000000..1724f96f5c
--- /dev/null
+++ b/third_party/rust/time/src/formatting/iso8601.rs
@@ -0,0 +1,139 @@
+//! Helpers for implementing formatting for ISO 8601.
+
+use std::io;
+
+use crate::format_description::well_known::iso8601::{
+ DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
+};
+use crate::format_description::well_known::Iso8601;
+use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else};
+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,
+ date: Date,
+) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+
+ match Iso8601::<CONFIG>::DATE_KIND {
+ DateKind::Calendar => {
+ 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())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ 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 += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ 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())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ 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 += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
+ 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())?;
+ } else if !(0..=9999).contains(&year) {
+ return Err(error::Format::InvalidComponent("year"));
+ } else {
+ 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)?;
+ }
+ }
+
+ Ok(bytes)
+}
+
+/// Format the time portion of ISO 8601.
+pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>(
+ output: &mut W,
+ time: Time,
+) -> Result<usize, error::Format> {
+ let mut bytes = 0;
+
+ // The "T" can only be omitted in extended format where there is no date being formatted.
+ bytes += write_if(
+ output,
+ Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE,
+ b"T",
+ )?;
+
+ let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano();
+
+ match Iso8601::<CONFIG>::TIME_PRECISION {
+ TimePrecision::Hour { decimal_digits } => {
+ let hours = (hours as f64)
+ + (minutes as f64) / 60.
+ + (seconds as f64) / 3_600.
+ + (nanoseconds as f64) / 3_600. / 1_000_000_000.;
+ format_float(output, hours, 2, decimal_digits)?;
+ }
+ TimePrecision::Minute { decimal_digits } => {
+ 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.
+ + (nanoseconds as f64) / 60. / 1_000_000_000.;
+ bytes += format_float(output, minutes, 2, decimal_digits)?;
+ }
+ TimePrecision::Second { decimal_digits } => {
+ 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 += 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)?;
+ }
+ }
+
+ Ok(bytes)
+}
+
+/// Format the UTC offset portion of ISO 8601.
+pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>(
+ output: &mut W,
+ offset: UtcOffset,
+) -> Result<usize, error::Format> {
+ if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() {
+ return Ok(write(output, b"Z")?);
+ }
+
+ let mut bytes = 0;
+
+ let (hours, minutes, seconds) = offset.as_hms();
+ if seconds != 0 {
+ 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())?;
+
+ 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())?;
+ }
+
+ Ok(bytes)
+}
diff --git a/third_party/rust/time/src/formatting/mod.rs b/third_party/rust/time/src/formatting/mod.rs
new file mode 100644
index 0000000000..e5409cc58f
--- /dev/null
+++ b/third_party/rust/time/src/formatting/mod.rs
@@ -0,0 +1,506 @@
+//! Formatting for various types.
+
+pub(crate) mod formattable;
+mod iso8601;
+
+use core::num::NonZeroU8;
+use std::io;
+
+pub use self::formattable::Formattable;
+use crate::format_description::{modifier, Component};
+use crate::{error, Date, Time, UtcOffset};
+
+#[allow(clippy::missing_docs_in_private_items)]
+const MONTH_NAMES: [&[u8]; 12] = [
+ b"January",
+ b"February",
+ b"March",
+ b"April",
+ b"May",
+ b"June",
+ b"July",
+ b"August",
+ b"September",
+ b"October",
+ b"November",
+ b"December",
+];
+
+#[allow(clippy::missing_docs_in_private_items)]
+const WEEKDAY_NAMES: [&[u8]; 7] = [
+ b"Monday",
+ b"Tuesday",
+ b"Wednesday",
+ b"Thursday",
+ b"Friday",
+ b"Saturday",
+ b"Sunday",
+];
+
+// region: extension trait
+/// A trait that indicates the formatted width of the value can be determined.
+///
+/// Note that this should not be implemented for any signed integers. This forces the caller to
+/// write the sign if desired.
+pub(crate) trait DigitCount {
+ /// The number of digits in the stringified value.
+ fn num_digits(self) -> u8;
+}
+impl DigitCount for u8 {
+ fn num_digits(self) -> u8 {
+ // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
+ if self < 10 {
+ 1
+ } else if self < 100 {
+ 2
+ } else {
+ 3
+ }
+ }
+}
+impl DigitCount for u16 {
+ fn num_digits(self) -> u8 {
+ // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
+ if self < 10 {
+ 1
+ } else if self < 100 {
+ 2
+ } else if self < 1_000 {
+ 3
+ } else if self < 10_000 {
+ 4
+ } else {
+ 5
+ }
+ }
+}
+
+impl DigitCount for u32 {
+ fn num_digits(self) -> u8 {
+ /// Lookup table
+ const TABLE: &[u64] = &[
+ 0x0001_0000_0000,
+ 0x0001_0000_0000,
+ 0x0001_0000_0000,
+ 0x0001_FFFF_FFF6,
+ 0x0002_0000_0000,
+ 0x0002_0000_0000,
+ 0x0002_FFFF_FF9C,
+ 0x0003_0000_0000,
+ 0x0003_0000_0000,
+ 0x0003_FFFF_FC18,
+ 0x0004_0000_0000,
+ 0x0004_0000_0000,
+ 0x0004_0000_0000,
+ 0x0004_FFFF_D8F0,
+ 0x0005_0000_0000,
+ 0x0005_0000_0000,
+ 0x0005_FFFE_7960,
+ 0x0006_0000_0000,
+ 0x0006_0000_0000,
+ 0x0006_FFF0_BDC0,
+ 0x0007_0000_0000,
+ 0x0007_0000_0000,
+ 0x0007_0000_0000,
+ 0x0007_FF67_6980,
+ 0x0008_0000_0000,
+ 0x0008_0000_0000,
+ 0x0008_FA0A_1F00,
+ 0x0009_0000_0000,
+ 0x0009_0000_0000,
+ 0x0009_C465_3600,
+ 0x000A_0000_0000,
+ 0x000A_0000_0000,
+ ];
+ ((self as u64 + TABLE[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _
+ }
+}
+// endregion extension trait
+
+/// Write all bytes to the output, returning the number of bytes written.
+pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
+ output.write_all(bytes)?;
+ Ok(bytes.len())
+}
+
+/// If `pred` is true, write all bytes to the output, returning the number of bytes written.
+pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> {
+ if pred { write(output, bytes) } else { Ok(0) }
+}
+
+/// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`.
+pub(crate) fn write_if_else(
+ output: &mut impl io::Write,
+ pred: bool,
+ true_bytes: &[u8],
+ false_bytes: &[u8],
+) -> io::Result<usize> {
+ write(output, if pred { true_bytes } else { false_bytes })
+}
+
+/// Write the floating point number to the output, returning the number of bytes written.
+///
+/// This method accepts the number of digits before and after the decimal. The value will be padded
+/// with zeroes to the left if necessary.
+pub(crate) fn format_float(
+ output: &mut impl io::Write,
+ value: f64,
+ digits_before_decimal: u8,
+ digits_after_decimal: Option<NonZeroU8>,
+) -> io::Result<usize> {
+ match digits_after_decimal {
+ 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,
+ )?;
+ 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)?;
+ Ok(width)
+ }
+ }
+}
+
+/// 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,
+ 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()),
+ }
+}
+
+/// 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,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
+ bytes += write(output, b" ")?;
+ }
+ bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
+ Ok(bytes)
+}
+
+/// 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,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
+ bytes += write(output, b"0")?;
+ }
+ bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
+ Ok(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.
+pub(crate) fn format_component(
+ output: &mut impl io::Write,
+ component: Component,
+ date: Option<Date>,
+ time: Option<Time>,
+ offset: Option<UtcOffset>,
+) -> Result<usize, error::Format> {
+ use Component::*;
+ Ok(match (component, date, time, offset) {
+ (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
+ (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
+ (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
+ (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
+ (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
+ (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
+ (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
+ (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
+ (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
+ (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
+ (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
+ (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)?,
+ _ => return Err(error::Format::InsufficientTypeInformation),
+ })
+}
+
+// region: date formatters
+/// Format the day into the designated output.
+fn fmt_day(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Day { padding }: modifier::Day,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, date.day(), padding)
+}
+
+/// Format the month into the designated output.
+fn fmt_month(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Month {
+ padding,
+ repr,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Month,
+) -> Result<usize, io::Error> {
+ match repr {
+ 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]),
+ }
+}
+
+/// Format the ordinal into the designated output.
+fn fmt_ordinal(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Ordinal { padding }: modifier::Ordinal,
+) -> Result<usize, io::Error> {
+ format_number::<3, _, _>(output, date.ordinal(), padding)
+}
+
+/// Format the weekday into the designated output.
+fn fmt_weekday(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Weekday {
+ repr,
+ one_indexed,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Weekday,
+) -> Result<usize, io::Error> {
+ match repr {
+ modifier::WeekdayRepr::Short => write(
+ output,
+ &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
+ ),
+ modifier::WeekdayRepr::Long => write(
+ output,
+ WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize],
+ ),
+ 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, _, _>(
+ output,
+ date.weekday().number_days_from_monday() + one_indexed as u8,
+ modifier::Padding::None,
+ ),
+ }
+}
+
+/// Format the week number into the designated output.
+fn fmt_week_number(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(
+ output,
+ match repr {
+ modifier::WeekNumberRepr::Iso => date.iso_week(),
+ modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
+ modifier::WeekNumberRepr::Monday => date.monday_based_week(),
+ },
+ padding,
+ )
+}
+
+/// Format the year into the designated output.
+fn fmt_year(
+ output: &mut impl io::Write,
+ date: Date,
+ modifier::Year {
+ padding,
+ repr,
+ iso_week_based,
+ sign_is_mandatory,
+ }: modifier::Year,
+) -> Result<usize, io::Error> {
+ let full_year = if iso_week_based {
+ date.iso_year_week().0
+ } else {
+ date.year()
+ };
+ let value = match repr {
+ modifier::YearRepr::Full => full_year,
+ modifier::YearRepr::LastTwo => (full_year % 100).abs(),
+ };
+ let format_number = match repr {
+ #[cfg(feature = "large-dates")]
+ 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, _, _>,
+ };
+ let mut bytes = 0;
+ if repr != modifier::YearRepr::LastTwo {
+ if full_year < 0 {
+ bytes += write(output, b"-")?;
+ } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
+ bytes += write(output, b"+")?;
+ }
+ }
+ bytes += format_number(output, value.unsigned_abs(), padding)?;
+ Ok(bytes)
+}
+// endregion date formatters
+
+// region: time formatters
+/// Format the hour into the designated output.
+fn fmt_hour(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Hour {
+ padding,
+ is_12_hour_clock,
+ }: modifier::Hour,
+) -> Result<usize, io::Error> {
+ let value = match (time.hour(), is_12_hour_clock) {
+ (hour, false) => hour,
+ (0 | 12, true) => 12,
+ (hour, true) if hour < 12 => hour,
+ (hour, true) => hour - 12,
+ };
+ format_number::<2, _, _>(output, value, padding)
+}
+
+/// Format the minute into the designated output.
+fn fmt_minute(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Minute { padding }: modifier::Minute,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, time.minute(), padding)
+}
+
+/// Format the period into the designated output.
+fn fmt_period(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Period {
+ is_uppercase,
+ case_sensitive: _, // no effect on formatting
+ }: modifier::Period,
+) -> Result<usize, io::Error> {
+ match (time.hour() >= 12, is_uppercase) {
+ (false, false) => write(output, b"am"),
+ (false, true) => write(output, b"AM"),
+ (true, false) => write(output, b"pm"),
+ (true, true) => write(output, b"PM"),
+ }
+}
+
+/// Format the second into the designated output.
+fn fmt_second(
+ output: &mut impl io::Write,
+ time: Time,
+ modifier::Second { padding }: modifier::Second,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, time.second(), padding)
+}
+
+/// Format the subsecond into the designated output.
+fn fmt_subsecond<W: io::Write>(
+ output: &mut W,
+ time: Time,
+ modifier::Subsecond { digits }: modifier::Subsecond,
+) -> Result<usize, io::Error> {
+ use modifier::SubsecondDigits::*;
+ let nanos = time.nanosecond();
+
+ if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
+ 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)
+ } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
+ 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)
+ } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
+ 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)
+ } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
+ 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)
+ } else {
+ format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
+ }
+}
+// endregion time formatters
+
+// region: offset formatters
+/// Format the offset hour into the designated output.
+fn fmt_offset_hour(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetHour {
+ padding,
+ sign_is_mandatory,
+ }: modifier::OffsetHour,
+) -> Result<usize, io::Error> {
+ let mut bytes = 0;
+ if offset.is_negative() {
+ bytes += write(output, b"-")?;
+ } else if sign_is_mandatory {
+ bytes += write(output, b"+")?;
+ }
+ bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?;
+ Ok(bytes)
+}
+
+/// Format the offset minute into the designated output.
+fn fmt_offset_minute(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetMinute { padding }: modifier::OffsetMinute,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding)
+}
+
+/// Format the offset second into the designated output.
+fn fmt_offset_second(
+ output: &mut impl io::Write,
+ offset: UtcOffset,
+ modifier::OffsetSecond { padding }: modifier::OffsetSecond,
+) -> Result<usize, io::Error> {
+ format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding)
+}
+// endregion offset formatters
diff --git a/third_party/rust/time/src/instant.rs b/third_party/rust/time/src/instant.rs
new file mode 100644
index 0000000000..2d2f65eefb
--- /dev/null
+++ b/third_party/rust/time/src/instant.rs
@@ -0,0 +1,262 @@
+//! The [`Instant`] struct and its associated `impl`s.
+
+use core::borrow::Borrow;
+use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+use std::time::Instant as StdInstant;
+
+use crate::Duration;
+
+/// A measurement of a monotonically non-decreasing clock. Opaque and useful only with [`Duration`].
+///
+/// Instants are always guaranteed to be no less than any previously measured instant when created,
+/// and are often useful for tasks such as measuring benchmarks or timing how long an operation
+/// takes.
+///
+/// Note, however, that instants are not guaranteed to be **steady**. In other words, each tick of
+/// the underlying clock may not be the same length (e.g. some seconds may be longer than others).
+/// An instant may jump forwards or experience time dilation (slow down or speed up), but it will
+/// never go backwards.
+///
+/// Instants are opaque types that can only be compared to one another. There is no method to get
+/// "the number of seconds" from an instant. Instead, it only allows measuring the duration between
+/// two instants (or comparing two instants).
+///
+/// This implementation allows for operations with signed [`Duration`]s, but is otherwise identical
+/// to [`std::time::Instant`].
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Instant(pub StdInstant);
+
+impl Instant {
+ // region: delegation
+ /// Returns an `Instant` corresponding to "now".
+ ///
+ /// ```rust
+ /// # use time::Instant;
+ /// println!("{:?}", Instant::now());
+ /// ```
+ pub fn now() -> Self {
+ Self(StdInstant::now())
+ }
+
+ /// Returns the amount of time elapsed since this instant was created. The duration will always
+ /// be nonnegative if the instant is not synthetically created.
+ ///
+ /// ```rust
+ /// # use time::{Instant, ext::{NumericalStdDuration, NumericalDuration}};
+ /// # use std::thread;
+ /// let instant = Instant::now();
+ /// thread::sleep(1.std_milliseconds());
+ /// assert!(instant.elapsed() >= 1.milliseconds());
+ /// ```
+ pub fn elapsed(self) -> Duration {
+ Self::now() - self
+ }
+ // endregion delegation
+
+ // region: checked arithmetic
+ /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
+ /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+ /// otherwise.
+ ///
+ /// ```rust
+ /// # use time::{Instant, ext::NumericalDuration};
+ /// let now = Instant::now();
+ /// assert_eq!(now.checked_add(5.seconds()), Some(now + 5.seconds()));
+ /// assert_eq!(now.checked_add((-5).seconds()), Some(now + (-5).seconds()));
+ /// ```
+ pub fn checked_add(self, duration: Duration) -> Option<Self> {
+ if duration.is_zero() {
+ Some(self)
+ } else if duration.is_positive() {
+ self.0.checked_add(duration.unsigned_abs()).map(Self)
+ } else {
+ debug_assert!(duration.is_negative());
+ self.0.checked_sub(duration.unsigned_abs()).map(Self)
+ }
+ }
+
+ /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
+ /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+ /// otherwise.
+ ///
+ /// ```rust
+ /// # use time::{Instant, ext::NumericalDuration};
+ /// let now = Instant::now();
+ /// assert_eq!(now.checked_sub(5.seconds()), Some(now - 5.seconds()));
+ /// assert_eq!(now.checked_sub((-5).seconds()), Some(now - (-5).seconds()));
+ /// ```
+ pub fn checked_sub(self, duration: Duration) -> Option<Self> {
+ if duration.is_zero() {
+ Some(self)
+ } else if duration.is_positive() {
+ self.0.checked_sub(duration.unsigned_abs()).map(Self)
+ } else {
+ debug_assert!(duration.is_negative());
+ self.0.checked_add(duration.unsigned_abs()).map(Self)
+ }
+ }
+ // endregion checked arithmetic
+
+ /// Obtain the inner [`std::time::Instant`].
+ ///
+ /// ```rust
+ /// # use time::Instant;
+ /// let now = Instant::now();
+ /// assert_eq!(now.into_inner(), now.0);
+ /// ```
+ pub const fn into_inner(self) -> StdInstant {
+ self.0
+ }
+}
+
+// region: trait impls
+impl From<StdInstant> for Instant {
+ fn from(instant: StdInstant) -> Self {
+ Self(instant)
+ }
+}
+
+impl From<Instant> for StdInstant {
+ fn from(instant: Instant) -> Self {
+ instant.0
+ }
+}
+
+impl Sub for Instant {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Self::Output {
+ match self.0.cmp(&other.0) {
+ Ordering::Equal => Duration::ZERO,
+ Ordering::Greater => (self.0 - other.0)
+ .try_into()
+ .expect("overflow converting `std::time::Duration` to `time::Duration`"),
+ Ordering::Less => -Duration::try_from(other.0 - self.0)
+ .expect("overflow converting `std::time::Duration` to `time::Duration`"),
+ }
+ }
+}
+
+impl Sub<StdInstant> for Instant {
+ type Output = Duration;
+
+ fn sub(self, other: StdInstant) -> Self::Output {
+ self - Self(other)
+ }
+}
+
+impl Sub<Instant> for StdInstant {
+ type Output = Duration;
+
+ fn sub(self, other: Instant) -> Self::Output {
+ Instant(self) - other
+ }
+}
+
+impl Add<Duration> for Instant {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ Self(self.0 + duration.unsigned_abs())
+ } else if duration.is_negative() {
+ Self(self.0 - duration.unsigned_abs())
+ } else {
+ debug_assert!(duration.is_zero());
+ self
+ }
+ }
+}
+
+impl Add<Duration> for StdInstant {
+ type Output = Self;
+
+ fn add(self, duration: Duration) -> Self::Output {
+ (Instant(self) + duration).0
+ }
+}
+
+impl Add<StdDuration> for Instant {
+ type Output = Self;
+
+ fn add(self, duration: StdDuration) -> Self::Output {
+ Self(self.0 + duration)
+ }
+}
+
+impl_add_assign!(Instant: Duration, StdDuration);
+impl_add_assign!(StdInstant: Duration);
+
+impl Sub<Duration> for Instant {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ if duration.is_positive() {
+ Self(self.0 - duration.unsigned_abs())
+ } else if duration.is_negative() {
+ Self(self.0 + duration.unsigned_abs())
+ } else {
+ debug_assert!(duration.is_zero());
+ self
+ }
+ }
+}
+
+impl Sub<Duration> for StdInstant {
+ type Output = Self;
+
+ fn sub(self, duration: Duration) -> Self::Output {
+ (Instant(self) - duration).0
+ }
+}
+
+impl Sub<StdDuration> for Instant {
+ type Output = Self;
+
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ Self(self.0 - duration)
+ }
+}
+
+impl_sub_assign!(Instant: Duration, StdDuration);
+impl_sub_assign!(StdInstant: Duration);
+
+impl PartialEq<StdInstant> for Instant {
+ fn eq(&self, rhs: &StdInstant) -> bool {
+ self.0.eq(rhs)
+ }
+}
+
+impl PartialEq<Instant> for StdInstant {
+ fn eq(&self, rhs: &Instant) -> bool {
+ self.eq(&rhs.0)
+ }
+}
+
+impl PartialOrd<StdInstant> for Instant {
+ fn partial_cmp(&self, rhs: &StdInstant) -> Option<Ordering> {
+ self.0.partial_cmp(rhs)
+ }
+}
+
+impl PartialOrd<Instant> for StdInstant {
+ fn partial_cmp(&self, rhs: &Instant) -> Option<Ordering> {
+ self.partial_cmp(&rhs.0)
+ }
+}
+
+impl AsRef<StdInstant> for Instant {
+ fn as_ref(&self) -> &StdInstant {
+ &self.0
+ }
+}
+
+impl Borrow<StdInstant> for Instant {
+ fn borrow(&self) -> &StdInstant {
+ &self.0
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/lib.rs b/third_party/rust/time/src/lib.rs
new file mode 100644
index 0000000000..b9868c1788
--- /dev/null
+++ b/third_party/rust/time/src/lib.rs
@@ -0,0 +1,357 @@
+//! # Feature flags
+//!
+//! This crate exposes a number of features. These can be enabled or disabled as shown
+//! [in Cargo's documentation](https://doc.rust-lang.org/cargo/reference/features.html). Features
+//! are _disabled_ by default unless otherwise noted.
+//!
+//! Reliance on a given feature is always indicated alongside the item definition.
+//!
+//! - `std` (_enabled by default, implicitly enables `alloc`_)
+//!
+//! This enables a number of features that depend on the standard library.
+//!
+//! - `alloc` (_enabled by default via `std`_)
+//!
+//! Enables a number of features that require the ability to dynamically allocate memory.
+//!
+//! - `macros`
+//!
+//! Enables macros that provide compile-time verification of values and intuitive syntax.
+//!
+//! - `formatting` (_implicitly enables `std`_)
+//!
+//! Enables formatting of most structs.
+//!
+//! - `parsing`
+//!
+//! Enables parsing of most structs.
+//!
+//! - `local-offset` (_implicitly enables `std`_)
+//!
+//! This feature enables a number of methods that allow obtaining the system's UTC offset.
+//!
+//! - `large-dates`
+//!
+//! By default, only years within the ±9999 range (inclusive) are supported. If you need support
+//! for years outside this range, consider enabling this feature; the supported range will be
+//! increased to ±999,999.
+//!
+//! Note that enabling this feature has some costs, as it means forgoing some optimizations.
+//! Ambiguities may be introduced when parsing that would not otherwise exist.
+//!
+//! - `serde`
+//!
+//! Enables [serde](https://docs.rs/serde) support for all types except [`Instant`].
+//!
+//! - `serde-human-readable` (_implicitly enables `serde`, `formatting`, and `parsing`_)
+//!
+//! Allows serde representations to use a human-readable format. This is determined by the
+//! serializer, not the user. If this feature is not enabled or if the serializer requests a
+//! non-human-readable format, a format optimized for binary representation will be used.
+//!
+//! Libraries should never enable this feature, as the decision of what format to use should be up
+//! to the user.
+//!
+//! - `serde-well-known` (_implicitly enables `serde-human-readable`_)
+//!
+//! _This feature flag is deprecated and will be removed in a future breaking release. Use the
+//! `serde-human-readable` feature instead._
+//!
+//! Enables support for serializing and deserializing well-known formats using serde's
+//! [`#[with]` attribute](https://serde.rs/field-attrs.html#with).
+//!
+//! - `rand`
+//!
+//! Enables [rand](https://docs.rs/rand) support for all types.
+//!
+//! - `quickcheck` (_implicitly enables `alloc`_)
+//!
+//! Enables [quickcheck](https://docs.rs/quickcheck) support for all types except [`Instant`].
+//!
+//! - `wasm-bindgen`
+//!
+//! 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(not(feature = "std"), no_std)]
+#![deny(
+ anonymous_parameters,
+ clippy::all,
+ clippy::alloc_instead_of_core,
+ clippy::explicit_auto_deref,
+ 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,
+ patterns_in_fns_without_body,
+ rust_2018_idioms,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unsafe_op_in_unsafe_fn,
+ unused_extern_crates,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::private_intra_doc_links
+)]
+#![warn(
+ clippy::dbg_macro,
+ clippy::decimal_literal_representation,
+ clippy::get_unwrap,
+ clippy::missing_docs_in_private_items,
+ clippy::nursery,
+ clippy::print_stdout,
+ clippy::todo,
+ clippy::unimplemented,
+ clippy::unnested_or_patterns,
+ clippy::unwrap_in_result,
+ clippy::unwrap_used,
+ clippy::use_debug,
+ deprecated_in_future,
+ missing_copy_implementations,
+ missing_debug_implementations,
+ unused_qualifications,
+ variant_size_differences
+)]
+#![allow(
+ clippy::redundant_pub_crate, // suggests bad style
+ clippy::option_if_let_else, // suggests terrible code
+ clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released
+ clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released
+)]
+#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
+#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
+#![doc(test(attr(deny(warnings))))]
+
+#[allow(unused_extern_crates)]
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+// region: macros
+/// Helper macro for easily implementing `OpAssign`.
+macro_rules! __impl_assign {
+ ($sym:tt $op:ident $fn:ident $target:ty : $($(#[$attr:meta])* $t:ty),+) => {$(
+ #[allow(unused_qualifications)]
+ $(#[$attr])*
+ impl core::ops::$op<$t> for $target {
+ fn $fn(&mut self, rhs: $t) {
+ *self = *self $sym rhs;
+ }
+ }
+ )+};
+}
+
+/// Implement `AddAssign` for the provided types.
+macro_rules! impl_add_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(+ AddAssign add_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `SubAssign` for the provided types.
+macro_rules! impl_sub_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(- SubAssign sub_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `MulAssign` for the provided types.
+macro_rules! impl_mul_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(* MulAssign mul_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Implement `DivAssign` for the provided types.
+macro_rules! impl_div_assign {
+ ($target:ty : $($(#[$attr:meta])* $t:ty),+ $(,)?) => {
+ __impl_assign!(/ DivAssign div_assign $target : $($(#[$attr])* $t),+);
+ };
+}
+
+/// Division of integers, rounding the resulting value towards negative infinity.
+macro_rules! div_floor {
+ ($a:expr, $b:expr) => {{
+ let _a = $a;
+ let _b = $b;
+
+ let (_quotient, _remainder) = (_a / _b, _a % _b);
+
+ if (_remainder > 0 && _b < 0) || (_remainder < 0 && _b > 0) {
+ _quotient - 1
+ } else {
+ _quotient
+ }
+ }};
+}
+
+/// Cascade an out-of-bounds value.
+macro_rules! cascade {
+ (@ordinal ordinal) => {};
+ (@year year) => {};
+
+ // Cascade an out-of-bounds value from "from" to "to".
+ ($from:ident in $min:literal.. $max:literal => $to:tt) => {
+ #[allow(unused_comparisons, unused_assignments)]
+ if $from >= $max {
+ $from -= $max - $min;
+ $to += 1;
+ } else if $from < $min {
+ $from += $max - $min;
+ $to -= 1;
+ }
+ };
+
+ // Special case the ordinal-to-year cascade, as it has different behavior.
+ ($ordinal:ident => $year:ident) => {
+ // We need to actually capture the idents. Without this, macro hygiene causes errors.
+ cascade!(@ordinal $ordinal);
+ cascade!(@year $year);
+ #[allow(unused_assignments)]
+ if $ordinal > crate::util::days_in_year($year) as i16 {
+ $ordinal -= crate::util::days_in_year($year) as i16;
+ $year += 1;
+ } else if $ordinal < 1 {
+ $year -= 1;
+ $ordinal += crate::util::days_in_year($year) as i16;
+ }
+ };
+}
+
+/// Returns `Err(error::ComponentRange)` if the value is not in range.
+macro_rules! ensure_value_in_range {
+ ($value:ident in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: false,
+ });
+ }
+ }};
+
+ ($value:ident conditionally in $start:expr => $end:expr) => {{
+ let _start = $start;
+ let _end = $end;
+ #[allow(trivial_numeric_casts, unused_comparisons)]
+ if $value < _start || $value > _end {
+ return Err(crate::error::ComponentRange {
+ name: stringify!($value),
+ minimum: _start as _,
+ maximum: _end as _,
+ value: $value as _,
+ conditional_range: true,
+ });
+ }
+ }};
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but does not perform `.into()`. Because of this, it is
+/// usable in `const` contexts.
+macro_rules! const_try {
+ ($e:expr) => {
+ match $e {
+ Ok(value) => value,
+ Err(error) => return Err(error),
+ }
+ };
+}
+
+/// Try to unwrap an expression, returning if not possible.
+///
+/// This is similar to the `?` operator, but is usable in `const` contexts.
+macro_rules! const_try_opt {
+ ($e:expr) => {
+ match $e {
+ Some(value) => value,
+ None => return None,
+ }
+ };
+}
+
+/// Try to unwrap an expression, panicking if not possible.
+///
+/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
+macro_rules! expect_opt {
+ ($e:expr, $message:literal) => {
+ match $e {
+ Some(value) => value,
+ None => crate::expect_failed($message),
+ }
+ };
+}
+// endregion macros
+
+mod date;
+mod duration;
+pub mod error;
+pub mod ext;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod format_description;
+#[cfg(feature = "formatting")]
+pub mod formatting;
+#[cfg(feature = "std")]
+mod instant;
+#[cfg(feature = "macros")]
+pub mod macros;
+mod month;
+mod offset_date_time;
+#[cfg(feature = "parsing")]
+pub mod parsing;
+mod primitive_date_time;
+#[cfg(feature = "quickcheck")]
+mod quickcheck;
+#[cfg(feature = "rand")]
+mod rand;
+#[cfg(feature = "serde")]
+#[allow(missing_copy_implementations, missing_debug_implementations)]
+pub mod serde;
+mod sys;
+#[cfg(test)]
+mod tests;
+mod time;
+mod utc_offset;
+pub mod util;
+mod weekday;
+
+pub use crate::date::Date;
+pub use crate::duration::Duration;
+pub use crate::error::Error;
+#[cfg(feature = "std")]
+pub use crate::instant::Instant;
+pub use crate::month::Month;
+pub use crate::offset_date_time::OffsetDateTime;
+pub use crate::primitive_date_time::PrimitiveDateTime;
+pub use crate::time::Time;
+pub use crate::utc_offset::UtcOffset;
+pub use crate::weekday::Weekday;
+
+/// An alias for [`std::result::Result`] with a generic error from the time crate.
+pub type Result<T> = core::result::Result<T, Error>;
+
+/// This is a separate function to reduce the code size of `expect_opt!`.
+#[inline(never)]
+#[cold]
+#[track_caller]
+const fn expect_failed(message: &str) -> ! {
+ panic!("{}", message)
+}
diff --git a/third_party/rust/time/src/macros.rs b/third_party/rust/time/src/macros.rs
new file mode 100644
index 0000000000..4f295e2ed8
--- /dev/null
+++ b/third_party/rust/time/src/macros.rs
@@ -0,0 +1,132 @@
+//! Macros to construct statically known values.
+
+/// Construct a [`Date`](crate::Date) with a statically known value.
+///
+/// The resulting expression can be used in `const` or `static` declarations.
+///
+/// Three formats are supported: year-week-weekday, year-ordinal, and year-month-day.
+///
+/// ```rust
+/// # use time::{Date, Weekday::*, Month, macros::date};
+/// assert_eq!(
+/// date!(2020 - W 01 - 3),
+/// Date::from_iso_week_date(2020, 1, Wednesday)?
+/// );
+/// assert_eq!(date!(2020 - 001), Date::from_ordinal_date(2020, 1)?);
+/// assert_eq!(
+/// date!(2020 - 01 - 01),
+/// Date::from_calendar_date(2020, Month::January, 1)?
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+pub use time_macros::date;
+/// Construct a [`PrimitiveDateTime`] or [`OffsetDateTime`] with a statically known value.
+///
+/// The resulting expression can be used in `const` or `static` declarations.
+///
+/// The syntax accepted by this macro is the same as [`date!`] and [`time!`], with an optional
+/// [`offset!`], all space-separated. If an [`offset!`] is provided, the resulting value will
+/// be an [`OffsetDateTime`]; otherwise it will be a [`PrimitiveDateTime`].
+///
+/// [`OffsetDateTime`]: crate::OffsetDateTime
+/// [`PrimitiveDateTime`]: crate::PrimitiveDateTime
+///
+/// ```rust
+/// # use time::{Date, Month, macros::datetime, UtcOffset};
+/// assert_eq!(
+/// datetime!(2020-01-01 0:00),
+/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
+/// );
+/// assert_eq!(
+/// datetime!(2020-01-01 0:00 UTC),
+/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().assume_utc()
+/// );
+/// assert_eq!(
+/// datetime!(2020-01-01 0:00 -1),
+/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
+/// .assume_offset(UtcOffset::from_hms(-1, 0, 0)?)
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+pub use time_macros::datetime;
+/// Equivalent of performing [`format_description::parse()`] at compile time.
+///
+/// Using the macro instead of the function results in a static slice rather than a [`Vec`],
+/// such that it can be used in `#![no_alloc]` situations.
+///
+/// The resulting expression can be used in `const` or `static` declarations, and implements
+/// the sealed traits required for both formatting and parsing.
+#[cfg_attr(feature = "alloc", doc = "```rust")]
+#[cfg_attr(not(feature = "alloc"), doc = "```rust,ignore")]
+/// # use time::{format_description, macros::format_description};
+/// assert_eq!(
+/// format_description!("[hour]:[minute]:[second]"),
+/// format_description::parse("[hour]:[minute]:[second]")?
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+///
+/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
+/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
+///
+/// [`format_description::parse()`]: crate::format_description::parse()
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub use time_macros::format_description;
+/// Construct a [`UtcOffset`](crate::UtcOffset) with a statically known value.
+///
+/// The resulting expression can be used in `const` or `static` declarations.
+///
+/// A sign and the hour must be provided; minutes and seconds default to zero. `UTC` (both
+/// uppercase and lowercase) is also allowed.
+///
+/// ```rust
+/// # use time::{UtcOffset, macros::offset};
+/// assert_eq!(offset!(UTC), UtcOffset::from_hms(0, 0, 0)?);
+/// assert_eq!(offset!(utc), UtcOffset::from_hms(0, 0, 0)?);
+/// assert_eq!(offset!(+0), UtcOffset::from_hms(0, 0, 0)?);
+/// assert_eq!(offset!(+1), UtcOffset::from_hms(1, 0, 0)?);
+/// assert_eq!(offset!(-1), UtcOffset::from_hms(-1, 0, 0)?);
+/// assert_eq!(offset!(+1:30), UtcOffset::from_hms(1, 30, 0)?);
+/// assert_eq!(offset!(-1:30), UtcOffset::from_hms(-1, -30, 0)?);
+/// assert_eq!(offset!(+1:30:59), UtcOffset::from_hms(1, 30, 59)?);
+/// assert_eq!(offset!(-1:30:59), UtcOffset::from_hms(-1, -30, -59)?);
+/// assert_eq!(offset!(+23:59:59), UtcOffset::from_hms(23, 59, 59)?);
+/// assert_eq!(offset!(-23:59:59), UtcOffset::from_hms(-23, -59, -59)?);
+/// # Ok::<_, time::Error>(())
+/// ```
+pub use time_macros::offset;
+/// Construct a [`Time`](crate::Time) with a statically known value.
+///
+/// The resulting expression can be used in `const` or `static` declarations.
+///
+/// Hours and minutes must be provided, while seconds defaults to zero. AM/PM is allowed
+/// (either uppercase or lowercase). Any number of subsecond digits may be provided (though any
+/// past nine will be discarded).
+///
+/// All components are validated at compile-time. An error will be raised if any value is
+/// invalid.
+///
+/// ```rust
+/// # use time::{Time, macros::time};
+/// assert_eq!(time!(0:00), Time::from_hms(0, 0, 0)?);
+/// assert_eq!(time!(1:02:03), Time::from_hms(1, 2, 3)?);
+/// assert_eq!(
+/// time!(1:02:03.004_005_006),
+/// Time::from_hms_nano(1, 2, 3, 4_005_006)?
+/// );
+/// assert_eq!(time!(12:00 am), Time::from_hms(0, 0, 0)?);
+/// assert_eq!(time!(1:02:03 am), Time::from_hms(1, 2, 3)?);
+/// assert_eq!(
+/// time!(1:02:03.004_005_006 am),
+/// Time::from_hms_nano(1, 2, 3, 4_005_006)?
+/// );
+/// assert_eq!(time!(12 pm), Time::from_hms(12, 0, 0)?);
+/// assert_eq!(time!(12:00 pm), Time::from_hms(12, 0, 0)?);
+/// assert_eq!(time!(1:02:03 pm), Time::from_hms(13, 2, 3)?);
+/// assert_eq!(
+/// time!(1:02:03.004_005_006 pm),
+/// Time::from_hms_nano(13, 2, 3, 4_005_006)?
+/// );
+/// # Ok::<_, time::Error>(())
+/// ```
+pub use time_macros::time;
diff --git a/third_party/rust/time/src/month.rs b/third_party/rust/time/src/month.rs
new file mode 100644
index 0000000000..0c657e9821
--- /dev/null
+++ b/third_party/rust/time/src/month.rs
@@ -0,0 +1,164 @@
+//! The `Month` enum and its associated `impl`s.
+
+use core::fmt;
+use core::num::NonZeroU8;
+use core::str::FromStr;
+
+use self::Month::*;
+use crate::error;
+
+/// Months of the year.
+#[allow(clippy::missing_docs_in_private_items)] // variants
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Month {
+ January = 1,
+ February = 2,
+ March = 3,
+ April = 4,
+ May = 5,
+ June = 6,
+ July = 7,
+ August = 8,
+ September = 9,
+ October = 10,
+ November = 11,
+ December = 12,
+}
+
+impl Month {
+ /// Create a `Month` from its numerical value.
+ pub(crate) const fn from_number(n: NonZeroU8) -> Result<Self, error::ComponentRange> {
+ match n.get() {
+ 1 => Ok(January),
+ 2 => Ok(February),
+ 3 => Ok(March),
+ 4 => Ok(April),
+ 5 => Ok(May),
+ 6 => Ok(June),
+ 7 => Ok(July),
+ 8 => Ok(August),
+ 9 => Ok(September),
+ 10 => Ok(October),
+ 11 => Ok(November),
+ 12 => Ok(December),
+ n => Err(error::ComponentRange {
+ name: "month",
+ minimum: 1,
+ maximum: 12,
+ value: n as _,
+ conditional_range: false,
+ }),
+ }
+ }
+
+ /// Get the previous month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// assert_eq!(Month::January.previous(), Month::December);
+ /// ```
+ pub const fn previous(self) -> Self {
+ match self {
+ January => December,
+ February => January,
+ March => February,
+ April => March,
+ May => April,
+ June => May,
+ July => June,
+ August => July,
+ September => August,
+ October => September,
+ November => October,
+ December => November,
+ }
+ }
+
+ /// Get the next month.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// assert_eq!(Month::January.next(), Month::February);
+ /// ```
+ pub const fn next(self) -> Self {
+ match self {
+ January => February,
+ February => March,
+ March => April,
+ April => May,
+ May => June,
+ June => July,
+ July => August,
+ August => September,
+ September => October,
+ October => November,
+ November => December,
+ December => January,
+ }
+ }
+}
+
+impl fmt::Display for Month {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ January => "January",
+ February => "February",
+ March => "March",
+ April => "April",
+ May => "May",
+ June => "June",
+ July => "July",
+ August => "August",
+ September => "September",
+ October => "October",
+ November => "November",
+ December => "December",
+ })
+ }
+}
+
+impl FromStr for Month {
+ type Err = error::InvalidVariant;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "January" => Ok(January),
+ "February" => Ok(February),
+ "March" => Ok(March),
+ "April" => Ok(April),
+ "May" => Ok(May),
+ "June" => Ok(June),
+ "July" => Ok(July),
+ "August" => Ok(August),
+ "September" => Ok(September),
+ "October" => Ok(October),
+ "November" => Ok(November),
+ "December" => Ok(December),
+ _ => Err(error::InvalidVariant),
+ }
+ }
+}
+
+impl From<Month> for u8 {
+ fn from(month: Month) -> Self {
+ month as _
+ }
+}
+
+impl TryFrom<u8> for Month {
+ type Error = error::ComponentRange;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match NonZeroU8::new(value) {
+ Some(value) => Self::from_number(value),
+ None => Err(error::ComponentRange {
+ name: "month",
+ minimum: 1,
+ maximum: 12,
+ value: 0,
+ conditional_range: false,
+ }),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/offset_date_time.rs b/third_party/rust/time/src/offset_date_time.rs
new file mode 100644
index 0000000000..c39a2b1d05
--- /dev/null
+++ b/third_party/rust/time/src/offset_date_time.rs
@@ -0,0 +1,1398 @@
+//! The [`OffsetDateTime`] struct and its associated `impl`s.
+
+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::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;
+#[cfg(feature = "parsing")]
+use crate::util;
+use crate::{error, Date, 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();
+
+/// 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,
+}
+
+impl OffsetDateTime {
+ /// Midnight, 1 January, 1970 (UTC).
+ ///
+ /// ```rust
+ /// # use time::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();
+
+ // region: now
+ /// Create a new `OffsetDateTime` with the current date and time in UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::offset;
+ /// assert!(OffsetDateTime::now_utc().year() >= 2019);
+ /// assert_eq!(OffsetDateTime::now_utc().offset(), offset!(UTC));
+ /// ```
+ #[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()
+ }
+
+ /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset.
+ /// If the offset cannot be determined, an error is returned.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # if false {
+ /// assert!(OffsetDateTime::now_local().is_ok());
+ /// # }
+ /// ```
+ #[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)?))
+ }
+ // endregion now
+
+ /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2000-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .year(),
+ /// 1999,
+ /// );
+ ///
+ /// // Let's see what time Sydney's new year's celebration is in New York and Los Angeles.
+ ///
+ /// // Construct midnight on new year's in Sydney.
+ /// let sydney = datetime!(2000-01-01 0:00 +11);
+ /// let new_york = sydney.to_offset(offset!(-5));
+ /// let los_angeles = sydney.to_offset(offset!(-8));
+ /// assert_eq!(sydney.hour(), 0);
+ /// assert_eq!(new_york.hour(), 8);
+ /// assert_eq!(los_angeles.hour(), 5);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// 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(),
+ ),
+ )
+ }
+
+ // region: constructors
+ /// Create an `OffsetDateTime` from the provided Unix timestamp. Calling `.offset()` on the
+ /// resulting value is guaranteed to return UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(0),
+ /// Ok(OffsetDateTime::UNIX_EPOCH),
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(1_546_300_800),
+ /// Ok(datetime!(2019-01-01 0:00 UTC)),
+ /// );
+ /// ```
+ ///
+ /// If you have a timestamp-nanosecond pair, you can use something along the lines of the
+ /// following:
+ ///
+ /// ```rust
+ /// # use time::{Duration, OffsetDateTime, ext::NumericalDuration};
+ /// let (timestamp, nanos) = (1, 500_000_000);
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp(timestamp)? + Duration::nanoseconds(nanos),
+ /// OffsetDateTime::UNIX_EPOCH + 1.5.seconds()
+ /// );
+ /// # 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())
+ }
+
+ /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling
+ /// `.offset()` on the resulting value is guaranteed to return UTC.
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp_nanos(0),
+ /// Ok(OffsetDateTime::UNIX_EPOCH),
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000),
+ /// Ok(datetime!(2019-01-01 0:00 UTC)),
+ /// );
+ /// ```
+ 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())
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the [`UtcOffset`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).offset(), offset!(UTC));
+ /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1));
+ /// ```
+ pub const fn offset(self) -> UtcOffset {
+ self.offset
+ }
+
+ /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp(), 0);
+ /// 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
+ }
+
+ /// Get the Unix timestamp in nanoseconds.
+ ///
+ /// ```rust
+ /// use time_macros::datetime;
+ /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos(), 0);
+ /// assert_eq!(
+ /// datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos(),
+ /// 3_600_000_000_000,
+ /// );
+ /// ```
+ pub const fn unix_timestamp_nanos(self) -> i128 {
+ self.unix_timestamp() as i128 * 1_000_000_000 + self.nanosecond() as i128
+ }
+
+ /// Get the [`Date`] in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).date(), date!(2019-01-01));
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .date(),
+ /// date!(2018-12-31),
+ /// );
+ /// ```
+ pub const fn date(self) -> Date {
+ self.local_datetime.date()
+ }
+
+ /// Get the [`Time`] in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset, time};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).time(), time!(0:00));
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC)
+ /// .to_offset(offset!(-1))
+ /// .time(),
+ /// time!(23:00)
+ /// );
+ /// ```
+ pub const fn time(self) -> Time {
+ self.local_datetime.time()
+ }
+
+ // region: date getters
+ /// Get the year of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).year(), 2019);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .year(),
+ /// 2020,
+ /// );
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.date().year()
+ }
+
+ /// Get the month of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).month(), Month::January);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .month(),
+ /// Month::January,
+ /// );
+ /// ```
+ pub const fn month(self) -> Month {
+ self.date().month()
+ }
+
+ /// Get the day of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).day(), 1);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .day(),
+ /// 1,
+ /// );
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.date().day()
+ }
+
+ /// Get the day of the year of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=366`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).ordinal(), 1);
+ /// assert_eq!(
+ /// datetime!(2019-12-31 23:00 UTC)
+ /// .to_offset(offset!(+1))
+ /// .ordinal(),
+ /// 1,
+ /// );
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ self.date().ordinal()
+ }
+
+ /// Get the ISO week number of the date in the stored offset.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).iso_week(), 53);
+ /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.date().iso_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).sunday_based_week(), 52);
+ /// 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()
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00 UTC).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00 UTC).monday_based_week(), 52);
+ /// 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()
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ self.date().to_calendar_date()
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_ordinal_date(),
+ /// (2019, 1)
+ /// );
+ /// ```
+ pub const fn to_ordinal_date(self) -> (i32, u16) {
+ self.date().to_ordinal_date()
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2019, 1, Tuesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-10-04 0:00 UTC).to_iso_week_date(),
+ /// (2019, 40, Friday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-12-31 0:00 UTC).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2021-01-01 0:00 UTC).to_iso_week_date(),
+ /// (2020, 53, Friday)
+ /// );
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ self.date().to_iso_week_date()
+ }
+
+ /// Get the weekday of the date in the stored offset.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-02-01 0:00 UTC).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ self.date().weekday()
+ }
+
+ /// Get the Julian day for the date. The time is not taken into account for this calculation.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(-4713-11-24 0:00 UTC).to_julian_day(), 0);
+ /// assert_eq!(datetime!(2000-01-01 0:00 UTC).to_julian_day(), 2_451_545);
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).to_julian_day(), 2_458_485);
+ /// 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()
+ }
+ // endregion date getters
+
+ // region: time getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00 UTC).to_hms(), (0, 0, 0));
+ /// 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()
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_milli(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999 UTC).to_hms_milli(),
+ /// (23, 59, 59, 999)
+ /// );
+ /// ```
+ pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) {
+ self.time().as_hms_milli()
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_micro(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999 UTC).to_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) {
+ self.time().as_hms_micro()
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00:00 UTC).to_hms_nano(),
+ /// (0, 0, 0, 0)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999_999 UTC).to_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) {
+ self.time().as_hms_nano()
+ }
+
+ /// Get the clock hour in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).hour(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(-2))
+ /// .hour(),
+ /// 21,
+ /// );
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.time().hour()
+ }
+
+ /// Get the minute within the hour in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).minute(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(+0:30))
+ /// .minute(),
+ /// 29,
+ /// );
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.time().minute()
+ }
+
+ /// Get the second within the minute in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).second(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59 UTC)
+ /// .to_offset(offset!(+0:00:30))
+ /// .second(),
+ /// 29,
+ /// );
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.time().second()
+ }
+
+ // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not
+ // change when adjusting for the offset.
+
+ /// Get the milliseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).millisecond(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ self.time().millisecond()
+ }
+
+ /// Get the microseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).microsecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999 UTC).microsecond(),
+ /// 999_999,
+ /// );
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.time().microsecond()
+ }
+
+ /// Get the nanoseconds within the second in the stored offset.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00 UTC).nanosecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999_999 UTC).nanosecond(),
+ /// 999_999_999,
+ /// );
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.time().nanosecond()
+ }
+ // endregion time getters
+ // endregion getters
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::{datetime, offset};
+ /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_add((-2).days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_add(2.days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).checked_add(27.hours()),
+ /// Some(datetime!(2019 - 11 - 26 18:30 +10))
+ /// );
+ /// ```
+ pub const fn checked_add(self, duration: Duration) -> Option<Self> {
+ Some(const_try_opt!(self.local_datetime.checked_add(duration)).assume_offset(self.offset))
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::{datetime, offset};
+ /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_sub(2.days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10));
+ /// assert_eq!(datetime.checked_sub((-2).days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).checked_sub(27.hours()),
+ /// Some(datetime!(2019 - 11 - 24 12:30 +10))
+ /// );
+ /// ```
+ pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
+ Some(const_try_opt!(self.local_datetime.checked_sub(duration)).assume_offset(self.offset))
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(-999999-01-01 0:00 +10).saturating_add((-2).days()),"
+ )]
+ #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10).saturating_add((-2).days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days()),"
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).saturating_add(27.hours()),
+ /// datetime!(2019 - 11 - 26 18:30 +10)
+ /// );
+ /// ```
+ 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)
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(-999999-01-01 0:00 +10).saturating_sub(2.days()),"
+ )]
+ #[cfg_attr(feature = "large-dates", doc = " datetime!(-999999-01-01 0:00 +10)")]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10).saturating_sub(2.days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(-9999-01-01 0:00 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days()),"
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days()),"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)"
+ )]
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30 +10).saturating_sub(27.hours()),
+ /// datetime!(2019 - 11 - 24 12:30 +10)
+ /// );
+ /// ```
+ 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)
+ }
+ }
+ // endregion: saturating arithmetic
+}
+
+// region: replacement
+/// Methods that replace part of the `OffsetDateTime`.
+impl OffsetDateTime {
+ /// Replace the time, which is assumed to be in the stored offset. The date and offset
+ /// components are unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, time};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00)),
+ /// datetime!(2020-01-01 12:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00)),
+ /// datetime!(2020-01-01 7:00 -5)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00)),
+ /// datetime!(2020-01-01 12:00 +1)
+ /// );
+ /// ```
+ #[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)
+ }
+
+ /// Replace the date, which is assumed to be in the stored offset. The time and offset
+ /// components are unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, date};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 12:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 0:00 +1)
+ /// );
+ /// ```
+ #[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)
+ }
+
+ /// Replace the date and time, which are assumed to be in the stored offset. The offset
+ /// component remains unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00)),
+ /// datetime!(2020-01-30 16:00 UTC)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00)),
+ /// datetime!(2020-01-30 0:00 +1)
+ /// );
+ /// ```
+ #[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)
+ }
+
+ /// Replace the offset. The date and time components remain unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5)),
+ /// datetime!(2020-01-01 0:00 -5)
+ /// );
+ /// ```
+ #[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)
+ }
+
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_year(2019),
+ /// Ok(datetime!(2019 - 02 - 18 12:00 +01))
+ /// );
+ /// 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
+ /// 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))
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_month(Month::January),
+ /// Ok(datetime!(2022 - 01 - 18 12:00 +01))
+ /// );
+ /// 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))
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00 +01).replace_day(1),
+ /// Ok(datetime!(2022 - 02 - 01 12:00 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(0).is_err()); // 00 isn't a valid day
+ /// 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))
+ }
+
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(7),
+ /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006 +01))
+ /// );
+ /// 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))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006 +01))
+ /// );
+ /// 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))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006 +01))
+ /// );
+ /// 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))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(
+ const_try!(self.local_datetime.replace_millisecond(millisecond))
+ .assume_offset(self.offset),
+ )
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(7_008),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008 +01))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(
+ const_try!(self.local_datetime.replace_microsecond(microsecond))
+ .assume_offset(self.offset),
+ )
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(7_008_009),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009 +01))
+ /// );
+ /// 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),
+ )
+ }
+}
+// endregion replacement
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl OffsetDateTime {
+ /// Format the `OffsetDateTime` using the provided [format
+ /// description](crate::format_description).
+ 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()),
+ Some(self.offset),
+ )
+ }
+
+ /// Format the `OffsetDateTime` using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::datetime;
+ /// let format = format_description::parse(
+ /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
+ /// sign:mandatory]:[offset_minute]:[offset_second]",
+ /// )?;
+ /// assert_eq!(
+ /// datetime!(2020-01-02 03:04:05 +06:07:08).format(&format)?,
+ /// "2020-01-02 03:04:05 +06:07:08"
+ /// );
+ /// # 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))
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl OffsetDateTime {
+ /// Parse an `OffsetDateTime` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::OffsetDateTime;
+ /// # use time_macros::{datetime, format_description};
+ /// let format = format_description!(
+ /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
+ /// sign:mandatory]:[offset_minute]:[offset_second]"
+ /// );
+ /// assert_eq!(
+ /// OffsetDateTime::parse("2020-01-02 03:04:05 +06:07:08", &format)?,
+ /// datetime!(2020-01-02 03:04:05 +06:07:08)
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ 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())
+ }
+}
+
+impl fmt::Display for OffsetDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {} {}", self.date(), self.time(), self.offset)
+ }
+}
+
+impl fmt::Debug for OffsetDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// 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 PartialOrd for OffsetDateTime {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(self.cmp(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 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<T> Add<T> for OffsetDateTime
+where
+ PrimitiveDateTime: Add<T, Output = PrimitiveDateTime>,
+{
+ type Output = Self;
+
+ fn add(self, rhs: T) -> Self::Output {
+ (self.local_datetime + rhs).assume_offset(self.offset)
+ }
+}
+
+impl_add_assign!(OffsetDateTime: Duration, StdDuration);
+
+impl<T> Sub<T> for OffsetDateTime
+where
+ PrimitiveDateTime: Sub<T, Output = PrimitiveDateTime>,
+{
+ type Output = Self;
+
+ fn sub(self, rhs: T) -> Self::Output {
+ (self.local_datetime - rhs).assume_offset(self.offset)
+ }
+}
+
+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
+ }
+}
+
+#[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 {
+ (OffsetDateTime::from(self) - duration).into()
+ }
+}
+
+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)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Sub<OffsetDateTime> for SystemTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: OffsetDateTime) -> Self::Output {
+ OffsetDateTime::from(self) - rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<SystemTime> for OffsetDateTime {
+ fn eq(&self, rhs: &SystemTime) -> bool {
+ self == &Self::from(*rhs)
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialEq<OffsetDateTime> for SystemTime {
+ fn eq(&self, rhs: &OffsetDateTime) -> bool {
+ &OffsetDateTime::from(*self) == rhs
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<SystemTime> for OffsetDateTime {
+ fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> {
+ self.partial_cmp(&Self::from(*other))
+ }
+}
+
+#[cfg(feature = "std")]
+impl PartialOrd<OffsetDateTime> for SystemTime {
+ fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
+ OffsetDateTime::from(*self).partial_cmp(other)
+ }
+}
+
+#[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(),
+ }
+ }
+}
+
+#[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()
+ }
+ }
+}
+
+#[allow(clippy::fallible_impl_from)]
+#[cfg(all(
+ target_arch = "wasm32",
+ 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")
+ }
+}
+
+#[cfg(all(
+ target_arch = "wasm32",
+ 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(&timestamp.into())
+ }
+}
+
+// endregion trait impls
diff --git a/third_party/rust/time/src/parsing/combinator/mod.rs b/third_party/rust/time/src/parsing/combinator/mod.rs
new file mode 100644
index 0000000000..3b4bc7a81b
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/mod.rs
@@ -0,0 +1,192 @@
+//! Implementations of the low-level parser combinators.
+
+pub(crate) mod rfc;
+
+use crate::format_description::modifier::Padding;
+use crate::parsing::shim::{Integer, IntegerParseBytes};
+use crate::parsing::ParsedItem;
+
+/// Parse a "+" or "-" sign. Returns the ASCII byte representing the sign, if present.
+pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ match input {
+ [sign @ (b'-' | b'+'), remaining @ ..] => Some(ParsedItem(remaining, *sign)),
+ _ => None,
+ }
+}
+
+/// Consume the first matching item, returning its associated value.
+pub(crate) fn first_match<'a, T>(
+ options: impl IntoIterator<Item = (&'a [u8], T)>,
+ case_sensitive: bool,
+) -> impl FnMut(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ let mut options = options.into_iter();
+ move |input| {
+ options.find_map(|(expected, t)| {
+ if case_sensitive {
+ Some(ParsedItem(input.strip_prefix(expected)?, t))
+ } else {
+ let n = expected.len();
+ if n <= input.len() {
+ let (head, tail) = input.split_at(n);
+ if head.eq_ignore_ascii_case(expected) {
+ return Some(ParsedItem(tail, t));
+ }
+ }
+ None
+ }
+ })
+ }
+}
+
+/// Consume zero or more instances of the provided parser. The parser must return the unit value.
+pub(crate) fn zero_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
+ parser: P,
+) -> impl FnMut(&'a [u8]) -> ParsedItem<'a, ()> {
+ move |mut input| {
+ while let Some(remaining) = parser(input) {
+ input = remaining.into_inner();
+ }
+ ParsedItem(input, ())
+ }
+}
+
+/// Consume one of or more instances of the provided parser. The parser must produce the unit value.
+pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
+ parser: P,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>> {
+ move |mut input| {
+ input = parser(input)?.into_inner();
+ while let Some(remaining) = parser(input) {
+ input = remaining.into_inner();
+ }
+ Some(ParsedItem(input, ()))
+ }
+}
+
+/// Consume between `n` and `m` instances of the provided parser.
+pub(crate) fn n_to_m<
+ 'a,
+ const N: u8,
+ const M: u8,
+ T,
+ P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
+>(
+ parser: P,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> {
+ debug_assert!(M >= N);
+ move |mut input| {
+ // We need to keep this to determine the total length eventually consumed.
+ let orig_input = input;
+
+ // Mandatory
+ for _ in 0..N {
+ input = parser(input)?.0;
+ }
+
+ // Optional
+ for _ in N..M {
+ match parser(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+
+ Some(ParsedItem(
+ input,
+ &orig_input[..(orig_input.len() - input.len())],
+ ))
+ }
+}
+
+/// Consume between `n` and `m` digits, returning the numerical value.
+pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T: Integer>(
+ input: &[u8],
+) -> Option<ParsedItem<'_, T>> {
+ debug_assert!(M >= N);
+ n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
+}
+
+/// Consume exactly `n` digits, returning the numerical value.
+pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
+ n_to_m_digits::<N, N, _>(input)
+}
+
+/// Consume exactly `n` digits, returning the numerical value.
+pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>(
+ padding: Padding,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ n_to_m_digits_padded::<N, N, _>(padding)
+}
+
+/// Consume between `n` and `m` digits, returning the numerical value.
+pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>(
+ padding: Padding,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ debug_assert!(M >= N);
+ move |mut input| match padding {
+ Padding::None => n_to_m_digits::<1, M, _>(input),
+ Padding::Space => {
+ debug_assert!(N > 0);
+
+ let mut orig_input = input;
+ for _ in 0..(N - 1) {
+ match ascii_char::<b' '>(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+ let pad_width = (orig_input.len() - input.len()) as u8;
+
+ orig_input = input;
+ for _ in 0..(N - pad_width) {
+ input = any_digit(input)?.0;
+ }
+ for _ in N..M {
+ match any_digit(input) {
+ Some(parsed) => input = parsed.0,
+ None => break,
+ }
+ }
+
+ ParsedItem(input, &orig_input[..(orig_input.len() - input.len())])
+ .flat_map(|value| value.parse_bytes())
+ }
+ Padding::Zero => n_to_m_digits::<N, M, _>(input),
+ }
+}
+
+/// Consume exactly one digit.
+pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ match input {
+ [c, remaining @ ..] if c.is_ascii_digit() => Some(ParsedItem(remaining, *c)),
+ _ => None,
+ }
+}
+
+/// Consume exactly one of the provided ASCII characters.
+pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
+ match input {
+ [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())),
+ _ => None,
+ }
+}
+
+/// Consume exactly one of the provided ASCII characters, case-insensitive.
+pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
+ match input {
+ [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())),
+ _ => None,
+ }
+}
+
+/// Optionally consume an input with a given parser.
+pub(crate) fn opt<'a, T>(
+ parser: impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
+) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
+ move |input| match parser(input) {
+ Some(value) => value.map(Some),
+ None => ParsedItem(input, None),
+ }
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs
new file mode 100644
index 0000000000..613a9057ff
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs
@@ -0,0 +1,173 @@
+//! Rules defined in [ISO 8601].
+//!
+//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
+
+use core::num::{NonZeroU16, NonZeroU8};
+
+use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign};
+use crate::parsing::ParsedItem;
+use crate::{Month, Weekday};
+
+/// What kind of format is being parsed. This is used to ensure each part of the format (date, time,
+/// offset) is the same kind.
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum ExtendedKind {
+ /// The basic format.
+ Basic,
+ /// The extended format.
+ Extended,
+ /// ¯\_(ツ)_/¯
+ Unknown,
+}
+
+impl ExtendedKind {
+ /// Is it possible that the format is extended?
+ pub(crate) const fn maybe_extended(self) -> bool {
+ matches!(self, Self::Extended | Self::Unknown)
+ }
+
+ /// Is the format known for certain to be extended?
+ pub(crate) const fn is_extended(self) -> bool {
+ matches!(self, Self::Extended)
+ }
+
+ /// If the kind is `Unknown`, make it `Basic`. Otherwise, do nothing. Returns `Some` if and only
+ /// if the kind is now `Basic`.
+ pub(crate) fn coerce_basic(&mut self) -> Option<()> {
+ match self {
+ Self::Basic => Some(()),
+ Self::Extended => None,
+ Self::Unknown => {
+ *self = Self::Basic;
+ Some(())
+ }
+ }
+ }
+
+ /// If the kind is `Unknown`, make it `Extended`. Otherwise, do nothing. Returns `Some` if and
+ /// only if the kind is now `Extended`.
+ pub(crate) fn coerce_extended(&mut self) -> Option<()> {
+ match self {
+ Self::Basic => None,
+ Self::Extended => Some(()),
+ Self::Unknown => {
+ *self = Self::Extended;
+ Some(())
+ }
+ }
+ }
+}
+
+/// Parse a possibly expanded year.
+pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> {
+ Some(match sign(input) {
+ Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| {
+ let val = val as i32;
+ if sign == b'-' { -val } else { val }
+ }),
+ None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _),
+ })
+}
+
+/// Parse a month.
+pub(crate) fn month(input: &[u8]) -> Option<ParsedItem<'_, Month>> {
+ first_match(
+ [
+ (b"01".as_slice(), Month::January),
+ (b"02".as_slice(), Month::February),
+ (b"03".as_slice(), Month::March),
+ (b"04".as_slice(), Month::April),
+ (b"05".as_slice(), Month::May),
+ (b"06".as_slice(), Month::June),
+ (b"07".as_slice(), Month::July),
+ (b"08".as_slice(), Month::August),
+ (b"09".as_slice(), Month::September),
+ (b"10".as_slice(), Month::October),
+ (b"11".as_slice(), Month::November),
+ (b"12".as_slice(), Month::December),
+ ],
+ true,
+ )(input)
+}
+
+/// Parse a week number.
+pub(crate) fn week(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the month.
+pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the week.
+pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> {
+ first_match(
+ [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"7".as_slice(), Weekday::Sunday),
+ ],
+ true,
+ )(input)
+}
+
+/// Parse a day of the year.
+pub(crate) fn dayo(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU16>> {
+ exactly_n_digits::<3, _>(input)
+}
+
+/// Parse the hour.
+pub(crate) fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse the minute.
+pub(crate) fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a floating point number as its integer and optional fractional parts.
+///
+/// The number must have two digits before the decimal point. If a decimal point is present, at
+/// least one digit must follow.
+///
+/// The return type is a tuple of the integer part and optional fraction part.
+pub(crate) fn float(input: &[u8]) -> Option<ParsedItem<'_, (u8, Option<f64>)>> {
+ // Two digits before the decimal.
+ let ParsedItem(input, integer_part) = match input {
+ [
+ first_digit @ b'0'..=b'9',
+ second_digit @ b'0'..=b'9',
+ input @ ..,
+ ] => ParsedItem(input, (first_digit - b'0') * 10 + (second_digit - b'0')),
+ _ => return None,
+ };
+
+ if let Some(ParsedItem(input, ())) = decimal_sign(input) {
+ // Mandatory post-decimal digit.
+ let ParsedItem(mut input, mut fractional_part) =
+ any_digit(input)?.map(|digit| ((digit - b'0') as f64) / 10.);
+
+ let mut divisor = 10.;
+ // Any number of subsequent digits.
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ input = new_input;
+ divisor *= 10.;
+ fractional_part += (digit - b'0') as f64 / divisor;
+ }
+
+ Some(ParsedItem(input, (integer_part, Some(fractional_part))))
+ } else {
+ Some(ParsedItem(input, (integer_part, None)))
+ }
+}
+
+/// Parse a "decimal sign", which is either a comma or a period.
+fn decimal_sign(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ ascii_char::<b'.'>(input).or_else(|| ascii_char::<b','>(input))
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/mod.rs b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs
new file mode 100644
index 0000000000..2974a4d5c4
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs
@@ -0,0 +1,10 @@
+//! Combinators for rules as defined in a standard.
+//!
+//! When applicable, these rules have been converted strictly following the ABNF syntax as specified
+//! in [RFC 2234].
+//!
+//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
+
+pub(crate) mod iso8601;
+pub(crate) mod rfc2234;
+pub(crate) mod rfc2822;
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs
new file mode 100644
index 0000000000..6753444358
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs
@@ -0,0 +1,13 @@
+//! Rules defined in [RFC 2234].
+//!
+//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
+
+use crate::parsing::ParsedItem;
+
+/// Consume exactly one space or tab.
+pub(crate) const fn wsp(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ match input {
+ [b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => None,
+ }
+}
diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs
new file mode 100644
index 0000000000..8410de06e3
--- /dev/null
+++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs
@@ -0,0 +1,115 @@
+//! Rules defined in [RFC 2822].
+//!
+//! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
+
+use crate::parsing::combinator::rfc::rfc2234::wsp;
+use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more};
+use crate::parsing::ParsedItem;
+
+/// Consume the `fws` rule.
+// The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/
+pub(crate) fn fws(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ if let [b'\r', b'\n', rest @ ..] = input {
+ one_or_more(wsp)(rest)
+ } else {
+ input = one_or_more(wsp)(input)?.into_inner();
+ while let [b'\r', b'\n', rest @ ..] = input {
+ input = one_or_more(wsp)(rest)?.into_inner();
+ }
+ Some(ParsedItem(input, ()))
+ }
+}
+
+/// Consume the `cfws` rule.
+// The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty.
+pub(crate) fn cfws(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ one_or_more(|input| fws(input).or_else(|| comment(input)))(input)
+}
+
+/// Consume the `comment` rule.
+fn comment(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'('>(input)?.into_inner();
+ input = zero_or_more(fws)(input).into_inner();
+ while let Some(rest) = ccontent(input) {
+ input = rest.into_inner();
+ input = zero_or_more(fws)(input).into_inner();
+ }
+ input = ascii_char::<b')'>(input)?.into_inner();
+
+ Some(ParsedItem(input, ()))
+}
+
+/// Consume the `ccontent` rule.
+fn ccontent(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ ctext(input)
+ .or_else(|| quoted_pair(input))
+ .or_else(|| comment(input))
+}
+
+/// Consume the `ctext` rule.
+#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+fn ctext(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ no_ws_ctl(input).or_else(|| match input {
+ [33..=39 | 42..=91 | 93..=126, rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => None,
+ })
+}
+
+/// Consume the `quoted_pair` rule.
+fn quoted_pair(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'\\'>(input)?.into_inner();
+
+ let old_input_len = input.len();
+
+ input = text(input).into_inner();
+
+ // If nothing is parsed, this means we hit the `obs-text` rule and nothing matched. This is
+ // technically a success, but we should still check the `obs-qp` rule to ensure we consume
+ // everything possible.
+ if input.len() == old_input_len {
+ match input {
+ [0..=127, rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => Some(ParsedItem(input, ())),
+ }
+ } else {
+ Some(ParsedItem(input, ()))
+ }
+}
+
+/// Consume the `no_ws_ctl` rule.
+const fn no_ws_ctl(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ match input {
+ [1..=8 | 11..=12 | 14..=31 | 127, rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => None,
+ }
+}
+
+/// Consume the `text` rule.
+fn text<'a>(input: &'a [u8]) -> ParsedItem<'a, ()> {
+ let new_text = |input: &'a [u8]| match input {
+ [1..=9 | 11..=12 | 14..=127, rest @ ..] => Some(ParsedItem(rest, ())),
+ _ => None,
+ };
+
+ let obs_char = |input: &'a [u8]| match input {
+ // This is technically allowed, but consuming this would mean the rest of the string is
+ // eagerly consumed without consideration for where the comment actually ends.
+ [b')', ..] => None,
+ [0..=9 | 11..=12 | 14..=127, rest @ ..] => Some(rest),
+ _ => None,
+ };
+
+ let obs_text = |mut input| {
+ input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner();
+ input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner();
+ while let Some(rest) = obs_char(input) {
+ input = rest;
+ input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner();
+ input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner();
+ }
+
+ ParsedItem(input, ())
+ };
+
+ new_text(input).unwrap_or_else(|| obs_text(input))
+}
diff --git a/third_party/rust/time/src/parsing/component.rs b/third_party/rust/time/src/parsing/component.rs
new file mode 100644
index 0000000000..38d632a0da
--- /dev/null
+++ b/third_party/rust/time/src/parsing/component.rs
@@ -0,0 +1,296 @@
+//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
+
+use core::num::{NonZeroU16, NonZeroU8};
+
+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,
+};
+use crate::parsing::ParsedItem;
+use crate::{Month, Weekday};
+
+// region: date components
+/// Parse the "year" component of a `Date`.
+pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<ParsedItem<'_, i32>> {
+ match modifiers.repr {
+ modifier::YearRepr::Full => {
+ let ParsedItem(input, sign) = opt(sign)(input);
+ #[cfg(not(feature = "large-dates"))]
+ let ParsedItem(input, year) =
+ exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
+ #[cfg(feature = "large-dates")]
+ let ParsedItem(input, year) =
+ n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
+ match sign {
+ Some(b'-') => Some(ParsedItem(input, -(year as i32))),
+ None if modifiers.sign_is_mandatory || year >= 10_000 => None,
+ _ => Some(ParsedItem(input, year as i32)),
+ }
+ }
+ modifier::YearRepr::LastTwo => {
+ Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32))
+ }
+ }
+}
+
+/// Parse the "month" component of a `Date`.
+pub(crate) fn parse_month(
+ input: &[u8],
+ modifiers: modifier::Month,
+) -> Option<ParsedItem<'_, Month>> {
+ use Month::*;
+ let ParsedItem(remaining, value) = first_match(
+ match modifiers.repr {
+ modifier::MonthRepr::Numerical => {
+ return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
+ .flat_map(|n| Month::from_number(n).ok());
+ }
+ modifier::MonthRepr::Long => [
+ (b"January".as_slice(), January),
+ (b"February".as_slice(), February),
+ (b"March".as_slice(), March),
+ (b"April".as_slice(), April),
+ (b"May".as_slice(), May),
+ (b"June".as_slice(), June),
+ (b"July".as_slice(), July),
+ (b"August".as_slice(), August),
+ (b"September".as_slice(), September),
+ (b"October".as_slice(), October),
+ (b"November".as_slice(), November),
+ (b"December".as_slice(), December),
+ ],
+ modifier::MonthRepr::Short => [
+ (b"Jan".as_slice(), January),
+ (b"Feb".as_slice(), February),
+ (b"Mar".as_slice(), March),
+ (b"Apr".as_slice(), April),
+ (b"May".as_slice(), May),
+ (b"Jun".as_slice(), June),
+ (b"Jul".as_slice(), July),
+ (b"Aug".as_slice(), August),
+ (b"Sep".as_slice(), September),
+ (b"Oct".as_slice(), October),
+ (b"Nov".as_slice(), November),
+ (b"Dec".as_slice(), December),
+ ],
+ },
+ modifiers.case_sensitive,
+ )(input)?;
+ Some(ParsedItem(remaining, value))
+}
+
+/// Parse the "week number" component of a `Date`.
+pub(crate) fn parse_week_number(
+ input: &[u8],
+ modifiers: modifier::WeekNumber,
+) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "weekday" component of a `Date`.
+pub(crate) fn parse_weekday(
+ input: &[u8],
+ modifiers: modifier::Weekday,
+) -> Option<ParsedItem<'_, Weekday>> {
+ first_match(
+ match (modifiers.repr, modifiers.one_indexed) {
+ (modifier::WeekdayRepr::Short, _) => [
+ (b"Mon".as_slice(), Weekday::Monday),
+ (b"Tue".as_slice(), Weekday::Tuesday),
+ (b"Wed".as_slice(), Weekday::Wednesday),
+ (b"Thu".as_slice(), Weekday::Thursday),
+ (b"Fri".as_slice(), Weekday::Friday),
+ (b"Sat".as_slice(), Weekday::Saturday),
+ (b"Sun".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Long, _) => [
+ (b"Monday".as_slice(), Weekday::Monday),
+ (b"Tuesday".as_slice(), Weekday::Tuesday),
+ (b"Wednesday".as_slice(), Weekday::Wednesday),
+ (b"Thursday".as_slice(), Weekday::Thursday),
+ (b"Friday".as_slice(), Weekday::Friday),
+ (b"Saturday".as_slice(), Weekday::Saturday),
+ (b"Sunday".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Sunday, false) => [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"0".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Sunday, true) => [
+ (b"2".as_slice(), Weekday::Monday),
+ (b"3".as_slice(), Weekday::Tuesday),
+ (b"4".as_slice(), Weekday::Wednesday),
+ (b"5".as_slice(), Weekday::Thursday),
+ (b"6".as_slice(), Weekday::Friday),
+ (b"7".as_slice(), Weekday::Saturday),
+ (b"1".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Monday, false) => [
+ (b"0".as_slice(), Weekday::Monday),
+ (b"1".as_slice(), Weekday::Tuesday),
+ (b"2".as_slice(), Weekday::Wednesday),
+ (b"3".as_slice(), Weekday::Thursday),
+ (b"4".as_slice(), Weekday::Friday),
+ (b"5".as_slice(), Weekday::Saturday),
+ (b"6".as_slice(), Weekday::Sunday),
+ ],
+ (modifier::WeekdayRepr::Monday, true) => [
+ (b"1".as_slice(), Weekday::Monday),
+ (b"2".as_slice(), Weekday::Tuesday),
+ (b"3".as_slice(), Weekday::Wednesday),
+ (b"4".as_slice(), Weekday::Thursday),
+ (b"5".as_slice(), Weekday::Friday),
+ (b"6".as_slice(), Weekday::Saturday),
+ (b"7".as_slice(), Weekday::Sunday),
+ ],
+ },
+ modifiers.case_sensitive,
+ )(input)
+}
+
+/// Parse the "ordinal" component of a `Date`.
+pub(crate) fn parse_ordinal(
+ input: &[u8],
+ modifiers: modifier::Ordinal,
+) -> Option<ParsedItem<'_, NonZeroU16>> {
+ exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
+}
+
+/// Parse the "day" component of a `Date`.
+pub(crate) fn parse_day(
+ input: &[u8],
+ modifiers: modifier::Day,
+) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+// endregion date components
+
+// region: time components
+/// Indicate whether the hour is "am" or "pm".
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Period {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Am,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Pm,
+}
+
+/// Parse the "hour" component of a `Time`.
+pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "minute" component of a `Time`.
+pub(crate) fn parse_minute(
+ input: &[u8],
+ modifiers: modifier::Minute,
+) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "second" component of a `Time`.
+pub(crate) fn parse_second(
+ input: &[u8],
+ modifiers: modifier::Second,
+) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
+}
+
+/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
+pub(crate) fn parse_period(
+ input: &[u8],
+ modifiers: modifier::Period,
+) -> Option<ParsedItem<'_, Period>> {
+ first_match(
+ if modifiers.is_uppercase {
+ [
+ (b"AM".as_slice(), Period::Am),
+ (b"PM".as_slice(), Period::Pm),
+ ]
+ } else {
+ [
+ (b"am".as_slice(), Period::Am),
+ (b"pm".as_slice(), Period::Pm),
+ ]
+ },
+ modifiers.case_sensitive,
+ )(input)
+}
+
+/// Parse the "subsecond" component of a `Time`.
+pub(crate) fn parse_subsecond(
+ input: &[u8],
+ modifiers: modifier::Subsecond,
+) -> Option<ParsedItem<'_, u32>> {
+ use modifier::SubsecondDigits::*;
+ Some(match modifiers.digits {
+ One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
+ Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
+ Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
+ Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
+ Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
+ Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
+ Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
+ Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
+ Nine => exactly_n_digits::<9, _>(input)?,
+ OneOrMore => {
+ let ParsedItem(mut input, mut value) =
+ any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ input = new_input;
+ multiplier /= 10;
+ }
+
+ ParsedItem(input, value)
+ }
+ })
+}
+// endregion time components
+
+// region: offset components
+/// Parse the "hour" component of a `UtcOffset`.
+pub(crate) fn parse_offset_hour(
+ input: &[u8],
+ modifiers: modifier::OffsetHour,
+) -> Option<ParsedItem<'_, i8>> {
+ 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))),
+ None if modifiers.sign_is_mandatory => None,
+ _ => Some(ParsedItem(input, hour as i8)),
+ }
+}
+
+/// Parse the "minute" component of a `UtcOffset`.
+pub(crate) fn parse_offset_minute(
+ input: &[u8],
+ modifiers: modifier::OffsetMinute,
+) -> Option<ParsedItem<'_, i8>> {
+ Some(
+ exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
+ .map(|offset_minute| offset_minute as _),
+ )
+}
+
+/// Parse the "second" component of a `UtcOffset`.
+pub(crate) fn parse_offset_second(
+ input: &[u8],
+ modifiers: modifier::OffsetSecond,
+) -> Option<ParsedItem<'_, i8>> {
+ Some(
+ exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
+ .map(|offset_second| offset_second as _),
+ )
+}
+// endregion offset components
diff --git a/third_party/rust/time/src/parsing/iso8601.rs b/third_party/rust/time/src/parsing/iso8601.rs
new file mode 100644
index 0000000000..21c89bab3f
--- /dev/null
+++ b/third_party/rust/time/src/parsing/iso8601.rs
@@ -0,0 +1,308 @@
+//! Parse parts of an ISO 8601-formatted value.
+
+use crate::error;
+use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::Iso8601;
+use crate::parsing::combinator::rfc::iso8601::{
+ day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
+};
+use crate::parsing::combinator::{ascii_char, sign};
+use crate::parsing::{Parsed, ParsedItem};
+
+impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
+ // Basic: [year][month][day]
+ // Extended: [year]["-"][month]["-"][day]
+ // Basic: [year][dayo]
+ // Extended: [year]["-"][dayo]
+ // Basic: [year]["W"][week][dayk]
+ // Extended: [year]["-"]["W"][week]["-"][dayk]
+ /// Parse a date in the basic or extended format. Reduced precision is permitted.
+ pub(crate) fn parse_date<'a>(
+ parsed: &'a mut Parsed,
+ extended_kind: &'a mut ExtendedKind,
+ ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
+ move |input| {
+ // Same for any acceptable format.
+ let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?;
+ *extended_kind = match ascii_char::<b'-'>(input) {
+ Some(ParsedItem(new_input, ())) => {
+ input = new_input;
+ ExtendedKind::Extended
+ }
+ None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
+ };
+
+ let mut ret_error = match (|| {
+ let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
+ if extended_kind.is_extended() {
+ input = ascii_char::<b'-'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ }
+ let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
+ Ok(ParsedItem(input, (month, day)))
+ })() {
+ Ok(ParsedItem(input, (month, day))) => {
+ *parsed = parsed
+ .with_year(year)
+ .ok_or(InvalidComponent("year"))?
+ .with_month(month)
+ .ok_or(InvalidComponent("month"))?
+ .with_day(day)
+ .ok_or(InvalidComponent("day"))?;
+ return Ok(input);
+ }
+ Err(err) => err,
+ };
+
+ // Don't check for `None`, as the error from year-month-day will always take priority.
+ if let Some(ParsedItem(input, ordinal)) = dayo(input) {
+ *parsed = parsed
+ .with_year(year)
+ .ok_or(InvalidComponent("year"))?
+ .with_ordinal(ordinal)
+ .ok_or(InvalidComponent("ordinal"))?;
+ return Ok(input);
+ }
+
+ match (|| {
+ let input = ascii_char::<b'W'>(input)
+ .ok_or((false, InvalidLiteral))?
+ .into_inner();
+ let ParsedItem(mut input, week) =
+ week(input).ok_or((true, InvalidComponent("week")))?;
+ if extended_kind.is_extended() {
+ input = ascii_char::<b'-'>(input)
+ .ok_or((true, InvalidLiteral))?
+ .into_inner();
+ }
+ let ParsedItem(input, weekday) =
+ dayk(input).ok_or((true, InvalidComponent("weekday")))?;
+ Ok(ParsedItem(input, (week, weekday)))
+ })() {
+ Ok(ParsedItem(input, (week, weekday))) => {
+ *parsed = parsed
+ .with_iso_year(year)
+ .ok_or(InvalidComponent("year"))?
+ .with_iso_week_number(week)
+ .ok_or(InvalidComponent("week"))?
+ .with_weekday(weekday)
+ .ok_or(InvalidComponent("weekday"))?;
+ return Ok(input);
+ }
+ Err((false, _err)) => {}
+ // This error is more accurate than the one from year-month-day.
+ Err((true, err)) => ret_error = err,
+ }
+
+ Err(ret_error.into())
+ }
+ }
+
+ // Basic: ["T"][hour][min][sec]
+ // Extended: ["T"][hour][":"][min][":"][sec]
+ // Reduced precision: components after [hour] (including their preceding separator) can be
+ // omitted. ["T"] can be omitted if there is no date present.
+ /// Parse a time in the basic or extended format. Reduced precision is permitted.
+ pub(crate) fn parse_time<'a>(
+ parsed: &'a mut Parsed,
+ extended_kind: &'a mut ExtendedKind,
+ date_is_present: bool,
+ ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
+ move |mut input| {
+ if date_is_present {
+ input = ascii_char::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ }
+
+ let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
+ match hour {
+ (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
+ (hour, Some(fractional_part)) => {
+ *parsed = parsed
+ .with_hour_24(hour)
+ .ok_or(InvalidComponent("hour"))?
+ .with_minute((fractional_part * 60.0) as _)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second((fractional_part * 3600.0 % 60.) as _)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _,
+ )
+ .ok_or(InvalidComponent("subsecond"))?;
+ return Ok(input);
+ }
+ };
+
+ if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
+ extended_kind
+ .coerce_extended()
+ .ok_or(InvalidComponent("minute"))?;
+ input = new_input;
+ };
+
+ let mut input = match float(input) {
+ Some(ParsedItem(input, (minute, None))) => {
+ extended_kind.coerce_basic();
+ parsed
+ .set_minute(minute)
+ .ok_or(InvalidComponent("minute"))?;
+ input
+ }
+ Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
+ // `None` is valid behavior, so don't error if this fails.
+ extended_kind.coerce_basic();
+ *parsed = parsed
+ .with_minute(minute)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second((fractional_part * 60.) as _)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(
+ (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _,
+ )
+ .ok_or(InvalidComponent("subsecond"))?;
+ return Ok(input);
+ }
+ // colon was present, so minutes are required
+ None if extended_kind.is_extended() => {
+ return Err(error::Parse::ParseFromDescription(InvalidComponent(
+ "minute",
+ )));
+ }
+ None => {
+ // Missing components are assumed to be zero.
+ *parsed = parsed
+ .with_minute(0)
+ .ok_or(InvalidComponent("minute"))?
+ .with_second(0)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(0)
+ .ok_or(InvalidComponent("subsecond"))?;
+ return Ok(input);
+ }
+ };
+
+ if extended_kind.is_extended() {
+ match ascii_char::<b':'>(input) {
+ Some(ParsedItem(new_input, ())) => input = new_input,
+ None => {
+ *parsed = parsed
+ .with_second(0)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(0)
+ .ok_or(InvalidComponent("subsecond"))?;
+ return Ok(input);
+ }
+ }
+ }
+
+ let (input, second, subsecond) = match float(input) {
+ Some(ParsedItem(input, (second, None))) => (input, second, 0),
+ Some(ParsedItem(input, (second, Some(fractional_part)))) => {
+ (input, second, round(fractional_part * 1_000_000_000.) as _)
+ }
+ None if extended_kind.is_extended() => {
+ return Err(error::Parse::ParseFromDescription(InvalidComponent(
+ "second",
+ )));
+ }
+ // Missing components are assumed to be zero.
+ None => (input, 0, 0),
+ };
+ *parsed = parsed
+ .with_second(second)
+ .ok_or(InvalidComponent("second"))?
+ .with_subsecond(subsecond)
+ .ok_or(InvalidComponent("subsecond"))?;
+
+ Ok(input)
+ }
+ }
+
+ // Basic: [±][hour][min] or ["Z"]
+ // Extended: [±][hour][":"][min] or ["Z"]
+ // Reduced precision: [±][hour] or ["Z"]
+ /// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
+ pub(crate) fn parse_offset<'a>(
+ parsed: &'a mut Parsed,
+ extended_kind: &'a mut ExtendedKind,
+ ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
+ move |input| {
+ if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
+ *parsed = parsed
+ .with_offset_hour(0)
+ .ok_or(InvalidComponent("offset hour"))?
+ .with_offset_minute_signed(0)
+ .ok_or(InvalidComponent("offset minute"))?
+ .with_offset_second_signed(0)
+ .ok_or(InvalidComponent("offset second"))?;
+ return Ok(input);
+ }
+
+ let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let mut input = hour(input)
+ .and_then(|parsed_item| {
+ parsed_item.consume_value(|hour| {
+ parsed.set_offset_hour(if sign == b'-' {
+ -(hour as i8)
+ } else {
+ hour as _
+ })
+ })
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+
+ if extended_kind.maybe_extended() {
+ if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
+ extended_kind
+ .coerce_extended()
+ .ok_or(InvalidComponent("offset minute"))?;
+ input = new_input;
+ };
+ }
+
+ let input = min(input)
+ .and_then(|parsed_item| {
+ parsed_item.consume_value(|min| {
+ parsed.set_offset_minute_signed(if sign == b'-' {
+ -(min as i8)
+ } else {
+ min as _
+ })
+ })
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+ // If `:` was present, the format has already been set to extended. As such, this call
+ // will do nothing in that case. If there wasn't `:` but minutes were
+ // present, we know it's the basic format. Do not use `?` on the call, as
+ // returning `None` is valid behavior.
+ extended_kind.coerce_basic();
+
+ Ok(input)
+ }
+ }
+}
+
+/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
+/// implementation for `no_std`
+fn round(value: f64) -> f64 {
+ #[cfg(feature = "std")]
+ {
+ value.round()
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ round_impl(value)
+ }
+}
+
+#[cfg(not(feature = "std"))]
+#[allow(clippy::missing_docs_in_private_items)]
+fn round_impl(value: f64) -> f64 {
+ debug_assert!(value.is_sign_positive() && !value.is_nan());
+
+ let f = value % 1.;
+ if f < 0.5 { value - f } else { value - f + 1. }
+}
diff --git a/third_party/rust/time/src/parsing/mod.rs b/third_party/rust/time/src/parsing/mod.rs
new file mode 100644
index 0000000000..4a2aa829f1
--- /dev/null
+++ b/third_party/rust/time/src/parsing/mod.rs
@@ -0,0 +1,50 @@
+//! Parsing for various types.
+
+pub(crate) mod combinator;
+pub(crate) mod component;
+mod iso8601;
+pub(crate) mod parsable;
+mod parsed;
+pub(crate) mod shim;
+
+pub use self::parsable::Parsable;
+pub use self::parsed::Parsed;
+
+/// An item that has been parsed. Represented as a `(remaining, value)` pair.
+#[derive(Debug)]
+pub(crate) struct ParsedItem<'a, T>(pub(crate) &'a [u8], pub(crate) T);
+
+impl<'a, T> ParsedItem<'a, T> {
+ /// Map the value to a new value, preserving the remaining input.
+ pub(crate) fn map<U>(self, f: impl FnOnce(T) -> U) -> ParsedItem<'a, U> {
+ ParsedItem(self.0, f(self.1))
+ }
+
+ /// Map the value to a new, optional value, preserving the remaining input.
+ pub(crate) fn flat_map<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<ParsedItem<'a, U>> {
+ Some(ParsedItem(self.0, f(self.1)?))
+ }
+
+ /// Consume the stored value with the provided function. The remaining input is returned.
+ #[must_use = "this returns the remaining input"]
+ pub(crate) fn consume_value(self, f: impl FnOnce(T) -> Option<()>) -> Option<&'a [u8]> {
+ f(self.1)?;
+ Some(self.0)
+ }
+}
+
+impl<'a> ParsedItem<'a, ()> {
+ /// Discard the unit value, returning the remaining input.
+ #[must_use = "this returns the remaining input"]
+ pub(crate) const fn into_inner(self) -> &'a [u8] {
+ self.0
+ }
+}
+
+impl<'a> ParsedItem<'a, Option<()>> {
+ /// Discard the potential unit value, returning the remaining input.
+ #[must_use = "this returns the remaining input"]
+ pub(crate) const fn into_inner(self) -> &'a [u8] {
+ self.0
+ }
+}
diff --git a/third_party/rust/time/src/parsing/parsable.rs b/third_party/rust/time/src/parsing/parsable.rs
new file mode 100644
index 0000000000..badb638088
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsable.rs
@@ -0,0 +1,754 @@
+//! A trait that can be used to parse an item from an input.
+
+use core::ops::Deref;
+
+use crate::error::TryFromParsed;
+use crate::format_description::well_known::iso8601::EncodedConfig;
+use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
+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};
+
+/// A type that can be parsed.
+#[cfg_attr(__time_03_docs, doc(notable_trait))]
+pub trait Parsable: sealed::Sealed {}
+impl Parsable for FormatItem<'_> {}
+impl Parsable for [FormatItem<'_>] {}
+#[cfg(feature = "alloc")]
+impl Parsable for OwnedFormatItem {}
+#[cfg(feature = "alloc")]
+impl Parsable for [OwnedFormatItem] {}
+impl Parsable for Rfc2822 {}
+impl Parsable for Rfc3339 {}
+impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
+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::*;
+
+ /// Parse the item using a format description and an input.
+ pub trait Sealed {
+ /// Parse the item into the provided [`Parsed`] struct.
+ ///
+ /// This method can be used to parse a single component without parsing the full value.
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse>;
+
+ /// Parse the item into a new [`Parsed`] struct.
+ ///
+ /// This method can only be used to parse a complete value of a type. If any characters
+ /// remain after parsing, an error will be returned.
+ fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
+ let mut parsed = Parsed::new();
+ if self.parse_into(input, &mut parsed)?.is_empty() {
+ Ok(parsed)
+ } else {
+ Err(error::Parse::UnexpectedTrailingCharacters)
+ }
+ }
+
+ /// Parse a [`Date`] from the format description.
+ fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`Time`] from the format description.
+ fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`UtcOffset`] from the format description.
+ fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
+ 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> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+ }
+}
+
+// region: custom formats
+impl sealed::Sealed for FormatItem<'_> {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_item(input, self)?)
+ }
+}
+
+impl sealed::Sealed for [FormatItem<'_>] {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_items(input, self)?)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::Sealed for OwnedFormatItem {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_item(input, self)?)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::Sealed for [OwnedFormatItem] {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ Ok(parsed.parse_items(input, self)?)
+ }
+}
+
+impl<T: Deref> sealed::Sealed for T
+where
+ T::Target: sealed::Sealed,
+{
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ self.deref().parse_into(input, parsed)
+ }
+}
+// endregion custom formats
+
+// region: well-known formats
+impl sealed::Sealed for Rfc2822 {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
+ use crate::parsing::combinator::{
+ ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
+ };
+
+ let colon = ascii_char::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ let input = opt(fws)(input).into_inner();
+ let input = first_match(
+ [
+ (b"Mon".as_slice(), Weekday::Monday),
+ (b"Tue".as_slice(), Weekday::Tuesday),
+ (b"Wed".as_slice(), Weekday::Wednesday),
+ (b"Thu".as_slice(), Weekday::Thursday),
+ (b"Fri".as_slice(), Weekday::Friday),
+ (b"Sat".as_slice(), Weekday::Saturday),
+ (b"Sun".as_slice(), Weekday::Sunday),
+ ],
+ false,
+ )(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_weekday(value)))
+ .ok_or(InvalidComponent("weekday"))?;
+ let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = n_to_m_digits::<1, 2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
+ .ok_or(InvalidComponent("day"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = first_match(
+ [
+ (b"Jan".as_slice(), Month::January),
+ (b"Feb".as_slice(), Month::February),
+ (b"Mar".as_slice(), Month::March),
+ (b"Apr".as_slice(), Month::April),
+ (b"May".as_slice(), Month::May),
+ (b"Jun".as_slice(), Month::June),
+ (b"Jul".as_slice(), Month::July),
+ (b"Aug".as_slice(), Month::August),
+ (b"Sep".as_slice(), Month::September),
+ (b"Oct".as_slice(), Month::October),
+ (b"Nov".as_slice(), Month::November),
+ (b"Dec".as_slice(), Month::December),
+ ],
+ false,
+ )(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
+ .ok_or(InvalidComponent("month"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = match exactly_n_digits::<4, u32>(input) {
+ Some(item) => {
+ let input = item
+ .flat_map(|year| if year >= 1900 { Some(year) } else { None })
+ .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
+ .ok_or(InvalidComponent("year"))?;
+ fws(input).ok_or(InvalidLiteral)?.into_inner()
+ }
+ None => {
+ let input = exactly_n_digits::<2, u32>(input)
+ .and_then(|item| {
+ item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
+ .map(|year| year as _)
+ .consume_value(|value| parsed.set_year(value))
+ })
+ .ok_or(InvalidComponent("year"))?;
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ }
+ };
+
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
+ .ok_or(InvalidComponent("hour"))?;
+ let input = opt(cfws)(input).into_inner();
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = opt(cfws)(input).into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
+ .ok_or(InvalidComponent("minute"))?;
+
+ let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
+ let input = input.into_inner(); // discard the colon
+ let input = opt(cfws)(input).into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
+ .ok_or(InvalidComponent("second"))?;
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ } else {
+ cfws(input).ok_or(InvalidLiteral)?.into_inner()
+ };
+
+ // The RFC explicitly allows leap seconds.
+ parsed.set_leap_second_allowed(true);
+
+ #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+ let zone_literal = first_match(
+ [
+ (b"UT".as_slice(), 0),
+ (b"GMT".as_slice(), 0),
+ (b"EST".as_slice(), -5),
+ (b"EDT".as_slice(), -4),
+ (b"CST".as_slice(), -6),
+ (b"CDT".as_slice(), -5),
+ (b"MST".as_slice(), -7),
+ (b"MDT".as_slice(), -6),
+ (b"PST".as_slice(), -8),
+ (b"PDT".as_slice(), -7),
+ ],
+ false,
+ )(input)
+ .or_else(|| match input {
+ [
+ b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
+ rest @ ..,
+ ] => Some(ParsedItem(rest, 0)),
+ _ => None,
+ });
+ if let Some(zone_literal) = zone_literal {
+ let input = zone_literal
+ .consume_value(|value| parsed.set_offset_hour(value))
+ .ok_or(InvalidComponent("offset hour"))?;
+ parsed
+ .set_offset_minute_signed(0)
+ .ok_or(InvalidComponent("offset minute"))?;
+ parsed
+ .set_offset_second_signed(0)
+ .ok_or(InvalidComponent("offset second"))?;
+ return Ok(input);
+ }
+
+ let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_hour(value))
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+
+ Ok(input)
+ }
+
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
+ use crate::parsing::combinator::{
+ ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
+ };
+
+ let colon = ascii_char::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ let input = opt(fws)(input).into_inner();
+ // This parses the weekday, but we don't actually use the value anywhere. Because of this,
+ // just return `()` to avoid unnecessary generated code.
+ let ParsedItem(input, ()) = first_match(
+ [
+ (b"Mon".as_slice(), ()),
+ (b"Tue".as_slice(), ()),
+ (b"Wed".as_slice(), ()),
+ (b"Thu".as_slice(), ()),
+ (b"Fri".as_slice(), ()),
+ (b"Sat".as_slice(), ()),
+ (b"Sun".as_slice(), ()),
+ ],
+ false,
+ )(input)
+ .ok_or(InvalidComponent("weekday"))?;
+ let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, day) =
+ n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, month) = first_match(
+ [
+ (b"Jan".as_slice(), Month::January),
+ (b"Feb".as_slice(), Month::February),
+ (b"Mar".as_slice(), Month::March),
+ (b"Apr".as_slice(), Month::April),
+ (b"May".as_slice(), Month::May),
+ (b"Jun".as_slice(), Month::June),
+ (b"Jul".as_slice(), Month::July),
+ (b"Aug".as_slice(), Month::August),
+ (b"Sep".as_slice(), Month::September),
+ (b"Oct".as_slice(), Month::October),
+ (b"Nov".as_slice(), Month::November),
+ (b"Dec".as_slice(), Month::December),
+ ],
+ false,
+ )(input)
+ .ok_or(InvalidComponent("month"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ let (input, year) = match exactly_n_digits::<4, u32>(input) {
+ Some(item) => {
+ let ParsedItem(input, year) = item
+ .flat_map(|year| if year >= 1900 { Some(year) } else { None })
+ .ok_or(InvalidComponent("year"))?;
+ let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, year)
+ }
+ None => {
+ let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input)
+ .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
+ .ok_or(InvalidComponent("year"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, year)
+ }
+ };
+
+ let ParsedItem(input, hour) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
+ let input = opt(cfws)(input).into_inner();
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = opt(cfws)(input).into_inner();
+ let ParsedItem(input, minute) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
+
+ let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
+ let input = input.into_inner(); // discard the colon
+ let input = opt(cfws)(input).into_inner();
+ let ParsedItem(input, second) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
+ let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
+ (input, second)
+ } else {
+ (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
+ };
+
+ #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
+ let zone_literal = first_match(
+ [
+ (b"UT".as_slice(), 0),
+ (b"GMT".as_slice(), 0),
+ (b"EST".as_slice(), -5),
+ (b"EDT".as_slice(), -4),
+ (b"CST".as_slice(), -6),
+ (b"CDT".as_slice(), -5),
+ (b"MST".as_slice(), -7),
+ (b"MDT".as_slice(), -6),
+ (b"PST".as_slice(), -8),
+ (b"PDT".as_slice(), -7),
+ ],
+ false,
+ )(input)
+ .or_else(|| match input {
+ [
+ b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
+ rest @ ..,
+ ] => Some(ParsedItem(rest, 0)),
+ _ => None,
+ });
+
+ let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
+ let ParsedItem(input, offset_hour) = zone_literal;
+ (input, offset_hour, 0)
+ } else {
+ let ParsedItem(input, offset_sign) =
+ sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
+ .map(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_minute) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
+ (input, offset_hour, offset_minute as i8)
+ };
+
+ if !input.is_empty() {
+ return Err(error::Parse::UnexpectedTrailingCharacters);
+ }
+
+ let mut nanosecond = 0;
+ let leap_second_input = if second == 60 {
+ second = 59;
+ nanosecond = 999_999_999;
+ true
+ } else {
+ false
+ };
+
+ let dt = (|| {
+ 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))
+ })()
+ .map_err(TryFromParsed::ComponentRange)?;
+
+ if leap_second_input && !dt.is_valid_leap_second_stand_in() {
+ return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
+ error::ComponentRange {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ )));
+ }
+
+ Ok(dt)
+ }
+}
+
+impl sealed::Sealed for Rfc3339 {
+ fn parse_into<'a>(
+ &self,
+ input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::{
+ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
+ };
+
+ let dash = ascii_char::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ let input = exactly_n_digits::<4, u32>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
+ .ok_or(InvalidComponent("year"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
+ .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
+ .ok_or(InvalidComponent("month"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
+ .ok_or(InvalidComponent("day"))?;
+ let input = ascii_char_ignore_case::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
+ .ok_or(InvalidComponent("hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
+ .ok_or(InvalidComponent("minute"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, _>(input)
+ .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
+ .ok_or(InvalidComponent("second"))?;
+ let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
+ let ParsedItem(mut input, mut value) = any_digit(input)
+ .ok_or(InvalidComponent("subsecond"))?
+ .map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ input = new_input;
+ multiplier /= 10;
+ }
+
+ parsed
+ .set_subsecond(value)
+ .ok_or(InvalidComponent("subsecond"))?;
+ input
+ } else {
+ input
+ };
+
+ // The RFC explicitly allows leap seconds.
+ parsed.set_leap_second_allowed(true);
+
+ if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
+ parsed
+ .set_offset_hour(0)
+ .ok_or(InvalidComponent("offset hour"))?;
+ parsed
+ .set_offset_minute_signed(0)
+ .ok_or(InvalidComponent("offset minute"))?;
+ parsed
+ .set_offset_second_signed(0)
+ .ok_or(InvalidComponent("offset second"))?;
+ return Ok(input);
+ }
+
+ let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_hour| {
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_hour(value))
+ })
+ .ok_or(InvalidComponent("offset hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let input = exactly_n_digits::<2, u8>(input)
+ .and_then(|item| {
+ item.map(|offset_minute| {
+ if offset_sign == b'-' {
+ -(offset_minute as i8)
+ } else {
+ offset_minute as _
+ }
+ })
+ .consume_value(|value| parsed.set_offset_minute_signed(value))
+ })
+ .ok_or(InvalidComponent("offset minute"))?;
+
+ Ok(input)
+ }
+
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
+ use crate::parsing::combinator::{
+ any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
+ };
+
+ let dash = ascii_char::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ let ParsedItem(input, year) =
+ exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, month) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?;
+ let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, day) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?;
+ let input = ascii_char_ignore_case::<b'T'>(input)
+ .ok_or(InvalidLiteral)?
+ .into_inner();
+ let ParsedItem(input, hour) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, minute) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, mut second) =
+ exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
+ let ParsedItem(input, mut nanosecond) =
+ if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
+ let ParsedItem(mut input, mut value) = any_digit(input)
+ .ok_or(InvalidComponent("subsecond"))?
+ .map(|v| (v - b'0') as u32 * 100_000_000);
+
+ let mut multiplier = 10_000_000;
+ while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
+ value += (digit - b'0') as u32 * multiplier;
+ input = new_input;
+ multiplier /= 10;
+ }
+
+ ParsedItem(input, value)
+ } else {
+ ParsedItem(input, 0)
+ };
+ let ParsedItem(input, offset) = {
+ if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
+ ParsedItem(input, UtcOffset::UTC)
+ } else {
+ let ParsedItem(input, offset_sign) =
+ sign(input).ok_or(InvalidComponent("offset hour"))?;
+ let ParsedItem(input, offset_hour) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?;
+ let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
+ let ParsedItem(input, offset_minute) =
+ exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
+ UtcOffset::from_hms(
+ if offset_sign == b'-' {
+ -(offset_hour as i8)
+ } else {
+ offset_hour as _
+ },
+ if offset_sign == b'-' {
+ -(offset_minute as i8)
+ } else {
+ offset_minute as _
+ },
+ 0,
+ )
+ .map(|offset| ParsedItem(input, offset))
+ .map_err(|mut err| {
+ // Provide the user a more accurate error.
+ if err.name == "hours" {
+ err.name = "offset hour";
+ } else if err.name == "minutes" {
+ err.name = "offset minute";
+ }
+ err
+ })
+ .map_err(TryFromParsed::ComponentRange)?
+ }
+ };
+
+ if !input.is_empty() {
+ return Err(error::Parse::UnexpectedTrailingCharacters);
+ }
+
+ // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
+ // the preceding nanosecond. However, leap seconds can only occur as the last second of the
+ // month UTC.
+ let leap_second_input = if second == 60 {
+ second = 59;
+ nanosecond = 999_999_999;
+ true
+ } else {
+ false
+ };
+
+ let dt = 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)?;
+
+ if leap_second_input && !dt.is_valid_leap_second_stand_in() {
+ return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
+ error::ComponentRange {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ )));
+ }
+
+ Ok(dt)
+ }
+}
+
+impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
+ fn parse_into<'a>(
+ &self,
+ mut input: &'a [u8],
+ parsed: &mut Parsed,
+ ) -> Result<&'a [u8], error::Parse> {
+ use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
+
+ let mut extended_kind = ExtendedKind::Unknown;
+ let mut date_is_present = false;
+ let mut time_is_present = false;
+ let mut offset_is_present = false;
+ let mut first_error = None;
+
+ match Self::parse_date(parsed, &mut extended_kind)(input) {
+ Ok(new_input) => {
+ input = new_input;
+ date_is_present = true;
+ }
+ Err(err) => {
+ first_error.get_or_insert(err);
+ }
+ }
+
+ match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
+ Ok(new_input) => {
+ input = new_input;
+ time_is_present = true;
+ }
+ Err(err) => {
+ first_error.get_or_insert(err);
+ }
+ }
+
+ // If a date and offset are present, a time must be as well.
+ if !date_is_present || time_is_present {
+ match Self::parse_offset(parsed, &mut extended_kind)(input) {
+ Ok(new_input) => {
+ input = new_input;
+ offset_is_present = true;
+ }
+ Err(err) => {
+ first_error.get_or_insert(err);
+ }
+ }
+ }
+
+ 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"),
+ }
+ }
+
+ Ok(input)
+ }
+}
+// endregion well-known formats
diff --git a/third_party/rust/time/src/parsing/parsed.rs b/third_party/rust/time/src/parsing/parsed.rs
new file mode 100644
index 0000000000..7b2279cbb1
--- /dev/null
+++ b/third_party/rust/time/src/parsing/parsed.rs
@@ -0,0 +1,759 @@
+//! Information parsed from an input and format description.
+
+use core::mem::MaybeUninit;
+use core::num::{NonZeroU16, NonZeroU8};
+
+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,
+};
+use crate::parsing::ParsedItem;
+use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// Sealed to prevent downstream implementations.
+mod sealed {
+ use super::*;
+
+ /// A trait to allow `parse_item` to be generic.
+ pub trait AnyFormatItem {
+ /// Parse a single item, returning the remaining input on success.
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription>;
+ }
+}
+
+impl sealed::AnyFormatItem for FormatItem<'_> {
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ match self {
+ Self::Literal(literal) => Parsed::parse_literal(input, literal),
+ Self::Component(component) => parsed.parse_component(input, *component),
+ Self::Compound(compound) => parsed.parse_items(input, compound),
+ Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)),
+ Self::First(items) => {
+ let mut first_err = None;
+
+ for item in items.iter() {
+ match parsed.parse_item(input, item) {
+ Ok(remaining_input) => return Ok(remaining_input),
+ Err(err) if first_err.is_none() => first_err = Some(err),
+ Err(_) => {}
+ }
+ }
+
+ match first_err {
+ Some(err) => Err(err),
+ // This location will be reached if the slice is empty, skipping the `for` loop.
+ // As this case is expected to be uncommon, there's no need to check up front.
+ None => Ok(input),
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl sealed::AnyFormatItem for OwnedFormatItem {
+ fn parse_item<'a>(
+ &self,
+ parsed: &mut Parsed,
+ input: &'a [u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ match self {
+ Self::Literal(literal) => Parsed::parse_literal(input, literal),
+ Self::Component(component) => parsed.parse_component(input, *component),
+ Self::Compound(compound) => parsed.parse_items(input, compound),
+ Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)),
+ Self::First(items) => {
+ let mut first_err = None;
+
+ for item in items.iter() {
+ match parsed.parse_item(input, item) {
+ Ok(remaining_input) => return Ok(remaining_input),
+ Err(err) if first_err.is_none() => first_err = Some(err),
+ Err(_) => {}
+ }
+ }
+
+ match first_err {
+ Some(err) => Err(err),
+ // This location will be reached if the slice is empty, skipping the `for` loop.
+ // As this case is expected to be uncommon, there's no need to check up front.
+ None => Ok(input),
+ }
+ }
+ }
+ }
+}
+
+/// All information parsed.
+///
+/// This information is directly used to construct the final values.
+///
+/// Most users will not need think about this struct in any way. It is public to allow for manual
+/// control over values, in the instance that the default parser is insufficient.
+#[derive(Debug, Clone, Copy)]
+pub struct Parsed {
+ /// Bitflags indicating whether a particular field is present.
+ flags: u16,
+ /// Calendar year.
+ year: MaybeUninit<i32>,
+ /// The last two digits of the calendar year.
+ year_last_two: MaybeUninit<u8>,
+ /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
+ iso_year: MaybeUninit<i32>,
+ /// The last two digits of the ISO week year.
+ iso_year_last_two: MaybeUninit<u8>,
+ /// Month of the year.
+ month: Option<Month>,
+ /// Week of the year, where week one begins on the first Sunday of the calendar year.
+ sunday_week_number: MaybeUninit<u8>,
+ /// Week of the year, where week one begins on the first Monday of the calendar year.
+ monday_week_number: MaybeUninit<u8>,
+ /// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
+ iso_week_number: Option<NonZeroU8>,
+ /// Day of the week.
+ weekday: Option<Weekday>,
+ /// Day of the year.
+ ordinal: Option<NonZeroU16>,
+ /// Day of the month.
+ day: Option<NonZeroU8>,
+ /// Hour within the day.
+ hour_24: MaybeUninit<u8>,
+ /// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
+ /// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
+ hour_12: Option<NonZeroU8>,
+ /// Whether the `hour_12` field indicates a time that "PM".
+ hour_12_is_pm: Option<bool>,
+ /// Minute within the hour.
+ minute: MaybeUninit<u8>,
+ /// Second within the minute.
+ second: MaybeUninit<u8>,
+ /// Nanosecond within the second.
+ subsecond: MaybeUninit<u32>,
+ /// Whole hours of the UTC offset.
+ offset_hour: MaybeUninit<i8>,
+ /// Minutes within the hour of the UTC offset.
+ offset_minute: MaybeUninit<i8>,
+ /// Seconds within the minute of the UTC offset.
+ offset_second: MaybeUninit<i8>,
+}
+
+#[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;
+ /// 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;
+}
+
+impl Parsed {
+ /// Create a new instance of `Parsed` with no information known.
+ pub const fn new() -> Self {
+ Self {
+ flags: 0,
+ year: MaybeUninit::uninit(),
+ year_last_two: MaybeUninit::uninit(),
+ iso_year: MaybeUninit::uninit(),
+ iso_year_last_two: MaybeUninit::uninit(),
+ month: None,
+ sunday_week_number: MaybeUninit::uninit(),
+ monday_week_number: MaybeUninit::uninit(),
+ iso_week_number: None,
+ weekday: None,
+ ordinal: None,
+ day: None,
+ hour_24: MaybeUninit::uninit(),
+ hour_12: None,
+ hour_12_is_pm: None,
+ minute: MaybeUninit::uninit(),
+ second: MaybeUninit::uninit(),
+ subsecond: MaybeUninit::uninit(),
+ offset_hour: MaybeUninit::uninit(),
+ offset_minute: MaybeUninit::uninit(),
+ offset_second: MaybeUninit::uninit(),
+ }
+ }
+
+ /// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining
+ /// input is returned as the `Ok` value.
+ ///
+ /// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not
+ /// fail; the input will be returned as-is if the expected format is not present.
+ pub fn parse_item<'a>(
+ &mut self,
+ input: &'a [u8],
+ item: &impl sealed::AnyFormatItem,
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ item.parse_item(self, input)
+ }
+
+ /// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The
+ /// remaining input is returned as the `Ok` value.
+ ///
+ /// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail
+ /// to parse. `self` will not be mutated in this instance.
+ pub fn parse_items<'a>(
+ &mut self,
+ mut input: &'a [u8],
+ items: &[impl sealed::AnyFormatItem],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ // Make a copy that we can mutate. It will only be set to the user's copy if everything
+ // succeeds.
+ let mut this = *self;
+ for item in items {
+ input = this.parse_item(input, item)?;
+ }
+ *self = this;
+ Ok(input)
+ }
+
+ /// Parse a literal byte sequence. The remaining input is returned as the `Ok` value.
+ pub fn parse_literal<'a>(
+ input: &'a [u8],
+ literal: &[u8],
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ input
+ .strip_prefix(literal)
+ .ok_or(error::ParseFromDescription::InvalidLiteral)
+ }
+
+ /// Parse a single component, mutating the struct. The remaining input is returned as the `Ok`
+ /// value.
+ pub fn parse_component<'a>(
+ &mut self,
+ input: &'a [u8],
+ component: Component,
+ ) -> Result<&'a [u8], error::ParseFromDescription> {
+ use error::ParseFromDescription::InvalidComponent;
+
+ match component {
+ Component::Day(modifiers) => parse_day(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_day(value)))
+ .ok_or(InvalidComponent("day")),
+ Component::Month(modifiers) => parse_month(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_month(value)))
+ .ok_or(InvalidComponent("month")),
+ Component::Ordinal(modifiers) => parse_ordinal(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_ordinal(value)))
+ .ok_or(InvalidComponent("ordinal")),
+ Component::Weekday(modifiers) => parse_weekday(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_weekday(value)))
+ .ok_or(InvalidComponent("weekday")),
+ Component::WeekNumber(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?;
+ match modifiers.repr {
+ WeekNumberRepr::Iso => {
+ NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value))
+ }
+ WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
+ WeekNumberRepr::Monday => self.set_monday_week_number(value),
+ }
+ .ok_or(InvalidComponent("week number"))?;
+ Ok(remaining)
+ }
+ Component::Year(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_year(input, modifiers).ok_or(InvalidComponent("year"))?;
+ match (modifiers.iso_week_based, modifiers.repr) {
+ (false, YearRepr::Full) => self.set_year(value),
+ (false, YearRepr::LastTwo) => self.set_year_last_two(value as _),
+ (true, YearRepr::Full) => self.set_iso_year(value),
+ (true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _),
+ }
+ .ok_or(InvalidComponent("year"))?;
+ Ok(remaining)
+ }
+ Component::Hour(modifiers) => {
+ let ParsedItem(remaining, value) =
+ parse_hour(input, modifiers).ok_or(InvalidComponent("hour"))?;
+ if modifiers.is_12_hour_clock {
+ NonZeroU8::new(value).and_then(|value| self.set_hour_12(value))
+ } else {
+ self.set_hour_24(value)
+ }
+ .ok_or(InvalidComponent("hour"))?;
+ Ok(remaining)
+ }
+ Component::Minute(modifiers) => parse_minute(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_minute(value)))
+ .ok_or(InvalidComponent("minute")),
+ Component::Period(modifiers) => parse_period(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_hour_12_is_pm(value == Period::Pm))
+ })
+ .ok_or(InvalidComponent("period")),
+ Component::Second(modifiers) => parse_second(input, modifiers)
+ .and_then(|parsed| parsed.consume_value(|value| self.set_second(value)))
+ .ok_or(InvalidComponent("second")),
+ Component::Subsecond(modifiers) => parse_subsecond(input, modifiers)
+ .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)))
+ .ok_or(InvalidComponent("offset hour")),
+ Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_offset_minute_signed(value))
+ })
+ .ok_or(InvalidComponent("offset minute")),
+ Component::OffsetSecond(modifiers) => parse_offset_second(input, modifiers)
+ .and_then(|parsed| {
+ parsed.consume_value(|value| self.set_offset_second_signed(value))
+ })
+ .ok_or(InvalidComponent("offset second")),
+ }
+ }
+}
+
+/// Generate getters for each of the fields.
+macro_rules! getters {
+ ($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
+ getters!(! $(@$flag)? $name: $ty);
+ )*};
+ (! $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ self.$name
+ }
+ };
+ (! @$flag:ident $name:ident : $ty:ty) => {
+ /// Obtain the named component.
+ pub const fn $name(&self) -> Option<$ty> {
+ if self.flags & Self::$flag != Self::$flag {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.$name.assume_init() })
+ }
+ }
+ };
+}
+
+/// Getter methods
+impl Parsed {
+ getters! {
+ @YEAR_FLAG year: i32,
+ @YEAR_LAST_TWO_FLAG year_last_two: u8,
+ @ISO_YEAR_FLAG iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
+ month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
+ iso_week_number: NonZeroU8,
+ weekday: Weekday,
+ ordinal: NonZeroU16,
+ day: NonZeroU8,
+ @HOUR_24_FLAG hour_24: u8,
+ hour_12: NonZeroU8,
+ hour_12_is_pm: bool,
+ @MINUTE_FLAG minute: u8,
+ @SECOND_FLAG second: u8,
+ @SUBSECOND_FLAG subsecond: u32,
+ @OFFSET_HOUR_FLAG offset_hour: i8,
+ }
+
+ /// Obtain the absolute value of the offset minute.
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")]
+ pub const fn offset_minute(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs())
+ }
+
+ /// 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 {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(unsafe { self.offset_minute.assume_init() })
+ }
+ }
+
+ /// Obtain the absolute value of the offset second.
+ #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")]
+ pub const fn offset_second(&self) -> Option<u8> {
+ Some(const_try_opt!(self.offset_second_signed()).unsigned_abs())
+ }
+
+ /// 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 {
+ None
+ } else {
+ // SAFETY: We just checked if the field is present.
+ Some(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
+ }
+}
+
+/// Generate setters for each of the fields.
+///
+/// This macro should only be used for fields where the value is not validated beyond its type.
+macro_rules! setters {
+ ($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
+ setters!(! $(@$flag)? $setter_name $name: $ty);
+ )*};
+ (! $setter_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component.
+ pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
+ self.$name = Some(value);
+ Some(())
+ }
+ };
+ (! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component.
+ pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(())
+ }
+ };
+}
+
+/// Setter methods
+///
+/// All setters return `Option<()>`, which is `Some` if the value was set, and `None` if not. The
+/// setters _may_ fail if the value is invalid, though behavior is not guaranteed.
+impl Parsed {
+ setters! {
+ @YEAR_FLAG set_year year: i32,
+ @YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8,
+ @ISO_YEAR_FLAG set_iso_year iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8,
+ set_month month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8,
+ set_iso_week_number iso_week_number: NonZeroU8,
+ set_weekday weekday: Weekday,
+ set_ordinal ordinal: NonZeroU16,
+ set_day day: NonZeroU8,
+ @HOUR_24_FLAG set_hour_24 hour_24: u8,
+ set_hour_12 hour_12: NonZeroU8,
+ set_hour_12_is_pm hour_12_is_pm: bool,
+ @MINUTE_FLAG set_minute minute: u8,
+ @SECOND_FLAG set_second second: u8,
+ @SUBSECOND_FLAG set_subsecond subsecond: u32,
+ @OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8,
+ }
+
+ /// Set the named component.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_minute_signed()` instead"
+ )]
+ pub fn set_offset_minute(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.set_offset_minute_signed(value as _)
+ }
+ }
+
+ /// 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;
+ Some(())
+ }
+
+ /// Set the named component.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.set_offset_second_signed()` instead"
+ )]
+ pub fn set_offset_second(&mut self, value: u8) -> Option<()> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.set_offset_second_signed(value as _)
+ }
+ }
+
+ /// 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;
+ 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.
+///
+/// This macro should only be used for fields where the value is not validated beyond its type.
+macro_rules! builders {
+ ($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
+ builders!(! $(@$flag)? $builder_name $name: $ty);
+ )*};
+ (! $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = Some(value);
+ Some(self)
+ }
+ };
+ (! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
+ /// Set the named component and return `self`.
+ pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(self)
+ }
+ };
+}
+
+/// Builder methods
+///
+/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if
+/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
+impl Parsed {
+ builders! {
+ @YEAR_FLAG with_year year: i32,
+ @YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
+ @ISO_YEAR_FLAG with_iso_year iso_year: i32,
+ @ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
+ with_month month: Month,
+ @SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
+ @MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
+ with_iso_week_number iso_week_number: NonZeroU8,
+ with_weekday weekday: Weekday,
+ with_ordinal ordinal: NonZeroU16,
+ with_day day: NonZeroU8,
+ @HOUR_24_FLAG with_hour_24 hour_24: u8,
+ with_hour_12 hour_12: NonZeroU8,
+ with_hour_12_is_pm hour_12_is_pm: bool,
+ @MINUTE_FLAG with_minute minute: u8,
+ @SECOND_FLAG with_second second: u8,
+ @SUBSECOND_FLAG with_subsecond subsecond: u32,
+ @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
+ }
+
+ /// Set the named component and return `self`.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.with_offset_minute_signed()` instead"
+ )]
+ pub const fn with_offset_minute(self, value: u8) -> Option<Self> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.with_offset_minute_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_minute` component and return `self`.
+ pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
+ self.offset_minute = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_MINUTE_FLAG;
+ Some(self)
+ }
+
+ /// Set the named component and return `self`.
+ #[deprecated(
+ since = "0.3.8",
+ note = "use `parsed.with_offset_second_signed()` instead"
+ )]
+ pub const fn with_offset_second(self, value: u8) -> Option<Self> {
+ if value > i8::MAX as u8 {
+ None
+ } else {
+ self.with_offset_second_signed(value as _)
+ }
+ }
+
+ /// Set the `offset_second` component and return `self`.
+ pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
+ self.offset_second = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_SECOND_FLAG;
+ Some(self)
+ }
+}
+
+impl TryFrom<Parsed> for Date {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ /// Match on the components that need to be present.
+ macro_rules! match_ {
+ (_ => $catch_all:expr $(,)?) => {
+ $catch_all
+ };
+ (($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => {
+ if let ($(Some($name)),*) = ($(parsed.$name()),*) {
+ $arm
+ } else {
+ match_!($($rest)*)
+ }
+ };
+ }
+
+ /// Get the value needed to adjust the ordinal day for Sunday and Monday-based week
+ /// numbering.
+ const fn adjustment(year: i32) -> i16 {
+ match Date::__from_ordinal_date_unchecked(year, 1).weekday() {
+ Weekday::Monday => 7,
+ Weekday::Tuesday => 1,
+ Weekday::Wednesday => 2,
+ Weekday::Thursday => 3,
+ Weekday::Friday => 4,
+ Weekday::Saturday => 5,
+ Weekday::Sunday => 6,
+ }
+ }
+
+ // TODO Only the basics have been covered. There are many other valid values that are not
+ // currently constructed from the information known.
+
+ match_! {
+ (year, ordinal) => Ok(Self::from_ordinal_date(year, ordinal.get())?),
+ (year, month, day) => Ok(Self::from_calendar_date(year, month, day.get())?),
+ (iso_year, iso_week_number, weekday) => Ok(Self::from_iso_week_date(
+ iso_year,
+ iso_week_number.get(),
+ weekday,
+ )?),
+ (year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date(
+ year,
+ (sunday_week_number as i16 * 7 + weekday.number_days_from_sunday() as i16
+ - adjustment(year)
+ + 1) as u16,
+ )?),
+ (year, monday_week_number, weekday) => Ok(Self::from_ordinal_date(
+ year,
+ (monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16
+ - adjustment(year)
+ + 1) as u16,
+ )?),
+ _ => Err(InsufficientInformation),
+ }
+ }
+}
+
+impl TryFrom<Parsed> for Time {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ let hour = match (parsed.hour_24(), parsed.hour_12(), parsed.hour_12_is_pm()) {
+ (Some(hour), _, _) => hour,
+ (_, Some(hour), Some(false)) if hour.get() == 12 => 0,
+ (_, Some(hour), Some(true)) if hour.get() == 12 => 12,
+ (_, Some(hour), Some(false)) => hour.get(),
+ (_, Some(hour), Some(true)) => hour.get() + 12,
+ _ => return Err(InsufficientInformation),
+ };
+ if parsed.hour_24().is_none()
+ && parsed.hour_12().is_some()
+ && parsed.hour_12_is_pm().is_some()
+ && parsed.minute().is_none()
+ && parsed.second().is_none()
+ && parsed.subsecond().is_none()
+ {
+ return Ok(Self::from_hms_nano(hour, 0, 0, 0)?);
+ }
+ let minute = parsed.minute().ok_or(InsufficientInformation)?;
+ let second = parsed.second().unwrap_or(0);
+ let subsecond = parsed.subsecond().unwrap_or(0);
+ Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
+ }
+}
+
+impl TryFrom<Parsed> for UtcOffset {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ let hour = parsed.offset_hour().ok_or(InsufficientInformation)?;
+ let minute = parsed.offset_minute_signed().unwrap_or(0);
+ let second = parsed.offset_second_signed().unwrap_or(0);
+
+ Self::from_hms(hour, minute, second).map_err(|mut err| {
+ // Provide the user a more accurate error.
+ if err.name == "hours" {
+ err.name = "offset hour";
+ } else if err.name == "minutes" {
+ err.name = "offset minute";
+ } else if err.name == "seconds" {
+ err.name = "offset second";
+ }
+ err.into()
+ })
+ }
+}
+
+impl TryFrom<Parsed> for PrimitiveDateTime {
+ type Error = error::TryFromParsed;
+
+ fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
+ Ok(Self::new(parsed.try_into()?, parsed.try_into()?))
+ }
+}
+
+impl TryFrom<Parsed> for OffsetDateTime {
+ 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> {
+ // 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 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 {
+ name: "second",
+ minimum: 0,
+ maximum: 59,
+ value: 60,
+ conditional_range: true,
+ },
+ ));
+ }
+ Ok(dt)
+ }
+}
diff --git a/third_party/rust/time/src/parsing/shim.rs b/third_party/rust/time/src/parsing/shim.rs
new file mode 100644
index 0000000000..00aaf4852b
--- /dev/null
+++ b/third_party/rust/time/src/parsing/shim.rs
@@ -0,0 +1,50 @@
+//! Extension traits for things either not implemented or not yet stable in the MSRV.
+
+/// Equivalent of `foo.parse()` for slices.
+pub(crate) trait IntegerParseBytes<T> {
+ #[allow(clippy::missing_docs_in_private_items)]
+ fn parse_bytes(&self) -> Option<T>;
+}
+
+impl<T: Integer> IntegerParseBytes<T> for [u8] {
+ fn parse_bytes(&self) -> Option<T> {
+ T::parse_bytes(self)
+ }
+}
+
+/// Marker trait for all integer types, including `NonZero*`
+pub(crate) trait Integer: Sized {
+ #[allow(clippy::missing_docs_in_private_items)]
+ fn parse_bytes(src: &[u8]) -> Option<Self>;
+}
+
+/// Parse the given types from bytes.
+macro_rules! impl_parse_bytes {
+ ($($t:ty)*) => ($(
+ impl Integer for $t {
+ #[allow(trivial_numeric_casts)]
+ fn parse_bytes(src: &[u8]) -> Option<Self> {
+ src.iter().try_fold::<Self, _, _>(0, |result, c| {
+ result.checked_mul(10)?.checked_add((c - b'0') as Self)
+ })
+ }
+ }
+ )*)
+}
+impl_parse_bytes! { u8 u16 u32 }
+
+/// Parse the given types from bytes.
+macro_rules! impl_parse_bytes_nonzero {
+ ($($t:ty)*) => {$(
+ impl Integer for $t {
+ fn parse_bytes(src: &[u8]) -> Option<Self> {
+ Self::new(src.parse_bytes()?)
+ }
+ }
+ )*}
+}
+
+impl_parse_bytes_nonzero! {
+ core::num::NonZeroU8
+ core::num::NonZeroU16
+}
diff --git a/third_party/rust/time/src/primitive_date_time.rs b/third_party/rust/time/src/primitive_date_time.rs
new file mode 100644
index 0000000000..6e842092ab
--- /dev/null
+++ b/third_party/rust/time/src/primitive_date_time.rs
@@ -0,0 +1,937 @@
+//! The [`PrimitiveDateTime`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[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};
+
+/// 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,
+}
+
+impl PrimitiveDateTime {
+ /// The smallest value that can be represented by `PrimitiveDateTime`.
+ ///
+ /// Depending on `large-dates` feature flag, value of this constant may vary.
+ ///
+ /// 1. With `large-dates` disabled it is equal to `-9999-01-01 00:00:00.0`
+ /// 2. With `large-dates` enabled it is equal to `-999999-01-01 00:00:00.0`
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::datetime;
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "// Assuming `large-dates` feature is enabled."
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-999999-01-01 0:00));"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "// Assuming `large-dates` feature is disabled."
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));"
+ )]
+ /// ```
+ pub const MIN: Self = Self::new(Date::MIN, Time::MIN);
+
+ /// The largest value that can be represented by `PrimitiveDateTime`.
+ ///
+ /// Depending on `large-dates` feature flag, value of this constant may vary.
+ ///
+ /// 1. With `large-dates` disabled it is equal to `9999-12-31 23:59:59.999_999_999`
+ /// 2. With `large-dates` enabled it is equal to `999999-12-31 23:59:59.999_999_999`
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::datetime;
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "// Assuming `large-dates` feature is enabled."
+ )]
+ #[cfg_attr(
+ feature = "large-dates",
+ doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+999999-12-31 23:59:59.999_999_999));"
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ doc = "// Assuming `large-dates` feature is disabled."
+ )]
+ #[cfg_attr(
+ not(feature = "large-dates"),
+ 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);
+
+ /// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`].
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::{date, datetime, time};
+ /// assert_eq!(
+ /// PrimitiveDateTime::new(date!(2019-01-01), time!(0:00)),
+ /// datetime!(2019-01-01 0:00),
+ /// );
+ /// ```
+ pub const fn new(date: Date, time: Time) -> Self {
+ Self { date, time }
+ }
+
+ // region: component getters
+ /// Get the [`Date`] component of the `PrimitiveDateTime`.
+ ///
+ /// ```rust
+ /// # use time_macros::{date, datetime};
+ /// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01));
+ /// ```
+ pub const fn date(self) -> Date {
+ self.date
+ }
+
+ /// Get the [`Time`] component of the `PrimitiveDateTime`.
+ ///
+ /// ```rust
+ /// # 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
+ }
+ // endregion component getters
+
+ // region: date getters
+ /// Get the year of the date.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).year(), 2019);
+ /// assert_eq!(datetime!(2019-12-31 0:00).year(), 2019);
+ /// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020);
+ /// ```
+ pub const fn year(self) -> i32 {
+ self.date.year()
+ }
+
+ /// Get the month of the date.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).month(), Month::January);
+ /// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December);
+ /// ```
+ pub const fn month(self) -> Month {
+ self.date.month()
+ }
+
+ /// Get the day of the date.
+ ///
+ /// The returned value will always be in the range `1..=31`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).day(), 1);
+ /// assert_eq!(datetime!(2019-12-31 0:00).day(), 31);
+ /// ```
+ pub const fn day(self) -> u8 {
+ self.date.day()
+ }
+
+ /// Get the day of the year.
+ ///
+ /// The returned value will always be in the range `1..=366` (`1..=365` for common years).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).ordinal(), 1);
+ /// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365);
+ /// ```
+ pub const fn ordinal(self) -> u16 {
+ self.date.ordinal()
+ }
+
+ /// Get the ISO week number.
+ ///
+ /// The returned value will always be in the range `1..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).iso_week(), 1);
+ /// assert_eq!(datetime!(2019-10-04 0:00).iso_week(), 40);
+ /// assert_eq!(datetime!(2020-01-01 0:00).iso_week(), 1);
+ /// assert_eq!(datetime!(2020-12-31 0:00).iso_week(), 53);
+ /// assert_eq!(datetime!(2021-01-01 0:00).iso_week(), 53);
+ /// ```
+ pub const fn iso_week(self) -> u8 {
+ self.date.iso_week()
+ }
+
+ /// Get the week number where week 1 begins on the first Sunday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00).sunday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00).sunday_based_week(), 52);
+ /// 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()
+ }
+
+ /// Get the week number where week 1 begins on the first Monday.
+ ///
+ /// The returned value will always be in the range `0..=53`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-01-01 0:00).monday_based_week(), 0);
+ /// assert_eq!(datetime!(2020-12-31 0:00).monday_based_week(), 52);
+ /// 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()
+ }
+
+ /// Get the year, month, and day.
+ ///
+ /// ```rust
+ /// # use time::Month;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).to_calendar_date(),
+ /// (2019, Month::January, 1)
+ /// );
+ /// ```
+ pub const fn to_calendar_date(self) -> (i32, Month, u8) {
+ self.date.to_calendar_date()
+ }
+
+ /// Get the year and ordinal day number.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// 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()
+ }
+
+ /// Get the ISO 8601 year, week number, and weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).to_iso_week_date(),
+ /// (2019, 1, Tuesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-10-04 0:00).to_iso_week_date(),
+ /// (2019, 40, Friday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-01-01 0:00).to_iso_week_date(),
+ /// (2020, 1, Wednesday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2020-12-31 0:00).to_iso_week_date(),
+ /// (2020, 53, Thursday)
+ /// );
+ /// assert_eq!(
+ /// datetime!(2021-01-01 0:00).to_iso_week_date(),
+ /// (2020, 53, Friday)
+ /// );
+ /// ```
+ pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) {
+ self.date.to_iso_week_date()
+ }
+
+ /// Get the weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday::*;
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-02-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-03-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-04-01 0:00).weekday(), Monday);
+ /// assert_eq!(datetime!(2019-05-01 0:00).weekday(), Wednesday);
+ /// assert_eq!(datetime!(2019-06-01 0:00).weekday(), Saturday);
+ /// assert_eq!(datetime!(2019-07-01 0:00).weekday(), Monday);
+ /// assert_eq!(datetime!(2019-08-01 0:00).weekday(), Thursday);
+ /// assert_eq!(datetime!(2019-09-01 0:00).weekday(), Sunday);
+ /// assert_eq!(datetime!(2019-10-01 0:00).weekday(), Tuesday);
+ /// assert_eq!(datetime!(2019-11-01 0:00).weekday(), Friday);
+ /// assert_eq!(datetime!(2019-12-01 0:00).weekday(), Sunday);
+ /// ```
+ pub const fn weekday(self) -> Weekday {
+ self.date.weekday()
+ }
+
+ /// Get the Julian day for the date. The time is not taken into account for this calculation.
+ ///
+ /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
+ /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(-4713-11-24 0:00).to_julian_day(), 0);
+ /// assert_eq!(datetime!(2000-01-01 0:00).to_julian_day(), 2_451_545);
+ /// assert_eq!(datetime!(2019-01-01 0:00).to_julian_day(), 2_458_485);
+ /// 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()
+ }
+ // endregion date getters
+
+ // region: time getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms(), (0, 0, 0));
+ /// 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()
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_milli(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999).as_hms_milli(),
+ /// (23, 59, 59, 999)
+ /// );
+ /// ```
+ pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
+ self.time.as_hms_milli()
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_micro(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999).as_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_micro()
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_nano(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// datetime!(2020-01-01 23:59:59.999_999_999).as_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
+ self.time.as_hms_nano()
+ }
+
+ /// Get the clock hour.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).hour(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23);
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.time.hour()
+ }
+
+ /// Get the minute within the hour.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).minute(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59);
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.time.minute()
+ }
+
+ /// Get the second within the minute.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).second(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59);
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.time.second()
+ }
+
+ /// Get the milliseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).millisecond(), 0);
+ /// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ self.time.millisecond()
+ }
+
+ /// Get the microseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).microsecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999).microsecond(),
+ /// 999_999
+ /// );
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.time.microsecond()
+ }
+
+ /// Get the nanoseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(datetime!(2019-01-01 0:00).nanosecond(), 0);
+ /// assert_eq!(
+ /// datetime!(2019-01-01 23:59:59.999_999_999).nanosecond(),
+ /// 999_999_999,
+ /// );
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.time.nanosecond()
+ }
+ // endregion time getters
+
+ // region: attach offset
+ /// Assuming that the existing `PrimitiveDateTime` represents a moment in the provided
+ /// [`UtcOffset`], return an [`OffsetDateTime`].
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, offset};
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00)
+ /// .assume_offset(offset!(UTC))
+ /// .unix_timestamp(),
+ /// 1_546_300_800,
+ /// );
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00)
+ /// .assume_offset(offset!(-1))
+ /// .unix_timestamp(),
+ /// 1_546_304_400,
+ /// );
+ /// ```
+ pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime {
+ OffsetDateTime {
+ local_datetime: self,
+ offset,
+ }
+ }
+
+ /// Assuming that the existing `PrimitiveDateTime` represents a moment in UTC, return an
+ /// [`OffsetDateTime`].
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2019-01-01 0:00).assume_utc().unix_timestamp(),
+ /// 1_546_300_800,
+ /// );
+ /// ```
+ pub const fn assume_utc(self) -> OffsetDateTime {
+ self.assume_offset(UtcOffset::UTC)
+ }
+ // endregion attach offset
+
+ // region: checked arithmetic
+ /// Computes `self + duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// let datetime = Date::MIN.midnight();
+ /// assert_eq!(datetime.checked_add((-2).days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight();
+ /// assert_eq!(datetime.checked_add(1.days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).checked_add(27.hours()),
+ /// Some(datetime!(2019 - 11 - 26 18:30))
+ /// );
+ /// ```
+ 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,
+ })
+ }
+
+ /// Computes `self - duration`, returning `None` if an overflow occurred.
+ ///
+ /// ```
+ /// # use time::{Date, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// let datetime = Date::MIN.midnight();
+ /// assert_eq!(datetime.checked_sub(2.days()), None);
+ ///
+ /// let datetime = Date::MAX.midnight();
+ /// assert_eq!(datetime.checked_sub((-1).days()), None);
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).checked_sub(27.hours()),
+ /// Some(datetime!(2019 - 11 - 24 12:30))
+ /// );
+ /// ```
+ 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,
+ })
+ }
+ // endregion: checked arithmetic
+
+ // region: saturating arithmetic
+ /// Computes `self + duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{PrimitiveDateTime, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// PrimitiveDateTime::MIN.saturating_add((-2).days()),
+ /// PrimitiveDateTime::MIN
+ /// );
+ ///
+ /// assert_eq!(
+ /// PrimitiveDateTime::MAX.saturating_add(2.days()),
+ /// PrimitiveDateTime::MAX
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).saturating_add(27.hours()),
+ /// datetime!(2019 - 11 - 26 18:30)
+ /// );
+ /// ```
+ 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
+ }
+ }
+
+ /// Computes `self - duration`, saturating value on overflow.
+ ///
+ /// ```
+ /// # use time::{PrimitiveDateTime, ext::NumericalDuration};
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// PrimitiveDateTime::MIN.saturating_sub(2.days()),
+ /// PrimitiveDateTime::MIN
+ /// );
+ ///
+ /// assert_eq!(
+ /// PrimitiveDateTime::MAX.saturating_sub((-2).days()),
+ /// PrimitiveDateTime::MAX
+ /// );
+ ///
+ /// assert_eq!(
+ /// datetime!(2019 - 11 - 25 15:30).saturating_sub(27.hours()),
+ /// datetime!(2019 - 11 - 24 12:30)
+ /// );
+ /// ```
+ 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
+ }
+ }
+ // endregion: saturating arithmetic
+}
+
+// region: replacement
+/// Methods that replace part of the `PrimitiveDateTime`.
+impl PrimitiveDateTime {
+ /// Replace the time, preserving the date.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, time};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 17:00).replace_time(time!(5:00)),
+ /// datetime!(2020-01-01 5:00)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_time(self, time: Time) -> Self {
+ self.date.with_time(time)
+ }
+
+ /// Replace the date, preserving the time.
+ ///
+ /// ```rust
+ /// # use time_macros::{datetime, date};
+ /// assert_eq!(
+ /// datetime!(2020-01-01 12:00).replace_date(date!(2020-01-30)),
+ /// datetime!(2020-01-30 12:00)
+ /// );
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_date(self, date: Date) -> Self {
+ date.with_time(self.time)
+ }
+
+ /// Replace the year. The month and day will be unchanged.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_year(2019),
+ /// Ok(datetime!(2019 - 02 - 18 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year
+ /// ```
+ #[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))
+ }
+
+ /// Replace the month of the year.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// # use time::Month;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_month(Month::January),
+ /// Ok(datetime!(2022 - 01 - 18 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 01 - 30 12:00).replace_month(Month::February).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[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))
+ }
+
+ /// Replace the day of the month.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 12:00).replace_day(1),
+ /// Ok(datetime!(2022 - 02 - 01 12:00))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(0).is_err()); // 00 isn't a valid day
+ /// assert!(datetime!(2022 - 02 - 18 12:00).replace_day(30).is_err()); // 30 isn't a valid day in February
+ /// ```
+ #[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))
+ }
+
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(7),
+ /// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour
+ /// ```
+ #[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))))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute
+ /// ```
+ #[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))))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second
+ /// ```
+ #[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))))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(7),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_millisecond(millisecond))))
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(7_008),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ #[must_use = "This method does not mutate the original `PrimitiveDateTime`."]
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ Ok(self
+ .date()
+ .with_time(const_try!(self.time.replace_microsecond(microsecond))))
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::datetime;
+ /// assert_eq!(
+ /// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(7_008_009),
+ /// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009))
+ /// );
+ /// assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond
+ /// ```
+ #[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))))
+ }
+}
+// endregion replacement
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl PrimitiveDateTime {
+ /// Format the `PrimitiveDateTime` using the provided [format
+ /// description](crate::format_description).
+ 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), None)
+ }
+
+ /// Format the `PrimitiveDateTime` using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::datetime;
+ /// let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?;
+ /// assert_eq!(
+ /// datetime!(2020-01-02 03:04:05).format(&format)?,
+ /// "2020-01-02 03:04:05"
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
+ format.format(Some(self.date), Some(self.time), None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl PrimitiveDateTime {
+ /// Parse a `PrimitiveDateTime` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::PrimitiveDateTime;
+ /// # use time_macros::{datetime, format_description};
+ /// let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
+ /// assert_eq!(
+ /// PrimitiveDateTime::parse("2020-01-02 03:04:05", &format)?,
+ /// datetime!(2020-01-02 03:04:05)
+ /// );
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_date_time(input.as_bytes())
+ }
+}
+
+impl fmt::Display for PrimitiveDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {}", self.date, self.time)
+ }
+}
+
+impl fmt::Debug for PrimitiveDateTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+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")
+ }
+}
+
+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,
+ }
+ }
+}
+
+impl_add_assign!(PrimitiveDateTime: Duration, StdDuration);
+
+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")
+ }
+}
+
+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,
+ }
+ }
+}
+
+impl_sub_assign!(PrimitiveDateTime: Duration, StdDuration);
+
+impl Sub for PrimitiveDateTime {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ (self.date - rhs.date) + (self.time - rhs.time)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/quickcheck.rs b/third_party/rust/time/src/quickcheck.rs
new file mode 100644
index 0000000000..707f3e0a97
--- /dev/null
+++ b/third_party/rust/time/src/quickcheck.rs
@@ -0,0 +1,220 @@
+//! Implementations of the [`quickcheck::Arbitrary`](quickcheck::Arbitrary) trait.
+//!
+//! This enables users to write tests such as this, and have test values provided automatically:
+//!
+//! ```
+//! # #![allow(dead_code)]
+//! use quickcheck::quickcheck;
+//! use time::Date;
+//!
+//! struct DateRange {
+//! from: Date,
+//! to: Date,
+//! }
+//!
+//! impl DateRange {
+//! fn new(from: Date, to: Date) -> Result<Self, ()> {
+//! Ok(DateRange { from, to })
+//! }
+//! }
+//!
+//! quickcheck! {
+//! fn date_range_is_well_defined(from: Date, to: Date) -> bool {
+//! let r = DateRange::new(from, to);
+//! if from <= to {
+//! r.is_ok()
+//! } else {
+//! r.is_err()
+//! }
+//! }
+//! }
+//! ```
+//!
+//! An implementation for `Instant` is intentionally omitted since its values are only meaningful in
+//! relation to a [`Duration`], and obtaining an `Instant` from a [`Duration`] is very simple
+//! anyway.
+
+use alloc::boxed::Box;
+
+use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen};
+
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// Obtain an arbitrary value between the minimum and maximum inclusive.
+macro_rules! arbitrary_between {
+ ($type:ty; $gen:expr, $min:expr, $max:expr) => {{
+ let min = $min;
+ let max = $max;
+ let range = max - min;
+ <$type>::arbitrary($gen).rem_euclid(range + 1) + min
+ }};
+}
+
+impl Arbitrary for Date {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::from_julian_day_unchecked(arbitrary_between!(
+ i32;
+ g,
+ Self::MIN.to_julian_day(),
+ Self::MAX.to_julian_day()
+ ))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ self.to_ordinal_date()
+ .shrink()
+ .flat_map(|(year, ordinal)| Self::from_ordinal_date(year, ordinal)),
+ )
+ }
+}
+
+impl Arbitrary for Duration {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::nanoseconds_i128(arbitrary_between!(
+ i128;
+ g,
+ Self::MIN.whole_nanoseconds(),
+ Self::MAX.whole_nanoseconds()
+ ))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.subsec_nanoseconds(), self.whole_seconds())
+ .shrink()
+ .map(|(mut nanoseconds, seconds)| {
+ // Coerce the sign if necessary.
+ if (seconds > 0 && nanoseconds < 0) || (seconds < 0 && nanoseconds > 0) {
+ nanoseconds *= -1;
+ }
+
+ Self::new_unchecked(seconds, nanoseconds)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for Time {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::__from_hms_nanos_unchecked(
+ arbitrary_between!(u8; g, 0, 23),
+ arbitrary_between!(u8; g, 0, 59),
+ arbitrary_between!(u8; g, 0, 59),
+ arbitrary_between!(u32; g, 0, 999_999_999),
+ )
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ self.as_hms_nano()
+ .shrink()
+ .map(|(hour, minute, second, nanosecond)| {
+ Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for PrimitiveDateTime {
+ fn arbitrary(g: &mut Gen) -> Self {
+ Self::new(<_>::arbitrary(g), <_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.date, self.time)
+ .shrink()
+ .map(|(date, time)| Self { date, time }),
+ )
+ }
+}
+
+impl Arbitrary for UtcOffset {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let seconds = arbitrary_between!(i32; g, -86_399, 86_399);
+ Self::__from_hms_unchecked(
+ (seconds / 3600) as _,
+ ((seconds % 3600) / 60) as _,
+ (seconds % 60) as _,
+ )
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ self.as_hms().shrink().map(|(hours, minutes, seconds)| {
+ Self::__from_hms_unchecked(hours, minutes, seconds)
+ }),
+ )
+ }
+}
+
+impl Arbitrary for OffsetDateTime {
+ fn arbitrary(g: &mut Gen) -> Self {
+ let datetime = PrimitiveDateTime::arbitrary(g);
+ datetime.assume_offset(<_>::arbitrary(g))
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ Box::new(
+ (self.local_datetime, self.offset)
+ .shrink()
+ .map(|(local_datetime, offset)| local_datetime.assume_offset(offset)),
+ )
+ }
+}
+
+impl Arbitrary for Weekday {
+ fn arbitrary(g: &mut Gen) -> Self {
+ use Weekday::*;
+ match arbitrary_between!(u8; g, 0, 6) {
+ 0 => Monday,
+ 1 => Tuesday,
+ 2 => Wednesday,
+ 3 => Thursday,
+ 4 => Friday,
+ 5 => Saturday,
+ val => {
+ debug_assert!(val == 6);
+ Sunday
+ }
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ match self {
+ Self::Monday => empty_shrinker(),
+ _ => single_shrinker(self.previous()),
+ }
+ }
+}
+
+impl Arbitrary for Month {
+ fn arbitrary(g: &mut Gen) -> Self {
+ use Month::*;
+ match arbitrary_between!(u8; g, 1, 12) {
+ 1 => January,
+ 2 => February,
+ 3 => March,
+ 4 => April,
+ 5 => May,
+ 6 => June,
+ 7 => July,
+ 8 => August,
+ 9 => September,
+ 10 => October,
+ 11 => November,
+ val => {
+ debug_assert!(val == 12);
+ December
+ }
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
+ match self {
+ Self::January => empty_shrinker(),
+ _ => single_shrinker(self.previous()),
+ }
+ }
+}
diff --git a/third_party/rust/time/src/rand.rs b/third_party/rust/time/src/rand.rs
new file mode 100644
index 0000000000..8afefe5075
--- /dev/null
+++ b/third_party/rust/time/src/rand.rs
@@ -0,0 +1,99 @@
+//! Implementation of [`Distribution`] for various structs.
+
+use rand::distributions::{Distribution, Standard};
+use rand::Rng;
+
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+impl Distribution<Time> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Time {
+ Time::__from_hms_nanos_unchecked(
+ rng.gen_range(0..24),
+ rng.gen_range(0..60),
+ rng.gen_range(0..60),
+ rng.gen_range(0..1_000_000_000),
+ )
+ }
+}
+
+impl Distribution<Date> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Date {
+ Date::from_julian_day_unchecked(
+ rng.gen_range(Date::MIN.to_julian_day()..=Date::MAX.to_julian_day()),
+ )
+ }
+}
+
+impl Distribution<UtcOffset> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UtcOffset {
+ let seconds = rng.gen_range(-86399..=86399);
+ UtcOffset::__from_hms_unchecked(
+ (seconds / 3600) as _,
+ ((seconds % 3600) / 60) as _,
+ (seconds % 60) as _,
+ )
+ }
+}
+
+impl Distribution<PrimitiveDateTime> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PrimitiveDateTime {
+ PrimitiveDateTime::new(Self.sample(rng), Self.sample(rng))
+ }
+}
+
+impl Distribution<OffsetDateTime> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> OffsetDateTime {
+ let date_time: PrimitiveDateTime = Self.sample(rng);
+ date_time.assume_offset(Self.sample(rng))
+ }
+}
+
+impl Distribution<Duration> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Duration {
+ Duration::nanoseconds_i128(
+ rng.gen_range(Duration::MIN.whole_nanoseconds()..=Duration::MAX.whole_nanoseconds()),
+ )
+ }
+}
+
+impl Distribution<Weekday> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Weekday {
+ use Weekday::*;
+
+ match rng.gen_range(0u8..7) {
+ 0 => Monday,
+ 1 => Tuesday,
+ 2 => Wednesday,
+ 3 => Thursday,
+ 4 => Friday,
+ 5 => Saturday,
+ val => {
+ debug_assert!(val == 6);
+ Sunday
+ }
+ }
+ }
+}
+
+impl Distribution<Month> for Standard {
+ fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Month {
+ use Month::*;
+ match rng.gen_range(1u8..=12) {
+ 1 => January,
+ 2 => February,
+ 3 => March,
+ 4 => April,
+ 5 => May,
+ 6 => June,
+ 7 => July,
+ 8 => August,
+ 9 => September,
+ 10 => October,
+ 11 => November,
+ val => {
+ debug_assert!(val == 12);
+ December
+ }
+ }
+ }
+}
diff --git a/third_party/rust/time/src/serde/iso8601.rs b/third_party/rust/time/src/serde/iso8601.rs
new file mode 100644
index 0000000000..75deb62f1a
--- /dev/null
+++ b/third_party/rust/time/src/serde/iso8601.rs
@@ -0,0 +1,77 @@
+//! Use the well-known [ISO 8601 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::iso8601::{Config, EncodedConfig};
+use crate::format_description::well_known::Iso8601;
+use crate::OffsetDateTime;
+
+/// The configuration of ISO 8601 used for serde implementations.
+pub(crate) const SERDE_CONFIG: EncodedConfig =
+ Config::DEFAULT.set_year_is_six_digits(true).encode();
+
+/// Serialize an [`OffsetDateTime`] using the well-known ISO 8601 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Iso8601::<SERDE_CONFIG>)
+ .map_err(S::Error::custom)?
+ .serialize(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))
+}
+
+/// Use the well-known ISO 8601 format when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known ISO 8601 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Iso8601::<SERDE_CONFIG>))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its ISO 8601 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Iso8601<SERDE_CONFIG>>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/mod.rs b/third_party/rust/time/src/serde/mod.rs
new file mode 100644
index 0000000000..e9f6d4394c
--- /dev/null
+++ b/third_party/rust/time/src/serde/mod.rs
@@ -0,0 +1,375 @@
+//! Differential formats for serde.
+// This also includes the serde implementations for all types. This doesn't need to be externally
+// documented, though.
+
+// Types with guaranteed stable serde representations. Strings are avoided to allow for optimal
+// representations in various binary forms.
+
+/// Consume the next item in a sequence.
+macro_rules! item {
+ ($seq:expr, $name:literal) => {
+ $seq.next_element()?
+ .ok_or_else(|| <A::Error as serde::de::Error>::custom(concat!("expected ", $name)))
+ };
+}
+
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod iso8601;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod rfc2822;
+#[cfg(any(feature = "formatting", feature = "parsing"))]
+pub mod rfc3339;
+pub mod timestamp;
+mod visitor;
+
+use core::marker::PhantomData;
+
+#[cfg(feature = "serde-human-readable")]
+use serde::ser::Error as _;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+/// Generate a custom serializer and deserializer from the provided string.
+///
+/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
+/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
+///
+/// # Usage
+///
+/// Invoked as `serde::format_description!(mod_name, Date, "<format string>")`. This puts a
+/// module named `mod_name` in the current scope that can be used to format `Date` structs. A
+/// submodule (`mod_name::option`) is also generated for `Option<Date>`. Both modules are only
+/// visible in the current scope.
+///
+/// The returned `Option` will contain a deserialized value if present and `None` if the field
+/// is present but the value is `null` (or the equivalent in other formats). To return `None`
+/// when the field is not present, you should use `#[serde(default)]` on the field.
+///
+/// # Examples
+///
+/// ```rust,no_run
+/// # use time::OffsetDateTime;
+#[cfg_attr(
+ all(feature = "formatting", feature = "parsing"),
+ doc = "use ::serde::{Serialize, Deserialize};"
+)]
+#[cfg_attr(
+ all(feature = "formatting", not(feature = "parsing")),
+ doc = "use ::serde::Serialize;"
+)]
+#[cfg_attr(
+ all(not(feature = "formatting"), feature = "parsing"),
+ doc = "use ::serde::Deserialize;"
+)]
+/// use time::serde;
+///
+/// // Makes a module `mod my_format { ... }`.
+/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]");
+#[cfg_attr(
+ all(feature = "formatting", feature = "parsing"),
+ doc = "#[derive(Serialize, Deserialize)]"
+)]
+#[cfg_attr(
+ all(feature = "formatting", not(feature = "parsing")),
+ doc = "#[derive(Serialize)]"
+)]
+#[cfg_attr(
+ all(not(feature = "formatting"), feature = "parsing"),
+ doc = "#[derive(Deserialize)]"
+)]
+/// # #[allow(dead_code)]
+/// struct SerializesWithCustom {
+/// #[serde(with = "my_format")]
+/// dt: OffsetDateTime,
+/// #[serde(with = "my_format::option")]
+/// maybe_dt: Option<OffsetDateTime>,
+/// }
+/// ```
+///
+/// [`format_description::parse()`]: crate::format_description::parse()
+#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))]
+pub use time_macros::serde_format_description as format_description;
+
+use self::visitor::Visitor;
+#[cfg(feature = "parsing")]
+use crate::format_description::{modifier, Component, FormatItem};
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+// region: Date
+/// The format used when serializing and deserializing a human-readable `Date`.
+#[cfg(feature = "parsing")]
+const DATE_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::Year(modifier::Year::default())),
+ FormatItem::Literal(b"-"),
+ FormatItem::Component(Component::Month(modifier::Month::default())),
+ FormatItem::Literal(b"-"),
+ FormatItem::Component(Component::Day(modifier::Day::default())),
+];
+
+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`")),
+ });
+ }
+
+ (self.year(), self.ordinal()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Date {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion date
+
+// region: Duration
+impl Serialize for Duration {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ return serializer.collect_str(&format_args!(
+ "{}.{:>09}",
+ self.whole_seconds(),
+ self.subsec_nanoseconds().abs()
+ ));
+ }
+
+ (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Duration {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Duration
+
+// region: OffsetDateTime
+/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
+#[cfg(feature = "parsing")]
+const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Compound(DATE_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(TIME_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(UTC_OFFSET_FORMAT),
+];
+
+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`")),
+ });
+ }
+
+ (
+ self.year(),
+ self.ordinal(),
+ self.hour(),
+ self.minute(),
+ self.second(),
+ self.nanosecond(),
+ self.offset.whole_hours(),
+ self.offset.minutes_past_hour(),
+ self.offset.seconds_past_minute(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for OffsetDateTime {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion OffsetDateTime
+
+// region: PrimitiveDateTime
+/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
+#[cfg(feature = "parsing")]
+const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Compound(DATE_FORMAT),
+ FormatItem::Literal(b" "),
+ FormatItem::Compound(TIME_FORMAT),
+];
+
+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`")),
+ });
+ }
+
+ (
+ self.year(),
+ self.ordinal(),
+ self.hour(),
+ self.minute(),
+ self.second(),
+ self.nanosecond(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for PrimitiveDateTime {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion PrimitiveDateTime
+
+// region: Time
+/// The format used when serializing and deserializing a human-readable `Time`.
+#[cfg(feature = "parsing")]
+const TIME_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::Hour(<modifier::Hour>::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::Minute(<modifier::Minute>::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::Second(<modifier::Second>::default())),
+ FormatItem::Literal(b"."),
+ FormatItem::Component(Component::Subsecond(<modifier::Subsecond>::default())),
+];
+
+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`")),
+ });
+ }
+
+ (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Time {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Time
+
+// region: UtcOffset
+/// The format used when serializing and deserializing a human-readable `UtcOffset`.
+#[cfg(feature = "parsing")]
+const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = &[
+ FormatItem::Component(Component::OffsetHour(modifier::OffsetHour::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
+ FormatItem::Literal(b":"),
+ FormatItem::Component(Component::OffsetSecond(modifier::OffsetSecond::default())),
+];
+
+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`")),
+ });
+ }
+
+ (
+ self.whole_hours(),
+ self.minutes_past_hour(),
+ self.seconds_past_minute(),
+ )
+ .serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for UtcOffset {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion UtcOffset
+
+// region: Weekday
+impl Serialize for Weekday {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ #[cfg(not(feature = "std"))]
+ use alloc::string::ToString;
+ return self.to_string().serialize(serializer);
+ }
+
+ self.number_from_monday().serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Weekday {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Weekday
+
+// region: Month
+impl Serialize for Month {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[cfg(feature = "serde-human-readable")]
+ if serializer.is_human_readable() {
+ #[cfg(not(feature = "std"))]
+ use alloc::string::String;
+ return self.to_string().serialize(serializer);
+ }
+
+ (*self as u8).serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for Month {
+ fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
+ if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
+ deserializer.deserialize_any(Visitor::<Self>(PhantomData))
+ } else {
+ deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
+ }
+ }
+}
+// endregion Month
diff --git a/third_party/rust/time/src/serde/rfc2822.rs b/third_party/rust/time/src/serde/rfc2822.rs
new file mode 100644
index 0000000000..eca90f5204
--- /dev/null
+++ b/third_party/rust/time/src/serde/rfc2822.rs
@@ -0,0 +1,72 @@
+//! Use the well-known [RFC2822 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::Rfc2822;
+use crate::OffsetDateTime;
+
+/// Serialize an [`OffsetDateTime`] using the well-known RFC2822 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Rfc2822)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+}
+
+/// Deserialize an [`OffsetDateTime`] from its RFC2822 representation.
+#[cfg(feature = "parsing")]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ deserializer.deserialize_str(Visitor::<Rfc2822>(PhantomData))
+}
+
+/// Use the well-known [RFC2822 format] when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC2822 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Rfc2822))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its RFC2822 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Rfc2822>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/rfc3339.rs b/third_party/rust/time/src/serde/rfc3339.rs
new file mode 100644
index 0000000000..b1ffb25130
--- /dev/null
+++ b/third_party/rust/time/src/serde/rfc3339.rs
@@ -0,0 +1,72 @@
+//! Use the well-known [RFC3339 format] when serializing and deserializing an [`OffsetDateTime`].
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6
+//! [with]: https://serde.rs/field-attrs.html#with
+
+#[cfg(feature = "parsing")]
+use core::marker::PhantomData;
+
+#[cfg(feature = "formatting")]
+use serde::ser::Error as _;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+#[cfg(feature = "formatting")]
+use serde::{Serialize, Serializer};
+
+#[cfg(feature = "parsing")]
+use super::Visitor;
+use crate::format_description::well_known::Rfc3339;
+use crate::OffsetDateTime;
+
+/// Serialize an [`OffsetDateTime`] using the well-known RFC3339 format.
+#[cfg(feature = "formatting")]
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime
+ .format(&Rfc3339)
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+}
+
+/// Deserialize an [`OffsetDateTime`] from its RFC3339 representation.
+#[cfg(feature = "parsing")]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ deserializer.deserialize_str(Visitor::<Rfc3339>(PhantomData))
+}
+
+/// Use the well-known [RFC3339 format] when serializing and deserializing an
+/// [`Option<OffsetDateTime>`].
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC3339 format.
+ #[cfg(feature = "formatting")]
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(|odt| odt.format(&Rfc3339))
+ .transpose()
+ .map_err(S::Error::custom)?
+ .serialize(serializer)
+ }
+
+ /// Deserialize an [`Option<OffsetDateTime>`] from its RFC3339 representation.
+ #[cfg(feature = "parsing")]
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer.deserialize_option(Visitor::<Option<Rfc3339>>(PhantomData))
+ }
+}
diff --git a/third_party/rust/time/src/serde/timestamp.rs b/third_party/rust/time/src/serde/timestamp.rs
new file mode 100644
index 0000000000..d86e6b9336
--- /dev/null
+++ b/third_party/rust/time/src/serde/timestamp.rs
@@ -0,0 +1,60 @@
+//! Treat an [`OffsetDateTime`] as a [Unix timestamp] for the purposes of serde.
+//!
+//! Use this module in combination with serde's [`#[with]`][with] attribute.
+//!
+//! When deserializing, the offset is assumed to be UTC.
+//!
+//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
+//! [with]: https://serde.rs/field-attrs.html#with
+
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
+
+use crate::OffsetDateTime;
+
+/// Serialize an `OffsetDateTime` as its Unix timestamp
+pub fn serialize<S: Serializer>(
+ datetime: &OffsetDateTime,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ datetime.unix_timestamp().serialize(serializer)
+}
+
+/// Deserialize an `OffsetDateTime` from its Unix timestamp
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+ OffsetDateTime::from_unix_timestamp(<_>::deserialize(deserializer)?)
+ .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
+}
+
+/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] for the purposes of
+/// serde.
+///
+/// Use this module in combination with serde's [`#[with]`][with] attribute.
+///
+/// When deserializing, the offset is assumed to be UTC.
+///
+/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
+/// [with]: https://serde.rs/field-attrs.html#with
+pub mod option {
+ #[allow(clippy::wildcard_imports)]
+ use super::*;
+
+ /// Serialize an `Option<OffsetDateTime>` as its Unix timestamp
+ pub fn serialize<S: Serializer>(
+ option: &Option<OffsetDateTime>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ option
+ .map(OffsetDateTime::unix_timestamp)
+ .serialize(serializer)
+ }
+
+ /// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp
+ pub fn deserialize<'a, D: Deserializer<'a>>(
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ Option::deserialize(deserializer)?
+ .map(OffsetDateTime::from_unix_timestamp)
+ .transpose()
+ .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
+ }
+}
diff --git a/third_party/rust/time/src/serde/visitor.rs b/third_party/rust/time/src/serde/visitor.rs
new file mode 100644
index 0000000000..e61989afde
--- /dev/null
+++ b/third_party/rust/time/src/serde/visitor.rs
@@ -0,0 +1,316 @@
+//! Serde visitor for various types.
+
+use core::fmt;
+use core::marker::PhantomData;
+
+use serde::de;
+#[cfg(feature = "parsing")]
+use serde::Deserializer;
+
+#[cfg(feature = "parsing")]
+use super::{
+ DATE_FORMAT, OFFSET_DATE_TIME_FORMAT, PRIMITIVE_DATE_TIME_FORMAT, TIME_FORMAT,
+ UTC_OFFSET_FORMAT,
+};
+use crate::error::ComponentRange;
+#[cfg(feature = "parsing")]
+use crate::format_description::well_known::*;
+use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
+
+/// A serde visitor for various types.
+pub(super) struct Visitor<T: ?Sized>(pub(super) PhantomData<T>);
+
+impl<'a> de::Visitor<'a> for Visitor<Date> {
+ type Value = Date;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Date`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Date, E> {
+ Date::parse(value, &DATE_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Date, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ Date::from_ordinal_date(year, ordinal).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Duration> {
+ type Value = Duration;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Duration`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Duration, E> {
+ let (seconds, nanoseconds) = value.split_once('.').ok_or_else(|| {
+ de::Error::invalid_value(de::Unexpected::Str(value), &"a decimal point")
+ })?;
+
+ let seconds = seconds
+ .parse()
+ .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(seconds), &"seconds"))?;
+ let mut nanoseconds = nanoseconds.parse().map_err(|_| {
+ de::Error::invalid_value(de::Unexpected::Str(nanoseconds), &"nanoseconds")
+ })?;
+
+ if seconds < 0 {
+ nanoseconds *= -1;
+ }
+
+ Ok(Duration::new(seconds, nanoseconds))
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Duration, A::Error> {
+ let seconds = item!(seq, "seconds")?;
+ let nanoseconds = item!(seq, "nanoseconds")?;
+ Ok(Duration::new(seconds, nanoseconds))
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<OffsetDateTime> {
+ type Value = OffsetDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("an `OffsetDateTime`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
+ OffsetDateTime::parse(value, &OFFSET_DATE_TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<OffsetDateTime, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+ let offset_hours = item!(seq, "offset hours")?;
+ let offset_minutes = item!(seq, "offset minutes")?;
+ let offset_seconds = item!(seq, "offset seconds")?;
+
+ Date::from_ordinal_date(year, ordinal)
+ .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
+ .and_then(|datetime| {
+ UtcOffset::from_hms(offset_hours, offset_minutes, offset_seconds)
+ .map(|offset| datetime.assume_offset(offset))
+ })
+ .map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<PrimitiveDateTime> {
+ type Value = PrimitiveDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `PrimitiveDateTime`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<PrimitiveDateTime, E> {
+ PrimitiveDateTime::parse(value, &PRIMITIVE_DATE_TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<PrimitiveDateTime, A::Error> {
+ let year = item!(seq, "year")?;
+ let ordinal = item!(seq, "day of year")?;
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+
+ Date::from_ordinal_date(year, ordinal)
+ .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
+ .map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Time> {
+ type Value = Time;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Time`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Time, E> {
+ Time::parse(value, &TIME_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<Time, A::Error> {
+ let hour = item!(seq, "hour")?;
+ let minute = item!(seq, "minute")?;
+ let second = item!(seq, "second")?;
+ let nanosecond = item!(seq, "nanosecond")?;
+
+ Time::from_hms_nano(hour, minute, second, nanosecond).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<UtcOffset> {
+ type Value = UtcOffset;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `UtcOffset`")
+ }
+
+ #[cfg(feature = "parsing")]
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<UtcOffset, E> {
+ UtcOffset::parse(value, &UTC_OFFSET_FORMAT).map_err(E::custom)
+ }
+
+ fn visit_seq<A: de::SeqAccess<'a>>(self, mut seq: A) -> Result<UtcOffset, A::Error> {
+ let hours = item!(seq, "offset hours")?;
+ let minutes = item!(seq, "offset minutes")?;
+ let seconds = item!(seq, "offset seconds")?;
+
+ UtcOffset::from_hms(hours, minutes, seconds).map_err(ComponentRange::into_de_error)
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Weekday> {
+ type Value = Weekday;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Weekday`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Weekday, E> {
+ match value {
+ "Monday" => Ok(Weekday::Monday),
+ "Tuesday" => Ok(Weekday::Tuesday),
+ "Wednesday" => Ok(Weekday::Wednesday),
+ "Thursday" => Ok(Weekday::Thursday),
+ "Friday" => Ok(Weekday::Friday),
+ "Saturday" => Ok(Weekday::Saturday),
+ "Sunday" => Ok(Weekday::Sunday),
+ _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Weekday`")),
+ }
+ }
+
+ fn visit_u64<E: de::Error>(self, value: u64) -> Result<Weekday, E> {
+ match value {
+ 1 => Ok(Weekday::Monday),
+ 2 => Ok(Weekday::Tuesday),
+ 3 => Ok(Weekday::Wednesday),
+ 4 => Ok(Weekday::Thursday),
+ 5 => Ok(Weekday::Friday),
+ 6 => Ok(Weekday::Saturday),
+ 7 => Ok(Weekday::Sunday),
+ _ => Err(E::invalid_value(
+ de::Unexpected::Unsigned(value),
+ &"a value in the range 1..=7",
+ )),
+ }
+ }
+}
+
+impl<'a> de::Visitor<'a> for Visitor<Month> {
+ type Value = Month;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a `Month`")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Month, E> {
+ match value {
+ "January" => Ok(Month::January),
+ "February" => Ok(Month::February),
+ "March" => Ok(Month::March),
+ "April" => Ok(Month::April),
+ "May" => Ok(Month::May),
+ "June" => Ok(Month::June),
+ "July" => Ok(Month::July),
+ "August" => Ok(Month::August),
+ "September" => Ok(Month::September),
+ "October" => Ok(Month::October),
+ "November" => Ok(Month::November),
+ "December" => Ok(Month::December),
+ _ => Err(E::invalid_value(de::Unexpected::Str(value), &"a `Month`")),
+ }
+ }
+
+ fn visit_u64<E: de::Error>(self, value: u64) -> Result<Month, E> {
+ match value {
+ 1 => Ok(Month::January),
+ 2 => Ok(Month::February),
+ 3 => Ok(Month::March),
+ 4 => Ok(Month::April),
+ 5 => Ok(Month::May),
+ 6 => Ok(Month::June),
+ 7 => Ok(Month::July),
+ 8 => Ok(Month::August),
+ 9 => Ok(Month::September),
+ 10 => Ok(Month::October),
+ 11 => Ok(Month::November),
+ 12 => Ok(Month::December),
+ _ => Err(E::invalid_value(
+ de::Unexpected::Unsigned(value),
+ &"a value in the range 1..=12",
+ )),
+ }
+ }
+}
+
+/// Implement a visitor for a well-known format.
+macro_rules! well_known {
+ ($article:literal, $name:literal, $($ty:tt)+) => {
+ #[cfg(feature = "parsing")]
+ impl<'a> de::Visitor<'a> for Visitor<$($ty)+> {
+ type Value = OffsetDateTime;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(concat!($article, " ", $name, "-formatted `OffsetDateTime`"))
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
+ OffsetDateTime::parse(value, &$($ty)+).map_err(E::custom)
+ }
+ }
+
+ #[cfg(feature = "parsing")]
+ impl<'a> de::Visitor<'a> for Visitor<Option<$($ty)+>> {
+ type Value = Option<OffsetDateTime>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(concat!(
+ $article,
+ " ",
+ $name,
+ "-formatted `Option<OffsetDateTime>`"
+ ))
+ }
+
+ fn visit_some<D: Deserializer<'a>>(
+ self,
+ deserializer: D,
+ ) -> Result<Option<OffsetDateTime>, D::Error> {
+ deserializer
+ .deserialize_any(Visitor::<$($ty)+>(PhantomData))
+ .map(Some)
+ }
+
+ fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> {
+ Ok(None)
+ }
+
+ fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+ }
+ };
+}
+
+well_known!("an", "RFC2822", Rfc2822);
+well_known!("an", "RFC3339", Rfc3339);
+well_known!(
+ "an",
+ "ISO 8601",
+ Iso8601::<{ super::iso8601::SERDE_CONFIG }>
+);
diff --git a/third_party/rust/time/src/sys/local_offset_at/imp.rs b/third_party/rust/time/src/sys/local_offset_at/imp.rs
new file mode 100644
index 0000000000..251fa667c2
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/imp.rs
@@ -0,0 +1,8 @@
+//! A fallback for any OS not covered.
+
+use crate::{OffsetDateTime, UtcOffset};
+
+#[allow(clippy::missing_docs_in_private_items)]
+pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> {
+ None
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/mod.rs b/third_party/rust/time/src/sys/local_offset_at/mod.rs
new file mode 100644
index 0000000000..f0bc4be3cc
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/mod.rs
@@ -0,0 +1,23 @@
+//! A method to obtain the local offset from UTC.
+
+#![allow(clippy::missing_const_for_fn)]
+
+#[cfg_attr(target_family = "windows", path = "windows.rs")]
+#[cfg_attr(target_family = "unix", path = "unix.rs")]
+#[cfg_attr(
+ all(
+ target_arch = "wasm32",
+ not(any(target_os = "emscripten", target_os = "wasi")),
+ feature = "wasm-bindgen"
+ ),
+ path = "wasm_js.rs"
+)]
+mod imp;
+
+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)
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/unix.rs b/third_party/rust/time/src/sys/local_offset_at/unix.rs
new file mode 100644
index 0000000000..6e849892da
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/unix.rs
@@ -0,0 +1,169 @@
+//! Get the system's UTC offset on Unix.
+
+use core::mem::MaybeUninit;
+
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error.
+///
+/// # Safety
+///
+/// This method must only be called when the process is single-threaded.
+///
+/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior
+/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set
+/// environment variables that makes it unsafe.
+unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
+ extern "C" {
+ #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")]
+ fn tzset();
+ }
+
+ // The exact type of `timestamp` beforehand can vary, so this conversion is necessary.
+ #[allow(clippy::useless_conversion)]
+ let timestamp = timestamp.try_into().ok()?;
+
+ let mut tm = MaybeUninit::uninit();
+
+ // Update timezone information from system. `localtime_r` does not do this for us.
+ //
+ // Safety: tzset is thread-safe.
+ unsafe { tzset() };
+
+ // Safety: We are calling a system API, which mutates the `tm` variable. If a null
+ // pointer is returned, an error occurred.
+ let tm_ptr = unsafe { libc::localtime_r(&timestamp, tm.as_mut_ptr()) };
+
+ if tm_ptr.is_null() {
+ None
+ } else {
+ // Safety: The value was initialized, as we no longer have a null pointer.
+ Some(unsafe { tm.assume_init() })
+ }
+}
+
+/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
+// This is available to any target known to have the `tm_gmtoff` extension.
+#[cfg(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> {
+ let seconds: i32 = tm.tm_gmtoff.try_into().ok()?;
+ UtcOffset::from_hms(
+ (seconds / 3_600) as _,
+ ((seconds / 60) % 60) as _,
+ (seconds % 60) as _,
+ )
+ .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> {
+ use crate::Date;
+
+ let mut tm = tm;
+ if tm.tm_sec == 60 {
+ // Leap seconds are not currently supported.
+ tm.tm_sec = 59;
+ }
+
+ let local_timestamp =
+ Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1)
+ .ok()?
+ .with_hms(
+ tm.tm_hour.try_into().ok()?,
+ tm.tm_min.try_into().ok()?,
+ tm.tm_sec.try_into().ok()?,
+ )
+ .ok()?
+ .assume_utc()
+ .unix_timestamp();
+
+ let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp())
+ .try_into()
+ .ok()?;
+
+ UtcOffset::from_hms(
+ (diff_secs / 3_600) as _,
+ ((diff_secs / 60) % 60) as _,
+ (diff_secs % 60) as _,
+ )
+ .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.
+ // 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)
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs
new file mode 100644
index 0000000000..fcea4b0f5a
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/wasm_js.rs
@@ -0,0 +1,12 @@
+use crate::{OffsetDateTime, UtcOffset};
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ let js_date: js_sys::Date = datetime.into();
+ // The number of minutes returned by getTimezoneOffset() is positive if the local time zone
+ // is behind UTC, and negative if the local time zone is ahead of UTC. For example,
+ // for UTC+10, -600 will be returned.
+ let timezone_offset = (js_date.get_timezone_offset() as i32) * -60;
+
+ UtcOffset::from_whole_seconds(timezone_offset).ok()
+}
diff --git a/third_party/rust/time/src/sys/local_offset_at/windows.rs b/third_party/rust/time/src/sys/local_offset_at/windows.rs
new file mode 100644
index 0000000000..fed01bf3a9
--- /dev/null
+++ b/third_party/rust/time/src/sys/local_offset_at/windows.rs
@@ -0,0 +1,114 @@
+//! Get the system's UTC offset on Windows.
+
+use core::mem::MaybeUninit;
+
+use crate::{OffsetDateTime, UtcOffset};
+
+// ffi: WINAPI FILETIME struct
+#[repr(C)]
+#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
+struct FileTime {
+ dwLowDateTime: u32,
+ dwHighDateTime: u32,
+}
+
+// ffi: WINAPI SYSTEMTIME struct
+#[repr(C)]
+#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
+struct SystemTime {
+ wYear: u16,
+ wMonth: u16,
+ wDayOfWeek: u16,
+ wDay: u16,
+ wHour: u16,
+ wMinute: u16,
+ wSecond: u16,
+ wMilliseconds: u16,
+}
+
+#[link(name = "kernel32")]
+extern "system" {
+ // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime
+ fn SystemTimeToFileTime(lpSystemTime: *const SystemTime, lpFileTime: *mut FileTime) -> i32;
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime
+ fn SystemTimeToTzSpecificLocalTime(
+ lpTimeZoneInformation: *const core::ffi::c_void, // We only pass `nullptr` here
+ lpUniversalTime: *const SystemTime,
+ lpLocalTime: *mut SystemTime,
+ ) -> i32;
+}
+
+/// Convert a `SYSTEMTIME` to a `FILETIME`. Returns `None` if any error occurred.
+fn systemtime_to_filetime(systime: &SystemTime) -> Option<FileTime> {
+ let mut ft = MaybeUninit::uninit();
+
+ // Safety: `SystemTimeToFileTime` is thread-safe.
+ if 0 == unsafe { SystemTimeToFileTime(systime, ft.as_mut_ptr()) } {
+ // failed
+ None
+ } else {
+ // Safety: The call succeeded.
+ Some(unsafe { ft.assume_init() })
+ }
+}
+
+/// Convert a `FILETIME` to an `i64`, representing a number of seconds.
+fn filetime_to_secs(filetime: &FileTime) -> i64 {
+ /// FILETIME represents 100-nanosecond intervals
+ const FT_TO_SECS: i64 = 10_000_000;
+ ((filetime.dwHighDateTime as i64) << 32 | filetime.dwLowDateTime as i64) / FT_TO_SECS
+}
+
+/// Convert an [`OffsetDateTime`] to a `SYSTEMTIME`.
+fn offset_to_systemtime(datetime: OffsetDateTime) -> SystemTime {
+ let (_, month, day_of_month) = datetime.to_offset(UtcOffset::UTC).date().to_calendar_date();
+ SystemTime {
+ wYear: datetime.year() as _,
+ wMonth: month as _,
+ wDay: day_of_month as _,
+ wDayOfWeek: 0, // ignored
+ wHour: datetime.hour() as _,
+ wMinute: datetime.minute() as _,
+ wSecond: datetime.second() as _,
+ wMilliseconds: datetime.millisecond(),
+ }
+}
+
+/// Obtain the system's UTC offset.
+pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
+ // This function falls back to UTC if any system call fails.
+ let systime_utc = offset_to_systemtime(datetime.to_offset(UtcOffset::UTC));
+
+ // Safety: `local_time` is only read if it is properly initialized, and
+ // `SystemTimeToTzSpecificLocalTime` is thread-safe.
+ let systime_local = unsafe {
+ let mut local_time = MaybeUninit::uninit();
+
+ if 0 == SystemTimeToTzSpecificLocalTime(
+ core::ptr::null(), // use system's current timezone
+ &systime_utc,
+ local_time.as_mut_ptr(),
+ ) {
+ // call failed
+ return None;
+ } else {
+ local_time.assume_init()
+ }
+ };
+
+ // Convert SYSTEMTIMEs to FILETIMEs so we can perform arithmetic on them.
+ 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))
+ .try_into()
+ .ok()?;
+
+ UtcOffset::from_hms(
+ (diff_secs / 3_600) as _,
+ ((diff_secs / 60) % 60) as _,
+ (diff_secs % 60) as _,
+ )
+ .ok()
+}
diff --git a/third_party/rust/time/src/sys/mod.rs b/third_party/rust/time/src/sys/mod.rs
new file mode 100644
index 0000000000..90bbf7ff3b
--- /dev/null
+++ b/third_party/rust/time/src/sys/mod.rs
@@ -0,0 +1,9 @@
+//! Functions with a common interface that rely on system calls.
+
+#![allow(unsafe_code)] // We're interfacing with system calls.
+
+#[cfg(feature = "local-offset")]
+mod local_offset_at;
+
+#[cfg(feature = "local-offset")]
+pub(crate) use local_offset_at::local_offset_at;
diff --git a/third_party/rust/time/src/tests.rs b/third_party/rust/time/src/tests.rs
new file mode 100644
index 0000000000..66c977d91d
--- /dev/null
+++ b/third_party/rust/time/src/tests.rs
@@ -0,0 +1,113 @@
+#![cfg(all( // require `--all-features` to be passed
+ feature = "default",
+ feature = "alloc",
+ feature = "formatting",
+ feature = "large-dates",
+ feature = "local-offset",
+ feature = "macros",
+ feature = "parsing",
+ feature = "quickcheck",
+ feature = "serde-human-readable",
+ feature = "serde-well-known",
+ feature = "std",
+ feature = "rand",
+ feature = "serde",
+))]
+#![allow(
+ clippy::let_underscore_drop,
+ clippy::clone_on_copy,
+ clippy::cognitive_complexity,
+ clippy::std_instead_of_core
+)]
+
+//! Tests for internal details.
+//!
+//! This module should only be used when it is not possible to test the implementation in a
+//! reasonable manner externally.
+
+use std::num::NonZeroU8;
+
+use crate::formatting::DigitCount;
+use crate::parsing::combinator::rfc::iso8601;
+use crate::parsing::shim::Integer;
+use crate::{duration, parsing};
+
+#[test]
+fn digit_count() {
+ assert_eq!(1_u8.num_digits(), 1);
+ assert_eq!(9_u8.num_digits(), 1);
+ assert_eq!(10_u8.num_digits(), 2);
+ assert_eq!(99_u8.num_digits(), 2);
+ assert_eq!(100_u8.num_digits(), 3);
+
+ assert_eq!(1_u16.num_digits(), 1);
+ assert_eq!(9_u16.num_digits(), 1);
+ assert_eq!(10_u16.num_digits(), 2);
+ assert_eq!(99_u16.num_digits(), 2);
+ assert_eq!(100_u16.num_digits(), 3);
+ assert_eq!(999_u16.num_digits(), 3);
+ assert_eq!(1_000_u16.num_digits(), 4);
+ assert_eq!(9_999_u16.num_digits(), 4);
+ assert_eq!(10_000_u16.num_digits(), 5);
+
+ assert_eq!(1_u32.num_digits(), 1);
+ assert_eq!(9_u32.num_digits(), 1);
+ assert_eq!(10_u32.num_digits(), 2);
+ assert_eq!(99_u32.num_digits(), 2);
+ assert_eq!(100_u32.num_digits(), 3);
+ assert_eq!(999_u32.num_digits(), 3);
+ assert_eq!(1_000_u32.num_digits(), 4);
+ assert_eq!(9_999_u32.num_digits(), 4);
+ assert_eq!(10_000_u32.num_digits(), 5);
+ assert_eq!(99_999_u32.num_digits(), 5);
+ assert_eq!(100_000_u32.num_digits(), 6);
+ assert_eq!(999_999_u32.num_digits(), 6);
+ assert_eq!(1_000_000_u32.num_digits(), 7);
+ assert_eq!(9_999_999_u32.num_digits(), 7);
+ assert_eq!(10_000_000_u32.num_digits(), 8);
+ assert_eq!(99_999_999_u32.num_digits(), 8);
+ assert_eq!(100_000_000_u32.num_digits(), 9);
+ assert_eq!(999_999_999_u32.num_digits(), 9);
+ assert_eq!(1_000_000_000_u32.num_digits(), 10);
+}
+
+#[test]
+fn default() {
+ assert_eq!(
+ duration::Padding::Optimize.clone(),
+ duration::Padding::default()
+ );
+}
+
+#[test]
+fn debug() {
+ let _ = format!("{:?}", duration::Padding::Optimize);
+ let _ = format!("{:?}", parsing::ParsedItem(b"", 0));
+ let _ = format!("{:?}", parsing::component::Period::Am);
+ let _ = format!("{:?}", iso8601::ExtendedKind::Basic);
+}
+
+#[test]
+fn clone() {
+ assert_eq!(
+ parsing::component::Period::Am.clone(),
+ parsing::component::Period::Am
+ );
+ // does not impl Debug
+ assert!(crate::time::Padding::Optimize.clone() == crate::time::Padding::Optimize);
+ // does not impl PartialEq
+ assert!(matches!(
+ iso8601::ExtendedKind::Basic.clone(),
+ iso8601::ExtendedKind::Basic
+ ));
+}
+
+#[test]
+fn parsing_internals() {
+ assert!(
+ parsing::ParsedItem(b"", ())
+ .flat_map(|_| None::<()>)
+ .is_none()
+ );
+ assert!(<NonZeroU8 as Integer>::parse_bytes(b"256").is_none());
+}
diff --git a/third_party/rust/time/src/time.rs b/third_party/rust/time/src/time.rs
new file mode 100644
index 0000000000..32fa97790f
--- /dev/null
+++ b/third_party/rust/time/src/time.rs
@@ -0,0 +1,761 @@
+//! The [`Time`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::{Add, Sub};
+use core::time::Duration as StdDuration;
+#[cfg(feature = "formatting")]
+use std::io;
+
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+use crate::util::DateAdjustment;
+use crate::{error, Duration};
+
+/// By explicitly inserting this enum where padding is expected, the compiler is able to better
+/// perform niche value optimization.
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub(crate) enum Padding {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Optimize,
+}
+
+/// The clock time within a given date. Nanosecond precision.
+///
+/// All minutes are assumed to have exactly 60 seconds; no attempt is made to handle leap seconds
+/// (either positive or negative).
+///
+/// When comparing two `Time`s, they are assumed to be in the same calendar date.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Time {
+ #[allow(clippy::missing_docs_in_private_items)]
+ hour: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ minute: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ second: u8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ nanosecond: u32,
+ #[allow(clippy::missing_docs_in_private_items)]
+ padding: Padding,
+}
+
+impl Time {
+ /// Create a `Time` that is exactly midnight.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// # use time_macros::time;
+ /// assert_eq!(Time::MIDNIGHT, time!(0:00));
+ /// ```
+ pub const MIDNIGHT: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0);
+
+ /// The smallest value that can be represented by `Time`.
+ ///
+ /// `00:00:00.0`
+ pub(crate) const MIN: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0);
+
+ /// The largest value that can be represented by `Time`.
+ ///
+ /// `23:59:59.999_999_999`
+ pub(crate) const MAX: Self = Self::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999);
+
+ // region: constructors
+ /// Create a `Time` from its components.
+ #[doc(hidden)]
+ pub const fn __from_hms_nanos_unchecked(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Self {
+ debug_assert!(hour < 24);
+ debug_assert!(minute < 60);
+ debug_assert!(second < 60);
+ debug_assert!(nanosecond < 1_000_000_000);
+
+ Self {
+ hour,
+ minute,
+ second,
+ nanosecond,
+ padding: Padding::Optimize,
+ }
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms(1, 2, 3).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms(24, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms(0, 60, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms(0, 0, 60).is_err()); // 60 isn't a valid second.
+ /// ```
+ pub const fn from_hms(hour: u8, minute: u8, second: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(hour, minute, second, 0))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_milli(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_milli(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_milli(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_milli(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_milli(0, 0, 0, 1_000).is_err()); // 1_000 isn't a valid millisecond.
+ /// ```
+ pub const fn from_hms_milli(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(millisecond in 0 => 999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ millisecond as u32 * 1_000_000,
+ ))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_micro(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_micro(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_micro(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_micro(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_micro(0, 0, 0, 1_000_000).is_err()); // 1_000_000 isn't a valid microsecond.
+ /// ```
+ pub const fn from_hms_micro(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(microsecond in 0 => 999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ minute,
+ second,
+ microsecond * 1_000,
+ ))
+ }
+
+ /// Attempt to create a `Time` from the hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_nano(1, 2, 3, 4).is_ok());
+ /// ```
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// assert!(Time::from_hms_nano(24, 0, 0, 0).is_err()); // 24 isn't a valid hour.
+ /// assert!(Time::from_hms_nano(0, 60, 0, 0).is_err()); // 60 isn't a valid minute.
+ /// assert!(Time::from_hms_nano(0, 0, 60, 0).is_err()); // 60 isn't a valid second.
+ /// assert!(Time::from_hms_nano(0, 0, 0, 1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond.
+ /// ```
+ pub const fn from_hms_nano(
+ hour: u8,
+ minute: u8,
+ second: u8,
+ nanosecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ ensure_value_in_range!(minute in 0 => 59);
+ ensure_value_in_range!(second in 0 => 59);
+ ensure_value_in_range!(nanosecond in 0 => 999_999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour, minute, second, nanosecond,
+ ))
+ }
+ // endregion constructors
+
+ // region: getters
+ /// Get the clock hour, minute, and second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms(), (0, 0, 0));
+ /// assert_eq!(time!(23:59:59).as_hms(), (23, 59, 59));
+ /// ```
+ pub const fn as_hms(self) -> (u8, u8, u8) {
+ (self.hour, self.minute, self.second)
+ }
+
+ /// Get the clock hour, minute, second, and millisecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_milli(), (0, 0, 0, 0));
+ /// assert_eq!(time!(23:59:59.999).as_hms_milli(), (23, 59, 59, 999));
+ /// ```
+ pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) {
+ (
+ self.hour,
+ self.minute,
+ self.second,
+ (self.nanosecond / 1_000_000) as u16,
+ )
+ }
+
+ /// Get the clock hour, minute, second, and microsecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_micro(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// time!(23:59:59.999_999).as_hms_micro(),
+ /// (23, 59, 59, 999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) {
+ (self.hour, self.minute, self.second, self.nanosecond / 1_000)
+ }
+
+ /// Get the clock hour, minute, second, and nanosecond.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).as_hms_nano(), (0, 0, 0, 0));
+ /// assert_eq!(
+ /// time!(23:59:59.999_999_999).as_hms_nano(),
+ /// (23, 59, 59, 999_999_999)
+ /// );
+ /// ```
+ pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) {
+ (self.hour, self.minute, self.second, self.nanosecond)
+ }
+
+ /// Get the clock hour.
+ ///
+ /// The returned value will always be in the range `0..24`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).hour(), 0);
+ /// assert_eq!(time!(23:59:59).hour(), 23);
+ /// ```
+ pub const fn hour(self) -> u8 {
+ self.hour
+ }
+
+ /// Get the minute within the hour.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).minute(), 0);
+ /// assert_eq!(time!(23:59:59).minute(), 59);
+ /// ```
+ pub const fn minute(self) -> u8 {
+ self.minute
+ }
+
+ /// Get the second within the minute.
+ ///
+ /// The returned value will always be in the range `0..60`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00:00).second(), 0);
+ /// assert_eq!(time!(23:59:59).second(), 59);
+ /// ```
+ pub const fn second(self) -> u8 {
+ self.second
+ }
+
+ /// Get the milliseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).millisecond(), 0);
+ /// assert_eq!(time!(23:59:59.999).millisecond(), 999);
+ /// ```
+ pub const fn millisecond(self) -> u16 {
+ (self.nanosecond / 1_000_000) as _
+ }
+
+ /// Get the microseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).microsecond(), 0);
+ /// assert_eq!(time!(23:59:59.999_999).microsecond(), 999_999);
+ /// ```
+ pub const fn microsecond(self) -> u32 {
+ self.nanosecond / 1_000
+ }
+
+ /// Get the nanoseconds within the second.
+ ///
+ /// The returned value will always be in the range `0..1_000_000_000`.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00).nanosecond(), 0);
+ /// assert_eq!(time!(23:59:59.999_999_999).nanosecond(), 999_999_999);
+ /// ```
+ pub const fn nanosecond(self) -> u32 {
+ self.nanosecond
+ }
+ // endregion getters
+
+ // region: arithmetic helpers
+ /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning whether
+ /// the date is different.
+ pub(crate) const fn adjusting_add(self, duration: Duration) -> (DateAdjustment, Self) {
+ let mut nanoseconds = self.nanosecond as i32 + duration.subsec_nanoseconds();
+ let mut seconds = self.second as i8 + (duration.whole_seconds() % 60) as i8;
+ let mut minutes = self.minute as i8 + (duration.whole_minutes() % 60) as i8;
+ let mut hours = self.hour as i8 + (duration.whole_hours() % 24) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..1_000_000_000 => seconds);
+ cascade!(seconds in 0..60 => minutes);
+ cascade!(minutes in 0..60 => hours);
+ if hours >= 24 {
+ hours -= 24;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += 24;
+ date_adjustment = DateAdjustment::Previous;
+ }
+
+ (
+ date_adjustment,
+ Self::__from_hms_nanos_unchecked(
+ hours as _,
+ minutes as _,
+ seconds as _,
+ nanoseconds as _,
+ ),
+ )
+ }
+
+ /// Subtract the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning
+ /// whether the date is different.
+ pub(crate) const fn adjusting_sub(self, duration: Duration) -> (DateAdjustment, Self) {
+ let mut nanoseconds = self.nanosecond as i32 - duration.subsec_nanoseconds();
+ let mut seconds = self.second as i8 - (duration.whole_seconds() % 60) as i8;
+ let mut minutes = self.minute as i8 - (duration.whole_minutes() % 60) as i8;
+ let mut hours = self.hour as i8 - (duration.whole_hours() % 24) as i8;
+ let mut date_adjustment = DateAdjustment::None;
+
+ cascade!(nanoseconds in 0..1_000_000_000 => seconds);
+ cascade!(seconds in 0..60 => minutes);
+ cascade!(minutes in 0..60 => hours);
+ if hours >= 24 {
+ hours -= 24;
+ date_adjustment = DateAdjustment::Next;
+ } else if hours < 0 {
+ hours += 24;
+ date_adjustment = DateAdjustment::Previous;
+ }
+
+ (
+ date_adjustment,
+ Self::__from_hms_nanos_unchecked(
+ hours as _,
+ minutes as _,
+ seconds as _,
+ nanoseconds as _,
+ ),
+ )
+ }
+
+ /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow,
+ /// returning whether the date is the previous date as the first element of the tuple.
+ pub(crate) const fn adjusting_add_std(self, duration: StdDuration) -> (bool, Self) {
+ let mut nanosecond = self.nanosecond + duration.subsec_nanos();
+ let mut second = self.second + (duration.as_secs() % 60) as u8;
+ let mut minute = self.minute + ((duration.as_secs() / 60) % 60) as u8;
+ let mut hour = self.hour + ((duration.as_secs() / 3_600) % 24) as u8;
+ let mut is_next_day = false;
+
+ cascade!(nanosecond in 0..1_000_000_000 => second);
+ cascade!(second in 0..60 => minute);
+ cascade!(minute in 0..60 => hour);
+ if hour >= 24 {
+ hour -= 24;
+ is_next_day = true;
+ }
+
+ (
+ is_next_day,
+ Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond),
+ )
+ }
+
+ /// Subtract the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow,
+ /// returning whether the date is the previous date as the first element of the tuple.
+ pub(crate) const fn adjusting_sub_std(self, duration: StdDuration) -> (bool, Self) {
+ let mut nanosecond = self.nanosecond as i32 - duration.subsec_nanos() as i32;
+ let mut second = self.second as i8 - (duration.as_secs() % 60) as i8;
+ let mut minute = self.minute as i8 - ((duration.as_secs() / 60) % 60) as i8;
+ let mut hour = self.hour as i8 - ((duration.as_secs() / 3_600) % 24) as i8;
+ let mut is_previous_day = false;
+
+ cascade!(nanosecond in 0..1_000_000_000 => second);
+ cascade!(second in 0..60 => minute);
+ cascade!(minute in 0..60 => hour);
+ if hour < 0 {
+ hour += 24;
+ is_previous_day = true;
+ }
+
+ (
+ is_previous_day,
+ Self::__from_hms_nanos_unchecked(hour as _, minute as _, second as _, nanosecond as _),
+ )
+ }
+ // endregion arithmetic helpers
+
+ // region: replacement
+ /// Replace the clock hour.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_hour(7),
+ /// Ok(time!(07:02:03.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hour in 0 => 23);
+ Ok(Self::__from_hms_nanos_unchecked(
+ hour,
+ self.minute,
+ self.second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the minutes within the hour.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_minute(7),
+ /// Ok(time!(01:07:03.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(minute in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ minute,
+ self.second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the seconds within the minute.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_second(7),
+ /// Ok(time!(01:02:07.004_005_006))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(second in 0 => 59);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ second,
+ self.nanosecond,
+ ))
+ }
+
+ /// Replace the milliseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_millisecond(7),
+ /// Ok(time!(01:02:03.007))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_millisecond(
+ self,
+ millisecond: u16,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(millisecond in 0 => 999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ millisecond as u32 * 1_000_000,
+ ))
+ }
+
+ /// Replace the microseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_microsecond(7_008),
+ /// Ok(time!(01:02:03.007_008))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_microsecond(
+ self,
+ microsecond: u32,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(microsecond in 0 => 999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ microsecond * 1000,
+ ))
+ }
+
+ /// Replace the nanoseconds within the second.
+ ///
+ /// ```rust
+ /// # use time_macros::time;
+ /// assert_eq!(
+ /// time!(01:02:03.004_005_006).replace_nanosecond(7_008_009),
+ /// Ok(time!(01:02:03.007_008_009))
+ /// );
+ /// assert!(time!(01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond
+ /// ```
+ #[must_use = "This method does not mutate the original `Time`."]
+ pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(nanosecond in 0 => 999_999_999);
+ Ok(Self::__from_hms_nanos_unchecked(
+ self.hour,
+ self.minute,
+ self.second,
+ nanosecond,
+ ))
+ }
+ // endregion replacement
+}
+
+// region: formatting & parsing
+#[cfg(feature = "formatting")]
+impl Time {
+ /// Format the `Time` using the provided [format description](crate::format_description).
+ pub fn format_into(
+ self,
+ output: &mut impl io::Write,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<usize, crate::error::Format> {
+ format.format_into(output, None, Some(self), None)
+ }
+
+ /// Format the `Time` using the provided [format description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::format_description;
+ /// # use time_macros::time;
+ /// let format = format_description::parse("[hour]:[minute]:[second]")?;
+ /// assert_eq!(time!(12:00).format(&format)?, "12:00:00");
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn format(
+ self,
+ format: &(impl Formattable + ?Sized),
+ ) -> Result<String, crate::error::Format> {
+ format.format(None, Some(self), None)
+ }
+}
+
+#[cfg(feature = "parsing")]
+impl Time {
+ /// Parse a `Time` from the input using the provided [format
+ /// description](crate::format_description).
+ ///
+ /// ```rust
+ /// # use time::Time;
+ /// # use time_macros::{time, format_description};
+ /// let format = format_description!("[hour]:[minute]:[second]");
+ /// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00));
+ /// # Ok::<_, time::Error>(())
+ /// ```
+ pub fn parse(
+ input: &str,
+ description: &(impl Parsable + ?Sized),
+ ) -> Result<Self, error::Parse> {
+ description.parse_time(input.as_bytes())
+ }
+}
+
+impl fmt::Display for Time {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let (value, width) = match self.nanosecond() {
+ nanos if nanos % 10 != 0 => (nanos, 9),
+ nanos if (nanos / 10) % 10 != 0 => (nanos / 10, 8),
+ nanos if (nanos / 100) % 10 != 0 => (nanos / 100, 7),
+ nanos if (nanos / 1_000) % 10 != 0 => (nanos / 1_000, 6),
+ nanos if (nanos / 10_000) % 10 != 0 => (nanos / 10_000, 5),
+ nanos if (nanos / 100_000) % 10 != 0 => (nanos / 100_000, 4),
+ nanos if (nanos / 1_000_000) % 10 != 0 => (nanos / 1_000_000, 3),
+ nanos if (nanos / 10_000_000) % 10 != 0 => (nanos / 10_000_000, 2),
+ nanos => (nanos / 100_000_000, 1),
+ };
+ write!(
+ f,
+ "{}:{:02}:{:02}.{value:0width$}",
+ self.hour, self.minute, self.second,
+ )
+ }
+}
+
+impl fmt::Debug for Time {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+// region: trait impls
+impl Add<Duration> for Time {
+ type Output = Self;
+
+ /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(12:00) + 2.hours(), time!(14:00));
+ /// assert_eq!(time!(0:00:01) + (-2).seconds(), time!(23:59:59));
+ /// ```
+ fn add(self, duration: Duration) -> Self::Output {
+ self.adjusting_add(duration).1
+ }
+}
+
+impl Add<StdDuration> for Time {
+ type Output = Self;
+
+ /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalStdDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(12:00) + 2.std_hours(), time!(14:00));
+ /// assert_eq!(time!(23:59:59) + 2.std_seconds(), time!(0:00:01));
+ /// ```
+ fn add(self, duration: StdDuration) -> Self::Output {
+ self.adjusting_add_std(duration).1
+ }
+}
+
+impl_add_assign!(Time: Duration, StdDuration);
+
+impl Sub<Duration> for Time {
+ type Output = Self;
+
+ /// Subtract the sub-day time of the [`Duration`] from the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(14:00) - 2.hours(), time!(12:00));
+ /// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01));
+ /// ```
+ fn sub(self, duration: Duration) -> Self::Output {
+ self.adjusting_sub(duration).1
+ }
+}
+
+impl Sub<StdDuration> for Time {
+ type Output = Self;
+
+ /// Subtract the sub-day time of the [`std::time::Duration`] from the `Time`. Wraps on overflow.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalStdDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(14:00) - 2.std_hours(), time!(12:00));
+ /// assert_eq!(time!(0:00:01) - 2.std_seconds(), time!(23:59:59));
+ /// ```
+ fn sub(self, duration: StdDuration) -> Self::Output {
+ self.adjusting_sub_std(duration).1
+ }
+}
+
+impl_sub_assign!(Time: Duration, StdDuration);
+
+impl Sub for Time {
+ type Output = Duration;
+
+ /// Subtract two `Time`s, returning the [`Duration`] between. This assumes both `Time`s are in
+ /// the same calendar day.
+ ///
+ /// ```rust
+ /// # use time::ext::NumericalDuration;
+ /// # use time_macros::time;
+ /// assert_eq!(time!(0:00) - time!(0:00), 0.seconds());
+ /// assert_eq!(time!(1:00) - time!(0:00), 1.hours());
+ /// assert_eq!(time!(0:00) - time!(1:00), (-1).hours());
+ /// assert_eq!(time!(0:00) - time!(23:00), (-23).hours());
+ /// ```
+ fn sub(self, rhs: Self) -> Self::Output {
+ let hour_diff = (self.hour as i8) - (rhs.hour as i8);
+ let minute_diff = (self.minute as i8) - (rhs.minute as i8);
+ let second_diff = (self.second as i8) - (rhs.second as i8);
+ let nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32);
+
+ let seconds = hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64;
+
+ let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 {
+ (seconds - 1, nanosecond_diff + 1_000_000_000)
+ } else if seconds < 0 && nanosecond_diff > 0 {
+ (seconds + 1, nanosecond_diff - 1_000_000_000)
+ } else {
+ (seconds, nanosecond_diff)
+ };
+
+ Duration::new_unchecked(seconds, nanoseconds)
+ }
+}
+// endregion trait impls
diff --git a/third_party/rust/time/src/utc_offset.rs b/third_party/rust/time/src/utc_offset.rs
new file mode 100644
index 0000000000..d69f0c1e39
--- /dev/null
+++ b/third_party/rust/time/src/utc_offset.rs
@@ -0,0 +1,346 @@
+//! The [`UtcOffset`] struct and its associated `impl`s.
+
+use core::fmt;
+use core::ops::Neg;
+#[cfg(feature = "formatting")]
+use std::io;
+
+use crate::error;
+#[cfg(feature = "formatting")]
+use crate::formatting::Formattable;
+#[cfg(feature = "parsing")]
+use crate::parsing::Parsable;
+#[cfg(feature = "local-offset")]
+use crate::sys::local_offset_at;
+#[cfg(feature = "local-offset")]
+use crate::OffsetDateTime;
+
+/// An offset from UTC.
+///
+/// This struct can store values up to ±23: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 {
+ #[allow(clippy::missing_docs_in_private_items)]
+ hours: i8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ minutes: i8,
+ #[allow(clippy::missing_docs_in_private_items)]
+ seconds: i8,
+}
+
+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_hms_unchecked(0, 0, 0);
+
+ // region: constructors
+ /// 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.
+ #[doc(hidden)]
+ pub const fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self {
+ if hours < 0 {
+ debug_assert!(minutes <= 0);
+ debug_assert!(seconds <= 0);
+ } else if hours > 0 {
+ debug_assert!(minutes >= 0);
+ debug_assert!(seconds >= 0);
+ }
+ if minutes < 0 {
+ debug_assert!(seconds <= 0);
+ } else if minutes > 0 {
+ debug_assert!(seconds >= 0);
+ }
+ debug_assert!(hours.unsigned_abs() < 24);
+ debug_assert!(minutes.unsigned_abs() < 60);
+ debug_assert!(seconds.unsigned_abs() < 60);
+
+ 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.
+ ///
+ /// ```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,
+ mut minutes: i8,
+ mut seconds: i8,
+ ) -> Result<Self, error::ComponentRange> {
+ ensure_value_in_range!(hours in -23 => 23);
+ ensure_value_in_range!(minutes in -59 => 59);
+ ensure_value_in_range!(seconds in -59 => 59);
+
+ if (hours > 0 && minutes < 0) || (hours < 0 && minutes > 0) {
+ minutes *= -1;
+ }
+ if (hours > 0 && seconds < 0)
+ || (hours < 0 && seconds > 0)
+ || (minutes > 0 && seconds < 0)
+ || (minutes < 0 && seconds > 0)
+ {
+ seconds *= -1;
+ }
+
+ Ok(Self::__from_hms_unchecked(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> {
+ ensure_value_in_range!(seconds in -86_399 => 86_399);
+
+ Ok(Self::__from_hms_unchecked(
+ (seconds / 3_600) as _,
+ ((seconds / 60) % 60) as _,
+ (seconds % 60) as _,
+ ))
+ }
+ // endregion constructors
+
+ // region: getters
+ /// 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, 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
+ }
+
+ /// 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 as i16 * 60 + self.minutes 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
+ }
+
+ /// 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 as i32 * 3_600 + self.minutes as i32 * 60 + self.seconds 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
+ }
+ // endregion getters
+
+ // region: is_{sign}
+ /// 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 == 0 && self.minutes == 0 && self.seconds == 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 > 0 || self.minutes > 0 || self.seconds > 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 < 0 || self.minutes < 0 || self.seconds < 0
+ }
+ // endregion is_{sign}
+
+ // region: local offset
+ /// 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)
+ }
+ // endregion: local offset
+}
+
+// region: formatting & parsing
+#[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,
+ 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())
+ }
+}
+
+impl fmt::Display for UtcOffset {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}{:02}:{:02}:{:02}",
+ if self.is_negative() { '-' } else { '+' },
+ self.hours.abs(),
+ self.minutes.abs(),
+ self.seconds.abs()
+ )
+ }
+}
+
+impl fmt::Debug for UtcOffset {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+// endregion formatting & parsing
+
+impl Neg for UtcOffset {
+ type Output = Self;
+
+ fn neg(self) -> Self::Output {
+ Self::__from_hms_unchecked(-self.hours, -self.minutes, -self.seconds)
+ }
+}
diff --git a/third_party/rust/time/src/util.rs b/third_party/rust/time/src/util.rs
new file mode 100644
index 0000000000..11330501ca
--- /dev/null
+++ b/third_party/rust/time/src/util.rs
@@ -0,0 +1,31 @@
+//! Utility functions.
+
+pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year};
+
+use crate::Month;
+
+/// Whether to adjust the date, and in which direction. Useful when implementing arithmetic.
+pub(crate) enum DateAdjustment {
+ /// The previous day should be used.
+ Previous,
+ /// The next day should be used.
+ Next,
+ /// The date should be used as-is.
+ None,
+}
+
+/// Get the number of days in the month of a given year.
+///
+/// ```rust
+/// # use time::{Month, util};
+/// assert_eq!(util::days_in_year_month(2020, Month::February), 29);
+/// ```
+pub const fn days_in_year_month(year: i32, month: Month) -> u8 {
+ use Month::*;
+ match month {
+ January | March | May | July | August | October | December => 31,
+ April | June | September | November => 30,
+ February if is_leap_year(year) => 29,
+ February => 28,
+ }
+}
diff --git a/third_party/rust/time/src/weekday.rs b/third_party/rust/time/src/weekday.rs
new file mode 100644
index 0000000000..f499c30eef
--- /dev/null
+++ b/third_party/rust/time/src/weekday.rs
@@ -0,0 +1,148 @@
+//! Days of the week.
+
+use core::fmt::{self, Display};
+use core::str::FromStr;
+
+use Weekday::*;
+
+use crate::error;
+
+/// Days of the week.
+///
+/// As order is dependent on context (Sunday could be either two days after or five days before
+/// Friday), this type does not implement `PartialOrd` or `Ord`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Weekday {
+ #[allow(clippy::missing_docs_in_private_items)]
+ Monday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Tuesday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Wednesday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Thursday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Friday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Saturday,
+ #[allow(clippy::missing_docs_in_private_items)]
+ Sunday,
+}
+
+impl Weekday {
+ /// Get the previous weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Tuesday.previous(), Weekday::Monday);
+ /// ```
+ pub const fn previous(self) -> Self {
+ match self {
+ Monday => Sunday,
+ Tuesday => Monday,
+ Wednesday => Tuesday,
+ Thursday => Wednesday,
+ Friday => Thursday,
+ Saturday => Friday,
+ Sunday => Saturday,
+ }
+ }
+
+ /// Get the next weekday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.next(), Weekday::Tuesday);
+ /// ```
+ pub const fn next(self) -> Self {
+ match self {
+ Monday => Tuesday,
+ Tuesday => Wednesday,
+ Wednesday => Thursday,
+ Thursday => Friday,
+ Friday => Saturday,
+ Saturday => Sunday,
+ Sunday => Monday,
+ }
+ }
+
+ /// Get the one-indexed number of days from Monday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_from_monday(), 1);
+ /// ```
+ #[doc(alias = "iso_weekday_number")]
+ pub const fn number_from_monday(self) -> u8 {
+ self.number_days_from_monday() + 1
+ }
+
+ /// Get the one-indexed number of days from Sunday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_from_sunday(), 2);
+ /// ```
+ pub const fn number_from_sunday(self) -> u8 {
+ self.number_days_from_sunday() + 1
+ }
+
+ /// Get the zero-indexed number of days from Monday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_days_from_monday(), 0);
+ /// ```
+ pub const fn number_days_from_monday(self) -> u8 {
+ self as _
+ }
+
+ /// Get the zero-indexed number of days from Sunday.
+ ///
+ /// ```rust
+ /// # use time::Weekday;
+ /// assert_eq!(Weekday::Monday.number_days_from_sunday(), 1);
+ /// ```
+ pub const fn number_days_from_sunday(self) -> u8 {
+ match self {
+ Monday => 1,
+ Tuesday => 2,
+ Wednesday => 3,
+ Thursday => 4,
+ Friday => 5,
+ Saturday => 6,
+ Sunday => 0,
+ }
+ }
+}
+
+impl Display for Weekday {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ Monday => "Monday",
+ Tuesday => "Tuesday",
+ Wednesday => "Wednesday",
+ Thursday => "Thursday",
+ Friday => "Friday",
+ Saturday => "Saturday",
+ Sunday => "Sunday",
+ })
+ }
+}
+
+impl FromStr for Weekday {
+ type Err = error::InvalidVariant;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "Monday" => Ok(Monday),
+ "Tuesday" => Ok(Tuesday),
+ "Wednesday" => Ok(Wednesday),
+ "Thursday" => Ok(Thursday),
+ "Friday" => Ok(Friday),
+ "Saturday" => Ok(Saturday),
+ "Sunday" => Ok(Sunday),
+ _ => Err(error::InvalidVariant),
+ }
+ }
+}