//! Formatting for various types. mod component_provider; pub(crate) mod formattable; mod iso8601; use core::num::NonZero; use std::io; use num_conv::prelude::*; use self::component_provider::ComponentProvider; pub use self::formattable::Formattable; use crate::ext::DigitCount; use crate::format_description::{Component, Period, modifier}; use crate::internal_macros::try_likely_ok; use crate::{Month, Weekday, error}; 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", ]; const WEEKDAY_NAMES: [&[u8]; 7] = [ b"Monday", b"Tuesday", b"Wednesday", b"Thursday", b"Friday", b"Saturday", b"Sunday", ]; /// Write all bytes to the output, returning the number of bytes written. #[inline] pub(crate) fn write(output: &mut (impl io::Write + ?Sized), 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. #[inline] pub(crate) fn write_if( output: &mut (impl io::Write + ?Sized), 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`. #[inline] pub(crate) fn write_if_else( output: &mut (impl io::Write + ?Sized), pred: bool, true_bytes: &[u8], false_bytes: &[u8], ) -> io::Result { write(output, if pred { true_bytes } else { false_bytes }) } /// Helper function to obtain 10^x, guaranteeing determinism for x ≤ 9. For these cases, the /// function optimizes to a lookup table. For x ≥ 10, it falls back to `10_f64.powi(x)`. The only /// situation where this would occur is if the user explicitly requests such precision when /// configuring the ISO 8601 well known format. All other possibilities max out at nine digits. #[inline] fn f64_10_pow_x(x: NonZero) -> f64 { match x.get() { 1 => 10., 2 => 100., 3 => 1_000., 4 => 10_000., 5 => 100_000., 6 => 1_000_000., 7 => 10_000_000., 8 => 100_000_000., 9 => 1_000_000_000., x => 10_f64.powi(x.cast_signed().extend()), } } /// 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. #[inline] pub(crate) fn format_float( output: &mut (impl io::Write + ?Sized), mut value: f64, digits_before_decimal: u8, digits_after_decimal: Option>, ) -> io::Result { match digits_after_decimal { Some(digits_after_decimal) => { // If the precision is less than nine digits after the decimal point, truncate the // value. This avoids rounding up and causing the value to exceed the maximum permitted // value (as in #678). If the precision is at least nine, then we don't truncate so as // to avoid having an off-by-one error (as in #724). The latter is necessary // because floating point values are inherently imprecise with decimal // values, so a minuscule error can be amplified easily. // // Note that this is largely an issue for second values, as for minute and hour decimals // the value is divided by 60 or 3,600, neither of which divide evenly into 10^x. // // While not a perfect approach, this addresses the bugs that have been reported so far // without being overly complex. if digits_after_decimal.get() < 9 { let trunc_num = f64_10_pow_x(digits_after_decimal); value = f64::trunc(value * trunc_num) / trunc_num; } let digits_after_decimal = digits_after_decimal.get().extend(); let width = digits_before_decimal.extend::() + 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.extend(); write!(output, "{value:0>width$}")?; Ok(width) } } } /// Format a number with the provided padding and width. /// /// The sign must be written by the caller. #[inline] pub(crate) fn format_number( output: &mut (impl io::Write + ?Sized), 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. #[inline] pub(crate) fn format_number_pad_space( output: &mut (impl io::Write + ?Sized), 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. #[inline] pub(crate) fn format_number_pad_zero( output: &mut (impl io::Write + ?Sized), 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. #[inline] pub(crate) fn format_number_pad_none( output: &mut (impl io::Write + ?Sized), 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. #[inline] pub(crate) fn format_component( output: &mut (impl io::Write + ?Sized), component: Component, value: &V, state: &mut V::State, ) -> Result where V: ComponentProvider, { use Component::*; Ok(match component { Day(modifier) if V::SUPPLIES_DATE => { try_likely_ok!(fmt_day(output, value.day(state), modifier)) } Month(modifier) if V::SUPPLIES_DATE => { try_likely_ok!(fmt_month(output, value.month(state), modifier)) } Ordinal(modifier) if V::SUPPLIES_DATE => { try_likely_ok!(fmt_ordinal(output, value.ordinal(state), modifier)) } Weekday(modifier) if V::SUPPLIES_DATE => { try_likely_ok!(fmt_weekday(output, value.weekday(state), modifier)) } WeekNumber(modifier) if V::SUPPLIES_DATE => try_likely_ok!(fmt_week_number( output, match modifier.repr { modifier::WeekNumberRepr::Iso => value.iso_week_number(state), modifier::WeekNumberRepr::Sunday => value.sunday_based_week(state), modifier::WeekNumberRepr::Monday => value.monday_based_week(state), }, modifier, )), Year(modifier) if V::SUPPLIES_DATE => try_likely_ok!(fmt_year( output, if modifier.iso_week_based { value.iso_year(state) } else { value.calendar_year(state) }, modifier, )), Hour(modifier) if V::SUPPLIES_TIME => { try_likely_ok!(fmt_hour(output, value.hour(state), modifier)) } Minute(modifier) if V::SUPPLIES_TIME => { try_likely_ok!(fmt_minute(output, value.minute(state), modifier)) } Period(modifier) if V::SUPPLIES_TIME => { try_likely_ok!(fmt_period(output, value.period(state), modifier)) } Second(modifier) if V::SUPPLIES_TIME => { try_likely_ok!(fmt_second(output, value.second(state), modifier)) } Subsecond(modifier) if V::SUPPLIES_TIME => { try_likely_ok!(fmt_subsecond(output, value.nanosecond(state), modifier)) } OffsetHour(modifier) if V::SUPPLIES_OFFSET => try_likely_ok!(fmt_offset_hour( output, value.offset_is_negative(state), value.offset_hour(state), modifier, )), OffsetMinute(modifier) if V::SUPPLIES_OFFSET => try_likely_ok!(fmt_offset_minute( output, value.offset_minute(state), modifier )), OffsetSecond(modifier) if V::SUPPLIES_OFFSET => try_likely_ok!(fmt_offset_second( output, value.offset_second(state), modifier )), Ignore(_) => 0, UnixTimestamp(modifier) if V::SUPPLIES_TIMESTAMP => match modifier.precision { modifier::UnixTimestampPrecision::Second => try_likely_ok!(fmt_unix_timestamp_seconds( output, value.unix_timestamp_seconds(state), modifier, )), modifier::UnixTimestampPrecision::Millisecond => { try_likely_ok!(fmt_unix_timestamp_milliseconds( output, value.unix_timestamp_milliseconds(state), modifier, )) } modifier::UnixTimestampPrecision::Microsecond => { try_likely_ok!(fmt_unix_timestamp_microseconds( output, value.unix_timestamp_microseconds(state), modifier, )) } modifier::UnixTimestampPrecision::Nanosecond => { try_likely_ok!(fmt_unix_timestamp_nanoseconds( output, value.unix_timestamp_nanoseconds(state), modifier, )) } }, End(modifier::End { trailing_input: _ }) => 0, // This is functionally the same as a wildcard arm, but it will cause an error if a new // component is added. This is to avoid a bug where a new component, the code compiles, and // formatting fails. // Allow unreachable patterns because some branches may be fully matched above. #[allow(unreachable_patterns)] Day(_) | Month(_) | Ordinal(_) | Weekday(_) | WeekNumber(_) | Year(_) | Hour(_) | Minute(_) | Period(_) | Second(_) | Subsecond(_) | OffsetHour(_) | OffsetMinute(_) | OffsetSecond(_) | Ignore(_) | UnixTimestamp(_) | End(_) => { return Err(error::Format::InsufficientTypeInformation); } }) } /// Format the day into the designated output. #[inline] fn fmt_day( output: &mut (impl io::Write + ?Sized), day: u8, modifier::Day { padding }: modifier::Day, ) -> Result { format_number::<2>(output, day, padding) } /// Format the month into the designated output. #[inline] fn fmt_month( output: &mut (impl io::Write + ?Sized), month: Month, modifier::Month { padding, repr, case_sensitive: _, // no effect on formatting }: modifier::Month, ) -> Result { match repr { modifier::MonthRepr::Numerical => format_number::<2>(output, u8::from(month), padding), modifier::MonthRepr::Long => { write(output, MONTH_NAMES[u8::from(month).extend::() - 1]) } // Safety: All month names are at least three bytes long. modifier::MonthRepr::Short => write(output, unsafe { MONTH_NAMES[u8::from(month).extend::() - 1].get_unchecked(..3) }), } } /// Format the ordinal into the designated output. #[inline] fn fmt_ordinal( output: &mut (impl io::Write + ?Sized), ordinal: u16, modifier::Ordinal { padding }: modifier::Ordinal, ) -> Result { format_number::<3>(output, ordinal, padding) } /// Format the weekday into the designated output. #[inline] fn fmt_weekday( output: &mut (impl io::Write + ?Sized), weekday: Weekday, modifier::Weekday { repr, one_indexed, case_sensitive: _, // no effect on formatting }: modifier::Weekday, ) -> Result { match repr { // Safety: All weekday names are at least three bytes long. modifier::WeekdayRepr::Short => write(output, unsafe { WEEKDAY_NAMES[weekday.number_days_from_monday().extend::()].get_unchecked(..3) }), modifier::WeekdayRepr::Long => write( output, WEEKDAY_NAMES[weekday.number_days_from_monday().extend::()], ), modifier::WeekdayRepr::Sunday => format_number::<1>( output, weekday.number_days_from_sunday() + u8::from(one_indexed), modifier::Padding::None, ), modifier::WeekdayRepr::Monday => format_number::<1>( output, weekday.number_days_from_monday() + u8::from(one_indexed), modifier::Padding::None, ), } } /// Format the week number into the designated output. #[inline] fn fmt_week_number( output: &mut (impl io::Write + ?Sized), week_number: u8, modifier::WeekNumber { padding, repr: _ }: modifier::WeekNumber, ) -> Result { format_number::<2>(output, week_number, padding) } /// Format the year into the designated output. fn fmt_year( output: &mut (impl io::Write + ?Sized), full_year: i32, modifier::Year { padding, repr, range, iso_week_based: _, sign_is_mandatory, }: modifier::Year, ) -> Result { let value = match repr { modifier::YearRepr::Full => full_year, modifier::YearRepr::Century => full_year / 100, modifier::YearRepr::LastTwo => (full_year % 100).abs(), }; let format_number = if cfg!(feature = "large-dates") && range == modifier::YearRange::Extended { match repr { modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6>, modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5>, modifier::YearRepr::Full => format_number::<4>, modifier::YearRepr::Century if value.abs() >= 1_000 => format_number::<4>, modifier::YearRepr::Century if value.abs() >= 100 => format_number::<3>, modifier::YearRepr::Century => format_number::<2>, modifier::YearRepr::LastTwo => format_number::<2>, } } else { match repr { modifier::YearRepr::Full | modifier::YearRepr::Century if full_year.abs() >= 10_000 => { return Err(error::ComponentRange::conditional("year").into()); } _ => {} } match repr { modifier::YearRepr::Full => format_number::<4>, modifier::YearRepr::Century => format_number::<2>, 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) } /// Format the hour into the designated output. #[inline] fn fmt_hour( output: &mut (impl io::Write + ?Sized), hour: u8, modifier::Hour { padding, is_12_hour_clock, }: modifier::Hour, ) -> Result { let value = match (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. #[inline] fn fmt_minute( output: &mut (impl io::Write + ?Sized), minute: u8, modifier::Minute { padding }: modifier::Minute, ) -> Result { format_number::<2>(output, minute, padding) } /// Format the period into the designated output. #[inline] fn fmt_period( output: &mut (impl io::Write + ?Sized), period: Period, modifier::Period { is_uppercase, case_sensitive: _, // no effect on formatting }: modifier::Period, ) -> Result { write( output, match (period, is_uppercase) { (Period::Am, false) => b"am", (Period::Am, true) => b"AM", (Period::Pm, false) => b"pm", (Period::Pm, true) => b"PM", }, ) } /// Format the second into the designated output. #[inline] fn fmt_second( output: &mut (impl io::Write + ?Sized), second: u8, modifier::Second { padding }: modifier::Second, ) -> Result { format_number::<2>(output, second, padding) } /// Format the subsecond into the designated output. #[inline] fn fmt_subsecond( output: &mut (impl io::Write + ?Sized), nanos: u32, modifier::Subsecond { digits }: modifier::Subsecond, ) -> Result { use modifier::SubsecondDigits::*; if digits == Nine || (digits == OneOrMore && !nanos.is_multiple_of(10)) { format_number_pad_zero::<9>(output, nanos) } else if digits == Eight || (digits == OneOrMore && !(nanos / 10).is_multiple_of(10)) { format_number_pad_zero::<8>(output, nanos / 10) } else if digits == Seven || (digits == OneOrMore && !(nanos / 100).is_multiple_of(10)) { format_number_pad_zero::<7>(output, nanos / 100) } else if digits == Six || (digits == OneOrMore && !(nanos / 1_000).is_multiple_of(10)) { format_number_pad_zero::<6>(output, nanos / 1_000) } else if digits == Five || (digits == OneOrMore && !(nanos / 10_000).is_multiple_of(10)) { format_number_pad_zero::<5>(output, nanos / 10_000) } else if digits == Four || (digits == OneOrMore && !(nanos / 100_000).is_multiple_of(10)) { format_number_pad_zero::<4>(output, nanos / 100_000) } else if digits == Three || (digits == OneOrMore && !(nanos / 1_000_000).is_multiple_of(10)) { format_number_pad_zero::<3>(output, nanos / 1_000_000) } else if digits == Two || (digits == OneOrMore && !(nanos / 10_000_000).is_multiple_of(10)) { format_number_pad_zero::<2>(output, nanos / 10_000_000) } else { format_number_pad_zero::<1>(output, nanos / 100_000_000) } } #[inline] fn fmt_offset_sign( output: &mut (impl io::Write + ?Sized), is_negative: bool, sign_is_mandatory: bool, ) -> Result { if is_negative { write(output, b"-") } else if sign_is_mandatory { write(output, b"+") } else { Ok(0) } } /// Format the offset hour into the designated output. #[inline] fn fmt_offset_hour( output: &mut (impl io::Write + ?Sized), is_negative: bool, hour: i8, modifier::OffsetHour { padding, sign_is_mandatory, }: modifier::OffsetHour, ) -> Result { let mut bytes = 0; bytes += fmt_offset_sign(output, is_negative, sign_is_mandatory)?; bytes += format_number::<2>(output, hour.unsigned_abs(), padding)?; Ok(bytes) } /// Format the offset minute into the designated output. #[inline] fn fmt_offset_minute( output: &mut (impl io::Write + ?Sized), offset_minute: i8, modifier::OffsetMinute { padding }: modifier::OffsetMinute, ) -> Result { format_number::<2>(output, offset_minute.unsigned_abs(), padding) } /// Format the offset second into the designated output. #[inline] fn fmt_offset_second( output: &mut (impl io::Write + ?Sized), offset_second: i8, modifier::OffsetSecond { padding }: modifier::OffsetSecond, ) -> Result { format_number::<2>(output, offset_second.unsigned_abs(), padding) } /// Format the Unix timestamp (in seconds) into the designated output. #[inline] fn fmt_unix_timestamp_seconds( output: &mut (impl io::Write + ?Sized), timestamp: i64, modifier::UnixTimestamp { precision, sign_is_mandatory, }: modifier::UnixTimestamp, ) -> Result { debug_assert_eq!(precision, modifier::UnixTimestampPrecision::Second); let mut bytes = 0; bytes += fmt_offset_sign(output, timestamp < 0, sign_is_mandatory)?; bytes += format_number_pad_none(output, timestamp.unsigned_abs())?; Ok(bytes) } /// Format the Unix timestamp (in milliseconds) into the designated output. #[inline] fn fmt_unix_timestamp_milliseconds( output: &mut (impl io::Write + ?Sized), timestamp_millis: i64, modifier::UnixTimestamp { precision, sign_is_mandatory, }: modifier::UnixTimestamp, ) -> Result { debug_assert_eq!(precision, modifier::UnixTimestampPrecision::Millisecond); let mut bytes = 0; bytes += fmt_offset_sign(output, timestamp_millis < 0, sign_is_mandatory)?; bytes += format_number_pad_none(output, timestamp_millis.unsigned_abs())?; Ok(bytes) } /// Format the Unix timestamp into the designated output. #[inline] fn fmt_unix_timestamp_microseconds( output: &mut (impl io::Write + ?Sized), timestamp_micros: i128, modifier::UnixTimestamp { precision, sign_is_mandatory, }: modifier::UnixTimestamp, ) -> Result { debug_assert_eq!(precision, modifier::UnixTimestampPrecision::Microsecond); let mut bytes = 0; bytes += fmt_offset_sign(output, timestamp_micros < 0, sign_is_mandatory)?; bytes += format_number_pad_none(output, timestamp_micros.unsigned_abs())?; Ok(bytes) } /// Format the Unix timestamp into the designated output. #[inline] fn fmt_unix_timestamp_nanoseconds( output: &mut (impl io::Write + ?Sized), timestamp_nanos: i128, modifier::UnixTimestamp { precision, sign_is_mandatory, }: modifier::UnixTimestamp, ) -> Result { debug_assert_eq!(precision, modifier::UnixTimestampPrecision::Nanosecond); let mut bytes = 0; bytes += fmt_offset_sign(output, timestamp_nanos < 0, sign_is_mandatory)?; bytes += format_number_pad_none(output, timestamp_nanos.unsigned_abs())?; Ok(bytes) }