//! 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; } /// A macro to generate implementations of `DigitCount` for unsigned integers. macro_rules! impl_digit_count { ($($t:ty),* $(,)?) => { $(impl DigitCount for $t { fn num_digits(self) -> u8 { match self.checked_ilog10() { Some(n) => (n as u8) + 1, None => 1, } } })* }; } impl_digit_count!(u8, u16, u32); // 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 { 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 { 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 { 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, ) -> io::Result { 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( output: &mut impl io::Write, value: impl itoa::Integer + DigitCount + Copy, padding: modifier::Padding, ) -> Result { match padding { modifier::Padding::Space => format_number_pad_space::(output, value), modifier::Padding::Zero => format_number_pad_zero::(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( output: &mut impl io::Write, value: impl itoa::Integer + DigitCount + Copy, ) -> Result { 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( output: &mut impl io::Write, value: impl itoa::Integer + DigitCount + Copy, ) -> Result { 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 { 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, time: Option