diff options
Diffstat (limited to 'vendor/time/src/formatting/formattable.rs')
-rw-r--r-- | vendor/time/src/formatting/formattable.rs | 304 |
1 files changed, 304 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 |