//! 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 { 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$}", 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( output: &mut W, value: V, 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 => 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 { 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 { 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, time: Option