diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/time/src/formatting | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/time/src/formatting')
-rw-r--r-- | third_party/rust/time/src/formatting/formattable.rs | 307 | ||||
-rw-r--r-- | third_party/rust/time/src/formatting/iso8601.rs | 140 | ||||
-rw-r--r-- | third_party/rust/time/src/formatting/mod.rs | 545 |
3 files changed, 992 insertions, 0 deletions
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..2b4b350883 --- /dev/null +++ b/third_party/rust/time/src/formatting/formattable.rs @@ -0,0 +1,307 @@ +//! 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 describes a format. +/// +/// Implementors of [`Formattable`] are [format descriptions](crate::format_description). +/// +/// [`Date::format`] and [`Time::format`] each use a format description to generate +/// a String from their data. See the respective methods for usage examples. +#[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..29d443ef46 --- /dev/null +++ b/third_party/rust/time/src/formatting/iso8601.rs @@ -0,0 +1,140 @@ +//! Helpers for implementing formatting for ISO 8601. + +use std::io; + +use crate::convert::*; +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<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, + 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<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, + 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) / Minute.per(Hour) as f64 + + (seconds as f64) / Second.per(Hour) as f64 + + (nanoseconds as f64) / Nanosecond.per(Hour) as f64; + 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) / Second.per(Minute) as f64 + + (nanoseconds as f64) / Nanosecond.per(Minute) as f64; + 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) / Nanosecond.per(Second) as f64; + bytes += format_float(output, seconds, 2, decimal_digits)?; + } + } + + Ok(bytes) +} + +/// Format the UTC offset portion of ISO 8601. +pub(super) fn format_offset<const CONFIG: EncodedConfig>( + output: &mut impl io::Write, + offset: UtcOffset, +) -> Result<usize, error::Format> { + if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() { + 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..e5017063ab --- /dev/null +++ b/third_party/rust/time/src/formatting/mod.rs @@ -0,0 +1,545 @@ +//! Formatting for various types. + +pub(crate) mod formattable; +mod iso8601; + +use core::num::NonZeroU8; +use std::io; + +pub use self::formattable::Formattable; +use crate::convert::*; +use crate::format_description::{modifier, Component}; +use crate::{error, Date, OffsetDateTime, 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$}")?; + Ok(width) + } + None => { + let value = value.trunc() as u64; + let width = digits_before_decimal as usize; + write!(output, "{value:0>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>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, + padding: modifier::Padding, +) -> Result<usize, io::Error> { + match padding { + modifier::Padding::Space => format_number_pad_space::<WIDTH>(output, value), + modifier::Padding::Zero => format_number_pad_zero::<WIDTH>(output, value), + modifier::Padding::None => format_number_pad_none(output, value), + } +} + +/// Format a number with the provided width and spaces as padding. +/// +/// The sign must be written by the caller. +pub(crate) fn format_number_pad_space<const WIDTH: u8>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, +) -> Result<usize, io::Error> { + let mut bytes = 0; + for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { + 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>( + output: &mut impl io::Write, + value: impl itoa::Integer + DigitCount + Copy, +) -> Result<usize, io::Error> { + let mut bytes = 0; + for _ in 0..(WIDTH.saturating_sub(value.num_digits())) { + bytes += write(output, b"0")?; + } + bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?; + Ok(bytes) +} + +/// Format a number with no padding. +/// +/// If the sign is mandatory, the sign must be written by the caller. +pub(crate) fn format_number_pad_none( + output: &mut impl io::Write, + value: impl itoa::Integer + Copy, +) -> Result<usize, io::Error> { + write(output, itoa::Buffer::new().format(value).as_bytes()) +} + +/// Format the provided component into the designated output. An `Err` will be returned if the +/// component requires information that it does not provide or if the value cannot be output to the +/// stream. +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)?, + (Ignore(_), ..) => 0, + (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => { + fmt_unix_timestamp(output, date, time, 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 + +/// Format the Unix timestamp into the designated output. +fn fmt_unix_timestamp( + output: &mut impl io::Write, + date: Date, + time: Time, + offset: UtcOffset, + modifier::UnixTimestamp { + precision, + sign_is_mandatory, + }: modifier::UnixTimestamp, +) -> Result<usize, io::Error> { + let date_time = date + .with_time(time) + .assume_offset(offset) + .to_offset(UtcOffset::UTC); + + if date_time < OffsetDateTime::UNIX_EPOCH { + write(output, b"-")?; + } else if sign_is_mandatory { + write(output, b"+")?; + } + + match precision { + modifier::UnixTimestampPrecision::Second => { + format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs()) + } + modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none( + output, + (date_time.unix_timestamp_nanos() / Nanosecond.per(Millisecond) as i128).unsigned_abs(), + ), + modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none( + output, + (date_time.unix_timestamp_nanos() / Nanosecond.per(Microsecond) as i128).unsigned_abs(), + ), + modifier::UnixTimestampPrecision::Nanosecond => { + format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs()) + } + } +} |