use crate::{
fields::{self, Field, FieldLength, FieldSymbol},
pattern::{runtime::PatternPlurals, PatternItem},
};
#[cfg(feature = "experimental")]
use alloc::vec::Vec;
use super::preferences;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Default, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct Bag {
pub era: Option<Text>,
pub year: Option<Year>,
pub month: Option<Month>,
pub week: Option<Week>,
pub day: Option<Day>,
pub weekday: Option<Text>,
pub hour: Option<Numeric>,
pub minute: Option<Numeric>,
pub second: Option<Numeric>,
pub fractional_second: Option<u8>,
pub time_zone_name: Option<TimeZoneName>,
pub preferences: Option<preferences::Bag>,
}
impl Bag {
pub fn empty() -> Self {
Self::default()
}
#[allow(clippy::wrong_self_convention)]
#[cfg(any(test, feature = "experimental"))] pub(crate) fn to_vec_fields(&self) -> Vec<Field> {
let mut fields = Vec::new();
if let Some(era) = self.era {
fields.push(Field {
symbol: FieldSymbol::Era,
length: match era {
Text::Short => FieldLength::Abbreviated,
Text::Long => FieldLength::Wide,
Text::Narrow => FieldLength::Narrow,
},
})
}
if let Some(year) = self.year {
fields.push(Field {
symbol: FieldSymbol::Year(match year {
Year::Numeric | Year::TwoDigit => fields::Year::Calendar,
Year::NumericWeekOf | Year::TwoDigitWeekOf => fields::Year::WeekOf,
}),
length: match year {
Year::Numeric | Year::NumericWeekOf => FieldLength::One,
Year::TwoDigit | Year::TwoDigitWeekOf => FieldLength::TwoDigit,
},
});
}
if let Some(month) = self.month {
fields.push(Field {
symbol: FieldSymbol::Month(fields::Month::Format),
length: match month {
Month::Numeric => FieldLength::One,
Month::TwoDigit => FieldLength::TwoDigit,
Month::Long => FieldLength::Wide,
Month::Short => FieldLength::Abbreviated,
Month::Narrow => FieldLength::Narrow,
},
});
}
if let Some(week) = self.week {
fields.push(Field {
symbol: FieldSymbol::Week(match week {
Week::WeekOfMonth => fields::Week::WeekOfMonth,
Week::NumericWeekOfYear | Week::TwoDigitWeekOfYear => fields::Week::WeekOfYear,
}),
length: match week {
Week::WeekOfMonth | Week::NumericWeekOfYear => FieldLength::One,
Week::TwoDigitWeekOfYear => FieldLength::TwoDigit,
},
});
}
if let Some(day) = self.day {
fields.push(Field {
symbol: FieldSymbol::Day(match day {
Day::NumericDayOfMonth | Day::TwoDigitDayOfMonth => fields::Day::DayOfMonth,
Day::DayOfWeekInMonth => fields::Day::DayOfWeekInMonth,
}),
length: match day {
Day::NumericDayOfMonth | Day::DayOfWeekInMonth => FieldLength::One,
Day::TwoDigitDayOfMonth => FieldLength::TwoDigit,
},
});
}
if let Some(weekday) = self.weekday {
fields.push(Field {
symbol: FieldSymbol::Weekday(fields::Weekday::Format),
length: match weekday {
Text::Long => FieldLength::Wide,
Text::Short => FieldLength::One,
Text::Narrow => FieldLength::Narrow,
},
});
}
if let Some(hour) = self.hour {
fields.push(Field {
symbol: FieldSymbol::Hour(match self.preferences {
Some(preferences::Bag {
hour_cycle: Some(hour_cycle),
}) => match hour_cycle {
preferences::HourCycle::H11 | preferences::HourCycle::H12 => {
fields::Hour::H12
}
preferences::HourCycle::H24 | preferences::HourCycle::H23 => {
fields::Hour::H23
}
},
_ => fields::Hour::H23,
}),
length: match hour {
Numeric::Numeric => FieldLength::One,
Numeric::TwoDigit => FieldLength::TwoDigit,
},
});
}
if let Some(minute) = self.minute {
fields.push(Field {
symbol: FieldSymbol::Minute,
length: match minute {
Numeric::Numeric => FieldLength::One,
Numeric::TwoDigit => FieldLength::TwoDigit,
},
});
}
if let Some(second) = self.second {
fields.push(Field {
symbol: FieldSymbol::Second(fields::Second::Second),
length: match second {
Numeric::Numeric => FieldLength::One,
Numeric::TwoDigit => FieldLength::TwoDigit,
},
});
}
if let Some(precision) = self.fractional_second {
fields.push(Field {
symbol: FieldSymbol::Second(fields::Second::FractionalSecond),
length: FieldLength::Fixed(precision),
});
}
if self.time_zone_name.is_some() {
fields.push(Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
length: FieldLength::One,
});
}
{
#![allow(clippy::indexing_slicing)] debug_assert!(
fields.windows(2).all(|f| f[0] < f[1]),
"The fields are sorted and unique."
);
}
fields
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Numeric {
Numeric,
TwoDigit,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Text {
Long,
Short,
Narrow,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Year {
Numeric,
TwoDigit,
NumericWeekOf,
TwoDigitWeekOf,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Month {
Numeric,
TwoDigit,
Long,
Short,
Narrow,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Week {
WeekOfMonth,
NumericWeekOfYear,
TwoDigitWeekOfYear,
}
#[derive(Debug, Clone, PartialEq, Copy)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum Day {
NumericDayOfMonth,
TwoDigitDayOfMonth,
DayOfWeekInMonth,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "kebab-case")
)]
#[non_exhaustive]
pub enum TimeZoneName {
ShortSpecific,
LongSpecific,
GmtOffset,
ShortGeneric,
LongGeneric,
}
impl From<TimeZoneName> for Field {
fn from(time_zone_name: TimeZoneName) -> Self {
match time_zone_name {
TimeZoneName::ShortSpecific => Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ),
length: FieldLength::One,
},
TimeZoneName::LongSpecific => Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ),
length: FieldLength::Wide,
},
TimeZoneName::GmtOffset => Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperO),
length: FieldLength::Wide,
},
TimeZoneName::ShortGeneric => Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
length: FieldLength::One,
},
TimeZoneName::LongGeneric => Field {
symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
length: FieldLength::Wide,
},
}
}
}
impl<'data> From<&PatternPlurals<'data>> for Bag {
fn from(other: &PatternPlurals) -> Self {
let pattern = match other {
PatternPlurals::SinglePattern(pattern) => pattern,
PatternPlurals::MultipleVariants(plural_pattern) => &plural_pattern.other,
};
let mut bag: Bag = Default::default();
for item in pattern.items.iter() {
let field = match item {
PatternItem::Field(ref field) => field,
PatternItem::Literal(_) => continue,
};
match field.symbol {
FieldSymbol::Era => {
bag.era = Some(match field.length {
FieldLength::One | FieldLength::TwoDigit | FieldLength::Abbreviated => {
Text::Short
}
FieldLength::Wide => Text::Long,
FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
Text::Narrow
}
});
}
FieldSymbol::Year(year) => {
bag.year = Some(match year {
fields::Year::Calendar => match field.length {
FieldLength::TwoDigit => Year::TwoDigit,
_ => Year::Numeric,
},
fields::Year::WeekOf => match field.length {
FieldLength::TwoDigit => Year::TwoDigitWeekOf,
_ => Year::NumericWeekOf,
},
});
}
FieldSymbol::Month(_) => {
bag.month = Some(match field.length {
FieldLength::One => Month::Numeric,
FieldLength::TwoDigit => Month::TwoDigit,
FieldLength::Abbreviated => Month::Short,
FieldLength::Wide => Month::Long,
FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
Month::Narrow
}
});
}
FieldSymbol::Week(week) => {
bag.week = Some(match week {
fields::Week::WeekOfYear => match field.length {
FieldLength::TwoDigit => Week::TwoDigitWeekOfYear,
_ => Week::NumericWeekOfYear,
},
fields::Week::WeekOfMonth => Week::WeekOfMonth,
});
}
FieldSymbol::Day(day) => {
bag.day = Some(match day {
fields::Day::DayOfMonth => match field.length {
FieldLength::TwoDigit => Day::TwoDigitDayOfMonth,
_ => Day::NumericDayOfMonth,
},
fields::Day::DayOfYear => unimplemented!("fields::Day::DayOfYear #591"),
fields::Day::DayOfWeekInMonth => Day::DayOfWeekInMonth,
fields::Day::ModifiedJulianDay => {
unimplemented!("fields::Day::ModifiedJulianDay")
}
});
}
FieldSymbol::Weekday(weekday) => {
bag.weekday = Some(match weekday {
fields::Weekday::Format => match field.length {
FieldLength::One | FieldLength::TwoDigit | FieldLength::Abbreviated => {
Text::Short
}
FieldLength::Wide => Text::Long,
_ => Text::Narrow,
},
fields::Weekday::StandAlone => match field.length {
FieldLength::One | FieldLength::TwoDigit => {
unimplemented!("Numeric stand-alone fields are not supported.")
}
FieldLength::Abbreviated => Text::Short,
FieldLength::Wide => Text::Long,
FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
Text::Narrow
}
},
fields::Weekday::Local => unimplemented!("fields::Weekday::Local"),
});
}
FieldSymbol::DayPeriod(_) => {
}
FieldSymbol::Hour(hour) => {
bag.hour = Some(match field.length {
FieldLength::TwoDigit => Numeric::TwoDigit,
_ => Numeric::Numeric,
});
bag.preferences = Some(preferences::Bag {
hour_cycle: Some(match hour {
fields::Hour::H11 => preferences::HourCycle::H11,
fields::Hour::H12 => preferences::HourCycle::H12,
fields::Hour::H23 => preferences::HourCycle::H23,
fields::Hour::H24 => preferences::HourCycle::H24,
}),
});
}
FieldSymbol::Minute => {
bag.minute = Some(match field.length {
FieldLength::TwoDigit => Numeric::TwoDigit,
_ => Numeric::Numeric,
});
}
FieldSymbol::Second(second) => {
match second {
fields::Second::Second => {
bag.second = Some(match field.length {
FieldLength::TwoDigit => Numeric::TwoDigit,
_ => Numeric::Numeric,
});
}
fields::Second::FractionalSecond => {
if let FieldLength::Fixed(p) = field.length {
if p > 0 {
bag.fractional_second = Some(p);
}
}
}
fields::Second::Millisecond => {
}
}
}
FieldSymbol::TimeZone(time_zone_name) => {
bag.time_zone_name = Some(match time_zone_name {
fields::TimeZone::LowerZ => match field.length {
FieldLength::One => TimeZoneName::ShortSpecific,
_ => TimeZoneName::LongSpecific,
},
fields::TimeZone::LowerV => match field.length {
FieldLength::One => TimeZoneName::ShortGeneric,
_ => TimeZoneName::LongGeneric,
},
fields::TimeZone::UpperO => TimeZoneName::GmtOffset,
fields::TimeZone::UpperZ => unimplemented!("fields::TimeZone::UpperZ"),
fields::TimeZone::UpperV => unimplemented!("fields::TimeZone::UpperV"),
fields::TimeZone::LowerX => unimplemented!("fields::TimeZone::LowerX"),
fields::TimeZone::UpperX => unimplemented!("fields::TimeZone::UpperX"),
});
}
}
}
bag
}
}
#[cfg(test)]
mod test {
use super::*;
type Symbol = FieldSymbol;
type Length = FieldLength;
#[test]
fn test_component_bag_to_vec_field() {
let bag = Bag {
year: Some(Year::Numeric),
month: Some(Month::Long),
week: Some(Week::WeekOfMonth),
day: Some(Day::NumericDayOfMonth),
hour: Some(Numeric::Numeric),
minute: Some(Numeric::Numeric),
second: Some(Numeric::Numeric),
fractional_second: Some(3),
..Default::default()
};
assert_eq!(
bag.to_vec_fields(),
vec![
(Symbol::Year(fields::Year::Calendar), Length::One).into(),
(Symbol::Month(fields::Month::Format), Length::Wide).into(),
(Symbol::Week(fields::Week::WeekOfMonth), Length::One).into(),
(Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
(Symbol::Hour(fields::Hour::H23), Length::One).into(),
(Symbol::Minute, Length::One).into(),
(Symbol::Second(fields::Second::Second), Length::One).into(),
(
Symbol::Second(fields::Second::FractionalSecond),
Length::Fixed(3)
)
.into(),
]
);
}
#[test]
fn test_component_bag_to_vec_field2() {
let bag = Bag {
year: Some(Year::Numeric),
month: Some(Month::TwoDigit),
day: Some(Day::NumericDayOfMonth),
..Default::default()
};
assert_eq!(
bag.to_vec_fields(),
vec![
(Symbol::Year(fields::Year::Calendar), Length::One).into(),
(Symbol::Month(fields::Month::Format), Length::TwoDigit).into(),
(Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
]
);
}
}