use smallvec::SmallVec;
use std::{convert::TryFrom, fmt};
use crate::{
fields::{self, Field, FieldLength, FieldSymbol},
options::length,
pattern::Pattern,
provider::gregory::patterns::{LengthPatternsV1, PatternV1, SkeletonV1, SkeletonsV1},
};
#[cfg(feature = "provider_serde")]
use serde::{
de,
ser::{self, SerializeSeq},
Deserialize, Deserializer, Serialize,
};
#[derive(Debug, PartialEq)]
struct FieldIndex(usize);
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd)]
pub struct Skeleton(SmallVec<[fields::Field; 5]>);
impl Skeleton {
fn fields_iter<'a>(&'a self) -> impl Iterator<Item = &Field> + 'a {
self.0.iter()
}
fn fields_len(&self) -> usize {
self.0.len()
}
}
#[cfg(feature = "provider_serde")]
#[allow(clippy::upper_case_acronyms)]
struct DeserializeSkeletonFieldsUTS35String;
#[cfg(feature = "provider_serde")]
impl<'de> de::Visitor<'de> for DeserializeSkeletonFieldsUTS35String {
type Value = Skeleton;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "Expected to find a valid skeleton.")
}
fn visit_str<E>(self, skeleton_string: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Skeleton::try_from(skeleton_string).map_err(|err| {
de::Error::invalid_value(
de::Unexpected::Other(&format!("{:?} {}", skeleton_string, err)),
&"field symbols representing a skeleton",
)
})
}
}
#[cfg(feature = "provider_serde")]
struct DeserializeSkeletonBincode;
#[cfg(feature = "provider_serde")]
impl<'de> de::Visitor<'de> for DeserializeSkeletonBincode {
type Value = Skeleton;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "Unable to deserialize a bincode Pattern.")
}
fn visit_seq<V>(self, mut seq: V) -> Result<Skeleton, V::Error>
where
V: de::SeqAccess<'de>,
{
let mut items: SmallVec<[fields::Field; 5]> = SmallVec::new();
while let Some(item) = seq.next_element()? {
if let Some(prev_item) = items.last() {
if prev_item >= &item {
return Err(de::Error::invalid_value(
de::Unexpected::Other(&format!(
"field item out of order or duplicate: {:?}",
item
)),
&"ordered field symbols representing a skeleton",
));
}
}
items.push(item)
}
Ok(Skeleton(items))
}
}
#[cfg(feature = "provider_serde")]
impl<'de> Deserialize<'de> for Skeleton {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(DeserializeSkeletonFieldsUTS35String)
} else {
deserializer.deserialize_seq(DeserializeSkeletonBincode)
}
}
}
#[cfg(feature = "provider_serde")]
impl Serialize for Skeleton {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
let mut string = String::new();
for field in self.0.iter() {
let ch: char = field.symbol.into();
for _ in 0..field.length as usize {
string.push(ch);
}
}
serializer.serialize_str(&string)
} else {
let mut seq = serializer.serialize_seq(Some(self.fields_len()))?;
for item in self.fields_iter() {
seq.serialize_element(item)?;
}
seq.end()
}
}
}
impl TryFrom<&str> for Skeleton {
type Error = SkeletonError;
fn try_from(skeleton_string: &str) -> Result<Self, Self::Error> {
let mut fields: SmallVec<[fields::Field; 5]> = SmallVec::new();
let mut iter = skeleton_string.bytes().peekable();
while let Some(byte) = iter.next() {
let field_symbol = FieldSymbol::try_from(byte)?;
let mut field_length: u8 = 1;
while let Some(next_byte) = iter.peek() {
if *next_byte != byte {
break;
}
field_length += 1;
iter.next();
}
let field = Field::from((field_symbol, FieldLength::try_from(field_length)?));
match fields.binary_search(&field) {
Ok(_) => return Err(SkeletonError::DuplicateField),
Err(pos) => fields.insert(pos, field),
}
}
Ok(Self(fields))
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct AvailableFormatPattern<'a> {
skeleton: &'a Skeleton,
pub pattern: &'a Pattern,
}
impl<'a> From<(&'a SkeletonV1, &'a PatternV1)> for AvailableFormatPattern<'a> {
fn from(tuple: (&'a SkeletonV1, &'a PatternV1)) -> Self {
let (skeleton_v1, pattern_v1) = tuple;
AvailableFormatPattern {
skeleton: &skeleton_v1.0,
pattern: &pattern_v1.0,
}
}
}
#[derive(Debug)]
pub enum SkeletonError {
InvalidFieldLength,
DuplicateField,
SymbolUnknown(char),
SymbolInvalid(char),
SymbolUnimplemented(char),
UnimplementedField(char),
Fields(fields::Error),
}
impl fmt::Display for SkeletonError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidFieldLength => write!(f, "field too long in skeleton"),
Self::DuplicateField => write!(f, "duplicate field in skeleton"),
Self::SymbolUnknown(ch) => write!(f, "symbol unknown {} in skeleton", ch),
Self::SymbolInvalid(ch) => write!(f, "symbol invalid {} in skeleton", ch),
Self::SymbolUnimplemented(ch) => {
write!(f, "symbol unimplemented {} in skeleton", ch)
}
Self::UnimplementedField(ch) => {
write!(f, "unimplemented field {} in skeleton", ch)
}
Self::Fields(err) => write!(f, "{} in skeleton", err),
}
}
}
impl From<fields::Error> for SkeletonError {
fn from(fields_error: fields::Error) -> Self {
Self::Fields(fields_error)
}
}
impl From<fields::LengthError> for SkeletonError {
fn from(_: fields::LengthError) -> Self {
Self::InvalidFieldLength
}
}
impl From<fields::SymbolError> for SkeletonError {
fn from(symbol_error: fields::SymbolError) -> Self {
match symbol_error {
fields::SymbolError::Invalid(ch) => Self::SymbolInvalid(ch),
fields::SymbolError::Unknown(byte) => {
match byte {
b'B'
| b'G'
| b'W'
| b'Q'
| b'w'
=> Self::SymbolUnimplemented(byte.into()),
_ => Self::SymbolUnknown(byte.into()),
}
}
}
}
}
const MAX_SKELETON_FIELDS: u32 = 10;
const NO_DISTANCE: u32 = 0;
const WIDTH_MISMATCH_DISTANCE: u32 = 1;
const TEXT_VS_NUMERIC_DISTANCE: u32 = 10;
const SUBSTANTIAL_DIFFERENCES_DISTANCE: u32 = 100;
const SKELETON_EXTRA_SYMBOL: u32 = 1000;
const REQUESTED_SYMBOL_MISSING: u32 = 10000;
#[derive(Debug, PartialEq, Clone)]
pub enum BestSkeleton<T> {
AllFieldsMatch(T),
MissingOrExtraFields(T),
NoMatch,
}
pub fn create_best_pattern_for_fields<'a>(
skeletons: &'a SkeletonsV1,
length_patterns: &LengthPatternsV1,
fields: &[Field],
) -> BestSkeleton<Pattern> {
let first_pattern_match = get_best_available_format_pattern(skeletons, fields);
if let BestSkeleton::AllFieldsMatch(pattern) = first_pattern_match {
return BestSkeleton::AllFieldsMatch(pattern.clone());
}
let FieldsByType { date, time, other } = group_fields_by_type(fields);
if !other.is_empty() {
unimplemented!(
"There are no \"other\" fields supported, these need to be appended to the pattern. {:?}", other
);
}
if date.is_empty() || time.is_empty() {
return match first_pattern_match {
BestSkeleton::AllFieldsMatch(_) => {
unreachable!("Logic error in implementation. AllFieldsMatch handled above.")
}
BestSkeleton::MissingOrExtraFields(pattern) => {
BestSkeleton::MissingOrExtraFields(pattern.clone())
}
BestSkeleton::NoMatch => BestSkeleton::NoMatch,
};
}
let (date_pattern, date_missing_or_extra) =
match get_best_available_format_pattern(skeletons, &date) {
BestSkeleton::MissingOrExtraFields(fields) => (Some(fields), true),
BestSkeleton::AllFieldsMatch(fields) => (Some(fields), false),
BestSkeleton::NoMatch => (None, true),
};
let (time_pattern, time_missing_or_extra) =
match get_best_available_format_pattern(skeletons, &time) {
BestSkeleton::MissingOrExtraFields(fields) => (Some(fields), true),
BestSkeleton::AllFieldsMatch(fields) => (Some(fields), false),
BestSkeleton::NoMatch => (None, true),
};
let pattern: Option<Pattern> = match (date_pattern, time_pattern) {
(Some(date_pattern), Some(time_pattern)) => {
let month_field = fields
.iter()
.find(|f| matches!(f.symbol, FieldSymbol::Month(_)));
let length = match month_field {
Some(field) => match field.length {
FieldLength::Wide => {
let weekday = fields
.iter()
.find(|f| matches!(f.symbol, FieldSymbol::Weekday(_)));
if weekday.is_some() {
length::Date::Full
} else {
length::Date::Long
}
}
FieldLength::Abbreviated => length::Date::Medium,
_ => length::Date::Short,
},
None => length::Date::Short,
};
let bytes = match length {
length::Date::Full => &length_patterns.full,
length::Date::Long => &length_patterns.long,
length::Date::Medium => &length_patterns.medium,
length::Date::Short => &length_patterns.short,
};
Some(
Pattern::from_bytes_combination(bytes, date_pattern.clone(), time_pattern.clone())
.expect("TODO"),
)
}
(None, Some(pattern)) => Some(pattern.clone()),
(Some(pattern), None) => Some(pattern.clone()),
(None, None) => None,
};
match pattern {
Some(pattern) => {
if date_missing_or_extra || time_missing_or_extra {
BestSkeleton::MissingOrExtraFields(pattern)
} else {
BestSkeleton::AllFieldsMatch(pattern)
}
}
None => BestSkeleton::NoMatch,
}
}
struct FieldsByType {
pub date: Vec<Field>,
pub time: Vec<Field>,
pub other: Vec<Field>,
}
fn group_fields_by_type(fields: &[Field]) -> FieldsByType {
let mut date = Vec::new();
let mut time = Vec::new();
let mut other = Vec::new();
for field in fields {
match field.symbol {
FieldSymbol::Year(_)
| FieldSymbol::Month(_)
| FieldSymbol::Day(_)
| FieldSymbol::Weekday(_) => date.push(*field),
FieldSymbol::DayPeriod(_)
| FieldSymbol::Hour(_)
| FieldSymbol::Minute
| FieldSymbol::Second(_) => time.push(*field),
FieldSymbol::TimeZone(_) => other.push(*field),
};
}
FieldsByType { date, time, other }
}
pub fn get_best_available_format_pattern<'a>(
skeletons: &'a SkeletonsV1,
fields: &[Field],
) -> BestSkeleton<&'a Pattern> {
let mut closest_format_pattern = None;
let mut closest_distance: u32 = u32::MAX;
let mut closest_missing_fields = 0;
for available_format_pattern in get_available_format_patterns(skeletons) {
let skeleton = &available_format_pattern.skeleton;
debug_assert!(
skeleton.fields_len() <= MAX_SKELETON_FIELDS as usize,
"The distance mechanism assumes skeletons are less than MAX_SKELETON_FIELDS in length."
);
let mut missing_fields = 0;
let mut distance: u32 = 0;
let mut requested_fields = fields.iter().peekable();
let mut skeleton_fields = skeleton.fields_iter().peekable();
loop {
let next = (requested_fields.peek(), skeleton_fields.peek());
match next {
(Some(requested_field), Some(skeleton_field)) => {
debug_assert!(
skeleton_field.symbol != FieldSymbol::Month(fields::Month::StandAlone)
);
if skeleton_field.symbol > requested_field.symbol {
skeleton_fields.next();
distance += SKELETON_EXTRA_SYMBOL;
continue;
}
if skeleton_field.symbol < requested_field.symbol {
distance += REQUESTED_SYMBOL_MISSING;
missing_fields += 1;
requested_fields.next();
continue;
}
distance += if requested_field == skeleton_field {
NO_DISTANCE
} else if requested_field.symbol != skeleton_field.symbol {
SUBSTANTIAL_DIFFERENCES_DISTANCE
} else if requested_field.get_length_type() != skeleton_field.get_length_type()
{
TEXT_VS_NUMERIC_DISTANCE
} else {
WIDTH_MISMATCH_DISTANCE
};
requested_fields.next();
skeleton_fields.next();
}
(None, Some(_)) => {
distance += SKELETON_EXTRA_SYMBOL;
skeleton_fields.next();
}
(Some(_), None) => {
distance += REQUESTED_SYMBOL_MISSING;
requested_fields.next();
missing_fields += 1;
}
(None, None) => {
break;
}
}
}
if distance < closest_distance {
closest_format_pattern = Some(available_format_pattern.pattern);
closest_distance = distance;
closest_missing_fields = missing_fields;
}
}
let closest_format_pattern =
closest_format_pattern.expect("At least one closest format pattern will always be found.");
if closest_missing_fields == fields.len() {
return BestSkeleton::NoMatch;
}
if closest_distance >= SKELETON_EXTRA_SYMBOL {
return BestSkeleton::MissingOrExtraFields(closest_format_pattern);
}
BestSkeleton::AllFieldsMatch(closest_format_pattern)
}
pub fn get_available_format_patterns<'a>(
skeletons: &'a SkeletonsV1,
) -> impl Iterator<Item = AvailableFormatPattern> + 'a {
skeletons.0.iter().map(AvailableFormatPattern::from)
}
#[cfg(all(test, feature = "provider_serde"))]
mod test {
use super::*;
use icu_locid_macros::langid;
use icu_provider::{DataProvider, DataRequest, ResourceOptions, ResourcePath};
use std::borrow::Cow;
use crate::{
fields::{Day, Field, FieldLength, Month, Weekday},
options::components,
provider::{gregory::DatesV1, key::GREGORY_V1},
};
fn get_data_provider() -> Cow<'static, DatesV1> {
let provider = icu_testdata::get_provider();
let langid = langid!("en");
provider
.load_payload(&DataRequest {
resource_path: ResourcePath {
key: GREGORY_V1,
options: ResourceOptions {
variant: None,
langid: Some(langid),
},
},
})
.unwrap()
.payload
.take()
.unwrap()
}
#[test]
fn test_skeleton_matching() {
let components = components::Bag {
year: Some(components::Numeric::Numeric),
month: Some(components::Month::Long),
day: Some(components::Numeric::Numeric),
hour: Some(components::Numeric::Numeric),
minute: Some(components::Numeric::Numeric),
second: Some(components::Numeric::Numeric),
..Default::default()
};
let requested_fields = components.to_vec_fields();
let data_provider = get_data_provider();
match get_best_available_format_pattern(
&data_provider.patterns.datetime.skeletons,
&requested_fields,
) {
BestSkeleton::AllFieldsMatch(available_format_pattern)
| BestSkeleton::MissingOrExtraFields(available_format_pattern) => {
assert_eq!(
available_format_pattern.to_string(),
String::from("MMM d, y")
)
}
BestSkeleton::NoMatch => {
panic!("No skeleton was found.")
}
};
}
#[test]
fn test_skeleton_matching_missing_fields() {
let components = components::Bag {
month: Some(components::Month::Numeric),
weekday: Some(components::Text::Short),
..Default::default()
};
let requested_fields = components.to_vec_fields();
let data_provider = get_data_provider();
match get_best_available_format_pattern(
&data_provider.patterns.datetime.skeletons,
&requested_fields,
) {
BestSkeleton::MissingOrExtraFields(available_format_pattern) => {
assert_eq!(available_format_pattern.to_string(), String::from("L"))
}
best => panic!("Unexpected {:?}", best),
};
}
#[test]
fn test_missing_append_items_support() {
let components = components::Bag {
year: Some(components::Numeric::Numeric),
month: Some(components::Month::Long),
day: Some(components::Numeric::Numeric),
time_zone_name: Some(components::TimeZoneName::Long),
..Default::default()
};
let requested_fields = components.to_vec_fields();
let data_provider = get_data_provider();
match create_best_pattern_for_fields(
&data_provider.patterns.datetime.skeletons,
&data_provider.patterns.datetime.length_patterns,
&requested_fields,
) {
BestSkeleton::AllFieldsMatch(available_format_pattern) => {
assert_eq!(
available_format_pattern.to_string(),
String::from("MMM d, y")
)
}
best => panic!("Unexpected {:?}", best),
};
}
#[test]
fn test_skeleton_empty_bag() {
let components: components::Bag = Default::default();
let requested_fields = components.to_vec_fields();
let data_provider = get_data_provider();
assert_eq!(
get_best_available_format_pattern(
&data_provider.patterns.datetime.skeletons,
&requested_fields
),
BestSkeleton::NoMatch,
"No match was found"
);
}
#[test]
fn test_skeleton_no_match() {
let components = components::Bag {
time_zone_name: Some(components::TimeZoneName::Long),
..Default::default()
};
let requested_fields = components.to_vec_fields();
let data_provider = get_data_provider();
assert_eq!(
get_best_available_format_pattern(
&data_provider.patterns.datetime.skeletons,
&requested_fields
),
BestSkeleton::NoMatch,
"No match was found"
);
}
#[rustfmt::skip]
const SUPPORTED_STRING_SKELETONS: [&str; 60] = [
"E", "dEEEE", "EHm", "EHms", "dE", "Ehm", "Ehms", "H", "HHmm", "HHmmss", "Hm", "Hms", "M",
"MdEEEE", "MdE", "MMM", "MMMdEEEE", "MMMdE", "MMMM", "MMMMdEEEE", "MMMMdE", "MMMMd",
"MMMMdd", "MMMd", "MMMdd", "MMd", "MMdd", "Md", "Mdd", "d", "h", "hm", "hms", "mmss", "ms",
"y", "yM", "yMdEEEE", "yMdE", "yMM", "yMMM", "yMMMdEEEE", "yMMMdE", "yMMMM", "yMMMMdEEEE",
"yMMMMdE", "yMMMMdcccc", "yMMMMd", "yMMMd", "yMMdd", "yMd",
"HHmmZ", "Hmsv", "Hmsvvvv", "Hmv", "Hmvvvv", "hmsv", "hmsvvvv", "hmv", "hmvvvv",
];
#[rustfmt::skip]
const UNSUPPORTED_STRING_SKELETONS: [&str; 19] = [
"Bh", "Bhm", "Bhms", "EBhm", "EBhms",
"Gy", "GyM", "GyMMM", "GyMMMdEEEE", "GyMMMdE", "GyMMMM", "GyMMMMdE", "GyMMMMd", "GyMMMd",
"MMMMW",
"yQ", "yQQQ", "yQQQQ",
"yw"
];
#[test]
fn test_known_skeletons_ok() {
for string_skeleton in &SUPPORTED_STRING_SKELETONS {
match Skeleton::try_from(*string_skeleton) {
Ok(_) => {}
Err(err) => {
panic!(
"Unable to parse string_skeleton {:?} with error, {:?}",
string_skeleton, err
)
}
}
}
}
#[test]
fn test_unsupported_skeletons_skeletons_err() {
for string_skeleton in &UNSUPPORTED_STRING_SKELETONS {
match Skeleton::try_from(*string_skeleton) {
Ok(_) => {
panic!(
"An unsupported field is now supported, consider moving {:?} to the \
supported skeletons, and ensure the skeleton is properly implemented.",
string_skeleton
)
}
Err(err) => match err {
SkeletonError::SymbolUnimplemented(_) => {
}
_ => panic!("{}", err),
},
}
}
}
#[test]
fn test_skeleton_deserialization() {
assert_eq!(
Skeleton::try_from("MMMMdEEEE").unwrap(),
Skeleton(
vec![
Field {
symbol: Month::Format.into(),
length: FieldLength::Wide
},
Field {
symbol: Day::DayOfMonth.into(),
length: FieldLength::One
},
Field {
symbol: Weekday::Format.into(),
length: FieldLength::Wide
},
]
.into()
)
);
}
#[test]
fn test_skeleton_tuple_ordering() {
let skeletons_strings = Vec::from([
"y", "yM", "yMdE", "yMdEEEE", "yMMM", "M", "Md", "Mdd", "MMd", "MMdd", "d", "h", "hm",
"hms", "Hm", "Hms", "ms", "mmss",
]);
let skeleton_fields: Vec<Skeleton> = skeletons_strings
.iter()
.map(|skeleton_string| Skeleton::try_from(*skeleton_string).unwrap())
.collect();
for (strings, fields) in skeletons_strings.windows(2).zip(skeleton_fields.windows(2)) {
if fields[0].cmp(&fields[1]) != std::cmp::Ordering::Less {
panic!("Expected {:?} < {:?}", strings[0], strings[1]);
}
}
}
#[test]
fn test_skeleton_json_reordering() {
let unordered_skeleton = "EEEEyMd";
let ordered_skeleton = "yMdEEEE";
let json: String = serde_json::to_string(unordered_skeleton).unwrap();
let skeleton = serde_json::from_str::<Skeleton>(&json)
.expect("Unable to parse an unordered skeletons.");
assert_eq!(
serde_json::to_string(&skeleton).unwrap(),
serde_json::to_string(ordered_skeleton).unwrap()
);
}
#[test]
fn test_skeleton_json_duplicate_fields() {
let json: String = serde_json::to_string("EEEEyMdEEEE").unwrap();
let err =
serde_json::from_str::<Skeleton>(&json).expect_err("Expected a duplicate field error.");
assert_eq!(
format!("{}", err),
"invalid value: \"EEEEyMdEEEE\" duplicate field in skeleton, expected field symbols representing a skeleton at line 1 column 13"
);
}
struct TestInvalidSkeleton(Vec<Field>);
#[cfg(feature = "provider_serde")]
impl Serialize for TestInvalidSkeleton {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let fields = &self.0;
let mut seq = serializer.serialize_seq(Some(fields.len()))?;
for item in fields.iter() {
seq.serialize_element(item)?;
}
seq.end()
}
}
#[test]
fn test_skeleton_bincode_reordering() {
let unordered_skeleton = TestInvalidSkeleton(vec![
Field::from((FieldSymbol::Day(Day::DayOfMonth), FieldLength::One)),
Field::from((FieldSymbol::Month(Month::Format), FieldLength::One)),
]);
let mut buffer: Vec<u8> = Vec::new();
bincode::serialize_into(&mut buffer, &unordered_skeleton).unwrap();
let err =
bincode::deserialize::<Skeleton>(&buffer).expect_err("Expected an unordered error");
assert_eq!(
format!("{}", err),
"invalid value: field item out of order or duplicate: Field { symbol: Month(Format), length: One }, expected ordered field symbols representing a skeleton"
);
}
#[test]
fn test_skeleton_bincode_duplicate_field() {
let unordered_skeleton = TestInvalidSkeleton(vec![
Field::from((FieldSymbol::Month(Month::Format), FieldLength::One)),
Field::from((FieldSymbol::Day(Day::DayOfMonth), FieldLength::One)),
Field::from((FieldSymbol::Day(Day::DayOfMonth), FieldLength::One)),
]);
let mut buffer: Vec<u8> = Vec::new();
bincode::serialize_into(&mut buffer, &unordered_skeleton).unwrap();
let err = bincode::deserialize::<Skeleton>(&buffer)
.expect_err("Expected a duplicate field error");
assert_eq!(
format!("{}", err),
"invalid value: field item out of order or duplicate: Field { symbol: Day(DayOfMonth), length: One }, expected ordered field symbols representing a skeleton"
);
}
}