| //! 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 |