summaryrefslogtreecommitdiffstats
path: root/vendor/time/src/formatting
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/time/src/formatting')
-rw-r--r--vendor/time/src/formatting/formattable.rs304
-rw-r--r--vendor/time/src/formatting/iso8601.rs139
-rw-r--r--vendor/time/src/formatting/mod.rs506
3 files changed, 949 insertions, 0 deletions
diff --git a/vendor/time/src/formatting/formattable.rs b/vendor/time/src/formatting/formattable.rs
new file mode 100644
index 000000000..7fee2fbea
--- /dev/null
+++ b/vendor/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/vendor/time/src/formatting/iso8601.rs b/vendor/time/src/formatting/iso8601.rs
new file mode 100644
index 000000000..1724f96f5
--- /dev/null
+++ b/vendor/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/vendor/time/src/formatting/mod.rs b/vendor/time/src/formatting/mod.rs
new file mode 100644
index 000000000..e5409cc58
--- /dev/null
+++ b/vendor/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