use crate::duration::options::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidatedDurationFormatterOptions {
pub(crate) base: BaseStyle,
pub(crate) year: FieldStyle,
pub(crate) year_visibility: FieldDisplay,
pub(crate) month: FieldStyle,
pub(crate) month_visibility: FieldDisplay,
pub(crate) week: FieldStyle,
pub(crate) week_visibility: FieldDisplay,
pub(crate) day: FieldStyle,
pub(crate) day_visibility: FieldDisplay,
pub(crate) hour: FieldStyle,
pub(crate) hour_visibility: FieldDisplay,
pub(crate) minute: FieldStyle,
pub(crate) minute_visibility: FieldDisplay,
pub(crate) second: FieldStyle,
pub(crate) second_visibility: FieldDisplay,
pub(crate) millisecond: FieldStyle,
pub(crate) millisecond_visibility: FieldDisplay,
pub(crate) microsecond: FieldStyle,
pub(crate) microsecond_visibility: FieldDisplay,
pub(crate) nanosecond: FieldStyle,
pub(crate) nanosecond_visibility: FieldDisplay,
pub(crate) fractional_digits: FractionalDigits,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, displaydoc::Display)]
pub enum DurationFormatterOptionsError {
#[displaydoc("A unit field is set to Always and the style is set to Fractional")]
DisplayAlwaysFractional,
#[displaydoc(
"A previous unit's style is Fractional, but the following unit's style is not Fractional"
)]
PreviousFractional,
#[displaydoc("A previous unit's style is set to Numeric or TwoDigit and the following unit's style is not Fractional, Numeric, or TwoDigit")]
PreviousNumeric,
#[displaydoc("The number of fractional digits is out of acceptable range")]
FractionalDigitsOutOfRange,
}
impl ValidatedDurationFormatterOptions {
pub fn validate(
value: DurationFormatterOptions,
) -> Result<Self, DurationFormatterOptionsError> {
let mut builder: ValidatedDurationFormatterOptionsBuilder = value.into();
let units = builder.iter_units();
let mut prev_style = None;
for (unit, style, visibility) in units.into_iter() {
let mut default_visibility = FieldDisplay::Always;
if style.is_none() {
if value.base == BaseStyle::Digital {
if unit != Unit::Hour || unit != Unit::Minute || unit != Unit::Second {
default_visibility = FieldDisplay::Auto;
}
*style = Some(unit.digital_default());
}
else {
if matches!(
prev_style,
Some(FieldStyle::Fractional | FieldStyle::Numeric | FieldStyle::TwoDigit)
) {
if unit != Unit::Minute || unit != Unit::Second {
default_visibility = FieldDisplay::Auto;
}
*style = Some(FieldStyle::Numeric);
}
else {
default_visibility = FieldDisplay::Auto;
*style = Some(value.base.into());
}
}
}
if *style == Some(FieldStyle::Numeric) {
if unit == Unit::Millisecond
|| unit == Unit::Microsecond
|| unit == Unit::Nanosecond
{
*style = Some(FieldStyle::Fractional);
default_visibility = FieldDisplay::Auto;
}
}
if visibility.is_none() {
*visibility = Some(default_visibility);
}
if *visibility == Some(FieldDisplay::Always) && *style == Some(FieldStyle::Fractional) {
return Err(DurationFormatterOptionsError::DisplayAlwaysFractional);
}
if prev_style == Some(FieldStyle::Fractional) {
if *style != Some(FieldStyle::Fractional) {
return Err(DurationFormatterOptionsError::PreviousFractional);
}
}
if prev_style == Some(FieldStyle::Numeric) || prev_style == Some(FieldStyle::TwoDigit) {
if !matches!(
*style,
Some(FieldStyle::Fractional | FieldStyle::Numeric | FieldStyle::TwoDigit)
) {
return Err(DurationFormatterOptionsError::PreviousNumeric);
}
if unit == Unit::Minute || unit == Unit::Second {
*style = Some(FieldStyle::TwoDigit);
}
}
prev_style = *style;
}
if let FractionalDigits::Fixed(i) = builder.fractional_digits {
if i > 9 {
return Err(DurationFormatterOptionsError::FractionalDigitsOutOfRange);
}
}
Ok(builder.try_into().unwrap())
}
#[allow(dead_code)]
pub(crate) fn iter_mut_units(&mut self) -> [(Unit, &mut FieldStyle, &mut FieldDisplay); 10] {
[
(Unit::Year, &mut self.year, &mut self.year_visibility),
(Unit::Month, &mut self.month, &mut self.month_visibility),
(Unit::Week, &mut self.week, &mut self.week_visibility),
(Unit::Day, &mut self.day, &mut self.day_visibility),
(Unit::Hour, &mut self.hour, &mut self.hour_visibility),
(Unit::Minute, &mut self.minute, &mut self.minute_visibility),
(Unit::Second, &mut self.second, &mut self.second_visibility),
(
Unit::Millisecond,
&mut self.millisecond,
&mut self.millisecond_visibility,
),
(
Unit::Microsecond,
&mut self.microsecond,
&mut self.microsecond_visibility,
),
(
Unit::Nanosecond,
&mut self.nanosecond,
&mut self.nanosecond_visibility,
),
]
}
pub(crate) fn iter_units(&self) -> [(Unit, FieldStyle, FieldDisplay); 10] {
[
(Unit::Year, self.year, self.year_visibility),
(Unit::Month, self.month, self.month_visibility),
(Unit::Week, self.week, self.week_visibility),
(Unit::Day, self.day, self.day_visibility),
(Unit::Hour, self.hour, self.hour_visibility),
(Unit::Minute, self.minute, self.minute_visibility),
(Unit::Second, self.second, self.second_visibility),
(
Unit::Millisecond,
self.millisecond,
self.millisecond_visibility,
),
(
Unit::Microsecond,
self.microsecond,
self.microsecond_visibility,
),
(
Unit::Nanosecond,
self.nanosecond,
self.nanosecond_visibility,
),
]
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
struct ValidatedDurationFormatterOptionsBuilder {
base: BaseStyle,
year: Option<FieldStyle>,
year_visibility: Option<FieldDisplay>,
month: Option<FieldStyle>,
month_visibility: Option<FieldDisplay>,
week: Option<FieldStyle>,
week_visibility: Option<FieldDisplay>,
day: Option<FieldStyle>,
day_visibility: Option<FieldDisplay>,
hour: Option<FieldStyle>,
hour_visibility: Option<FieldDisplay>,
minute: Option<FieldStyle>,
minute_visibility: Option<FieldDisplay>,
second: Option<FieldStyle>,
second_visibility: Option<FieldDisplay>,
millisecond: Option<FieldStyle>,
millisecond_visibility: Option<FieldDisplay>,
microsecond: Option<FieldStyle>,
microsecond_visibility: Option<FieldDisplay>,
nanosecond: Option<FieldStyle>,
nanosecond_visibility: Option<FieldDisplay>,
fractional_digits: FractionalDigits,
}
impl ValidatedDurationFormatterOptionsBuilder {
fn iter_units(&mut self) -> [(Unit, &mut Option<FieldStyle>, &mut Option<FieldDisplay>); 10] {
[
(Unit::Year, &mut self.year, &mut self.year_visibility),
(Unit::Month, &mut self.month, &mut self.month_visibility),
(Unit::Week, &mut self.week, &mut self.week_visibility),
(Unit::Day, &mut self.day, &mut self.day_visibility),
(Unit::Hour, &mut self.hour, &mut self.hour_visibility),
(Unit::Minute, &mut self.minute, &mut self.minute_visibility),
(Unit::Second, &mut self.second, &mut self.second_visibility),
(
Unit::Millisecond,
&mut self.millisecond,
&mut self.millisecond_visibility,
),
(
Unit::Microsecond,
&mut self.microsecond,
&mut self.microsecond_visibility,
),
(
Unit::Nanosecond,
&mut self.nanosecond,
&mut self.nanosecond_visibility,
),
]
}
}
impl From<DurationFormatterOptions> for ValidatedDurationFormatterOptionsBuilder {
fn from(value: DurationFormatterOptions) -> Self {
ValidatedDurationFormatterOptionsBuilder {
base: value.base,
year: value.year.map(FieldStyle::from),
year_visibility: value.year_visibility,
month: value.month.map(FieldStyle::from),
month_visibility: value.month_visibility,
week: value.week.map(FieldStyle::from),
week_visibility: value.week_visibility,
day: value.day.map(FieldStyle::from),
day_visibility: value.day_visibility,
hour: value.hour.map(FieldStyle::from),
hour_visibility: value.hour_visibility,
minute: value.minute.map(FieldStyle::from),
minute_visibility: value.minute_visibility,
second: value.second.map(FieldStyle::from),
second_visibility: value.second_visibility,
millisecond: value.millisecond.map(FieldStyle::from),
millisecond_visibility: value.millisecond_visibility,
microsecond: value.microsecond.map(FieldStyle::from),
microsecond_visibility: value.microsecond_visibility,
nanosecond: value.nanosecond.map(FieldStyle::from),
nanosecond_visibility: value.nanosecond_visibility,
fractional_digits: value.fractional_digits,
}
}
}
impl TryFrom<ValidatedDurationFormatterOptionsBuilder> for ValidatedDurationFormatterOptions {
type Error = ();
fn try_from(value: ValidatedDurationFormatterOptionsBuilder) -> Result<Self, Self::Error> {
Ok(ValidatedDurationFormatterOptions {
base: value.base,
year: value.year.ok_or(())?,
year_visibility: value.year_visibility.ok_or(())?,
month: value.month.ok_or(())?,
month_visibility: value.month_visibility.ok_or(())?,
week: value.week.ok_or(())?,
week_visibility: value.week_visibility.ok_or(())?,
day: value.day.ok_or(())?,
day_visibility: value.day_visibility.ok_or(())?,
hour: value.hour.ok_or(())?,
hour_visibility: value.hour_visibility.ok_or(())?,
minute: value.minute.ok_or(())?,
minute_visibility: value.minute_visibility.ok_or(())?,
second: value.second.ok_or(())?,
second_visibility: value.second_visibility.ok_or(())?,
millisecond: value.millisecond.ok_or(())?,
millisecond_visibility: value.millisecond_visibility.ok_or(())?,
microsecond: value.microsecond.ok_or(())?,
microsecond_visibility: value.microsecond_visibility.ok_or(())?,
nanosecond: value.nanosecond.ok_or(())?,
nanosecond_visibility: value.nanosecond_visibility.ok_or(())?,
fractional_digits: value.fractional_digits,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Unit {
Year,
Month,
Week,
Day,
Hour,
Minute,
Second,
Millisecond,
Microsecond,
Nanosecond,
}
impl Unit {
pub(crate) fn digital_default(&self) -> FieldStyle {
match self {
Unit::Year => YearStyle::Short.into(),
Unit::Month => MonthStyle::Short.into(),
Unit::Week => WeekStyle::Short.into(),
Unit::Day => DayStyle::Short.into(),
Unit::Hour => HourStyle::Numeric.into(),
Unit::Minute => MinuteStyle::Numeric.into(),
Unit::Second => SecondStyle::Numeric.into(),
Unit::Millisecond => MilliSecondStyle::Numeric.into(),
Unit::Microsecond => MicroSecondStyle::Numeric.into(),
Unit::Nanosecond => NanoSecondStyle::Numeric.into(),
}
}
pub(crate) const fn as_unit_formatter_name(&self) -> &'static str {
match self {
Unit::Year => "year",
Unit::Month => "month",
Unit::Week => "week",
Unit::Day => "day",
Unit::Hour => "hour",
Unit::Minute => "minute",
Unit::Second => "second",
Unit::Millisecond => "millisecond",
Unit::Microsecond => "microsecond",
Unit::Nanosecond => "nanosecond",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fractional_digit_error() {
let options = DurationFormatterOptions {
fractional_digits: FractionalDigits::Fixed(10),
..Default::default()
};
assert_eq!(
ValidatedDurationFormatterOptions::validate(options),
Err(DurationFormatterOptionsError::FractionalDigitsOutOfRange)
);
}
#[test]
fn test_invalid_style_after_two_digit() {
let options = DurationFormatterOptions {
hour: Some(HourStyle::TwoDigit),
minute: Some(MinuteStyle::Long),
..Default::default()
};
assert_eq!(
ValidatedDurationFormatterOptions::validate(options),
Err(DurationFormatterOptionsError::PreviousNumeric)
);
let options = DurationFormatterOptions {
hour: Some(HourStyle::TwoDigit),
..options
};
assert_eq!(
ValidatedDurationFormatterOptions::validate(options),
Err(DurationFormatterOptionsError::PreviousNumeric)
);
}
#[test]
fn test_display_always_fractional_style() {
let options = DurationFormatterOptions {
millisecond: Some(MilliSecondStyle::Numeric),
millisecond_visibility: Some(FieldDisplay::Always),
..Default::default()
};
assert_eq!(
ValidatedDurationFormatterOptions::validate(options),
Err(DurationFormatterOptionsError::DisplayAlwaysFractional)
);
}
#[test]
fn test_previous_fractional() {
let options = DurationFormatterOptions {
millisecond: Some(MilliSecondStyle::Numeric),
nanosecond: Some(NanoSecondStyle::Long),
..Default::default()
};
assert_eq!(
ValidatedDurationFormatterOptions::validate(options),
Err(DurationFormatterOptionsError::PreviousFractional)
);
}
}