pub(crate) mod formattable;
use std::io;
#[allow(unused_imports)]
use standback::prelude::*;
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",
];
pub(crate) trait DigitCount {
fn num_digits(self) -> u8;
}
impl DigitCount for u8 {
fn num_digits(self) -> u8 {
if self < 10 {
1
} else if self < 100 {
2
} else {
3
}
}
}
impl DigitCount for u16 {
fn num_digits(self) -> u8 {
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 {
if self < 10 {
1
} else if self < 100 {
2
} else if self < 1_000 {
3
} else if self < 10_000 {
4
} else if self < 100_000 {
5
} else if self < 1_000_000 {
6
} else if self < 10_000_000 {
7
} else if self < 100_000_000 {
8
} else if self < 1_000_000_000 {
9
} else {
10
}
}
}
pub(crate) fn format_number(
output: &mut impl io::Write,
value: impl itoa::Integer + DigitCount + Copy,
padding: modifier::Padding,
width: u8,
) -> Result<usize, io::Error> {
match padding {
modifier::Padding::Space => format_number_pad_space(output, value, width),
modifier::Padding::Zero => format_number_pad_zero(output, value, width),
modifier::Padding::None => itoa::write(output, value),
}
}
pub(crate) fn format_number_pad_space(
output: &mut impl io::Write,
value: impl itoa::Integer + DigitCount + Copy,
width: u8,
) -> Result<usize, io::Error> {
let mut bytes = 0;
for _ in 0..(width.saturating_sub(value.num_digits())) {
bytes += output.write(&[b' '])?;
}
bytes += itoa::write(output, value)?;
Ok(bytes)
}
pub(crate) fn format_number_pad_zero(
output: &mut impl io::Write,
value: impl itoa::Integer + DigitCount + Copy,
width: u8,
) -> Result<usize, io::Error> {
let mut bytes = 0;
for _ in 0..(width.saturating_sub(value.num_digits())) {
bytes += output.write(&[b'0'])?;
}
bytes += itoa::write(output, value)?;
Ok(bytes)
}
pub(crate) fn format_component(
output: &mut impl io::Write,
component: Component,
date: Option<Date>,
time: Option<Time>,
offset: Option<UtcOffset>,
) -> Result<usize, error::Format> {
use Component::*;
Ok(match (component, date, time, offset) {
(Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
(Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
(Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
(Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
(WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
(Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
(Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
(Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
(Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
(Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
(Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
(OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
(OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
(OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
_ => return Err(error::Format::InsufficientTypeInformation),
})
}
fn fmt_day(
output: &mut impl io::Write,
date: Date,
modifier::Day { padding }: modifier::Day,
) -> Result<usize, io::Error> {
format_number(output, date.day(), padding, 2)
}
fn fmt_month(
output: &mut impl io::Write,
date: Date,
modifier::Month { padding, repr }: modifier::Month,
) -> Result<usize, io::Error> {
match repr {
modifier::MonthRepr::Numerical => format_number(output, date.month() as u8, padding, 2),
modifier::MonthRepr::Long => output.write(MONTH_NAMES[date.month() as usize - 1]),
modifier::MonthRepr::Short => output.write(&MONTH_NAMES[date.month() as usize - 1][..3]),
}
}
fn fmt_ordinal(
output: &mut impl io::Write,
date: Date,
modifier::Ordinal { padding }: modifier::Ordinal,
) -> Result<usize, io::Error> {
format_number(output, date.ordinal(), padding, 3)
}
fn fmt_weekday(
output: &mut impl io::Write,
date: Date,
modifier::Weekday { repr, one_indexed }: modifier::Weekday,
) -> Result<usize, io::Error> {
match repr {
modifier::WeekdayRepr::Short => {
output.write(&WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3])
}
modifier::WeekdayRepr::Long => {
output.write(WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize])
}
modifier::WeekdayRepr::Sunday => format_number(
output,
date.weekday().number_days_from_sunday() + one_indexed as u8,
modifier::Padding::None,
1,
),
modifier::WeekdayRepr::Monday => format_number(
output,
date.weekday().number_days_from_monday() + one_indexed as u8,
modifier::Padding::None,
1,
),
}
}
fn fmt_week_number(
output: &mut impl io::Write,
date: Date,
modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
) -> Result<usize, io::Error> {
format_number(
output,
match repr {
modifier::WeekNumberRepr::Iso => date.iso_week(),
modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
modifier::WeekNumberRepr::Monday => date.monday_based_week(),
},
padding,
2,
)
}
fn fmt_year(
output: &mut impl io::Write,
date: Date,
modifier::Year {
padding,
repr,
iso_week_based,
sign_is_mandatory,
}: modifier::Year,
) -> Result<usize, io::Error> {
let full_year = if iso_week_based {
date.iso_year_week().0
} else {
date.year()
};
let value = match repr {
modifier::YearRepr::Full => full_year,
modifier::YearRepr::LastTwo => (full_year % 100).abs(),
};
let width = match repr {
#[cfg(feature = "large-dates")]
modifier::YearRepr::Full if value.abs() >= 100_000 => 6,
#[cfg(feature = "large-dates")]
modifier::YearRepr::Full if value.abs() >= 10_000 => 5,
modifier::YearRepr::Full => 4,
modifier::YearRepr::LastTwo => 2,
};
let mut bytes = 0;
if repr != modifier::YearRepr::LastTwo {
if full_year < 0 {
bytes += output.write(&[b'-'])?;
} else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
bytes += output.write(&[b'+'])?;
}
}
bytes += format_number(output, value.unsigned_abs(), padding, width)?;
Ok(bytes)
}
fn fmt_hour(
output: &mut impl io::Write,
time: Time,
modifier::Hour {
padding,
is_12_hour_clock,
}: modifier::Hour,
) -> Result<usize, io::Error> {
let value = match (time.hour(), is_12_hour_clock) {
(hour, false) => hour,
(0, true) | (12, true) => 12,
(hour, true) if hour < 12 => hour,
(hour, true) => hour - 12,
};
format_number(output, value, padding, 2)
}
fn fmt_minute(
output: &mut impl io::Write,
time: Time,
modifier::Minute { padding }: modifier::Minute,
) -> Result<usize, io::Error> {
format_number(output, time.minute(), padding, 2)
}
fn fmt_period(
output: &mut impl io::Write,
time: Time,
modifier::Period { is_uppercase }: modifier::Period,
) -> Result<usize, io::Error> {
match (time.hour() >= 12, is_uppercase) {
(false, false) => output.write(b"am"),
(false, true) => output.write(b"AM"),
(true, false) => output.write(b"pm"),
(true, true) => output.write(b"PM"),
}
}
fn fmt_second(
output: &mut impl io::Write,
time: Time,
modifier::Second { padding }: modifier::Second,
) -> Result<usize, io::Error> {
format_number(output, time.second(), padding, 2)
}
fn fmt_subsecond(
output: &mut impl io::Write,
time: Time,
modifier::Subsecond { digits }: modifier::Subsecond,
) -> Result<usize, io::Error> {
let (value, width) = match digits {
modifier::SubsecondDigits::One => (time.nanosecond() / 100_000_000, 1),
modifier::SubsecondDigits::Two => (time.nanosecond() / 10_000_000, 2),
modifier::SubsecondDigits::Three => (time.nanosecond() / 1_000_000, 3),
modifier::SubsecondDigits::Four => (time.nanosecond() / 100_000, 4),
modifier::SubsecondDigits::Five => (time.nanosecond() / 10_000, 5),
modifier::SubsecondDigits::Six => (time.nanosecond() / 1_000, 6),
modifier::SubsecondDigits::Seven => (time.nanosecond() / 100, 7),
modifier::SubsecondDigits::Eight => (time.nanosecond() / 10, 8),
modifier::SubsecondDigits::Nine => (time.nanosecond(), 9),
modifier::SubsecondDigits::OneOrMore => match time.nanosecond() {
nanos if nanos % 10 != 0 => (nanos, 9),
nanos if (nanos / 10) % 10 != 0 => (nanos / 10, 8),
nanos if (nanos / 100) % 10 != 0 => (nanos / 100, 7),
nanos if (nanos / 1_000) % 10 != 0 => (nanos / 1_000, 6),
nanos if (nanos / 10_000) % 10 != 0 => (nanos / 10_000, 5),
nanos if (nanos / 100_000) % 10 != 0 => (nanos / 100_000, 4),
nanos if (nanos / 1_000_000) % 10 != 0 => (nanos / 1_000_000, 3),
nanos if (nanos / 10_000_000) % 10 != 0 => (nanos / 10_000_000, 2),
nanos => (nanos / 100_000_000, 1),
},
};
format_number_pad_zero(output, value, width)
}
fn fmt_offset_hour(
output: &mut impl io::Write,
offset: UtcOffset,
modifier::OffsetHour {
padding,
sign_is_mandatory,
}: modifier::OffsetHour,
) -> Result<usize, io::Error> {
let mut bytes = 0;
if offset.is_negative() {
bytes += output.write(&[b'-'])?;
} else if sign_is_mandatory {
bytes += output.write(&[b'+'])?;
}
bytes += format_number(output, offset.whole_hours().unsigned_abs(), padding, 2)?;
Ok(bytes)
}
fn fmt_offset_minute(
output: &mut impl io::Write,
offset: UtcOffset,
modifier::OffsetMinute { padding }: modifier::OffsetMinute,
) -> Result<usize, io::Error> {
format_number(
output,
offset.minutes_past_hour().unsigned_abs(),
padding,
2,
)
}
fn fmt_offset_second(
output: &mut impl io::Write,
offset: UtcOffset,
modifier::OffsetSecond { padding }: modifier::OffsetSecond,
) -> Result<usize, io::Error> {
format_number(
output,
offset.seconds_past_minute().unsigned_abs(),
padding,
2,
)
}