mod error;
mod helpers;
pub mod reference;
pub mod runtime;
#[cfg(feature = "serde")]
mod serde;
pub use error::*;
pub use helpers::*;
#[cfg(all(test, feature = "datagen", feature = "experimental"))]
#[cfg(feature = "compiled_data")]
mod test {
use super::reference::Skeleton;
use super::*;
use icu_provider::prelude::*;
use crate::{
fields::{Day, Field, FieldLength, Month, Weekday},
options::{components, preferences},
pattern::runtime,
provider::calendar::{
DateSkeletonPatternsV1, DateSkeletonPatternsV1Marker, GregorianDateLengthsV1Marker,
SkeletonV1,
},
};
use core::convert::TryFrom;
use core::str::FromStr;
use litemap::LiteMap;
fn get_data_payload() -> (
DataPayload<GregorianDateLengthsV1Marker>,
DataPayload<DateSkeletonPatternsV1Marker>,
) {
let locale = "en-u-ca-gregory".parse().unwrap();
let req = DataRequest {
locale: &locale,
metadata: Default::default(),
};
let patterns = crate::provider::Baked
.load(req)
.expect("Failed to load payload")
.take_payload()
.expect("Failed to retrieve payload");
let skeletons = crate::provider::Baked
.load(req)
.expect("Failed to load payload")
.take_payload()
.expect("Failed to retrieve payload");
(patterns, skeletons)
}
#[test]
fn test_skeleton_matching() {
let components = components::Bag {
year: Some(components::Year::Numeric),
month: Some(components::Month::Long),
day: Some(components::Day::NumericDayOfMonth),
hour: Some(components::Numeric::Numeric),
minute: Some(components::Numeric::Numeric),
second: Some(components::Numeric::Numeric),
..Default::default()
};
let requested_fields = components.to_vec_fields(preferences::HourCycle::H23);
let (_, skeletons) = get_data_payload();
match get_best_available_format_pattern(skeletons.get(), &requested_fields, false) {
BestSkeleton::AllFieldsMatch(available_format_pattern)
| BestSkeleton::MissingOrExtraFields(available_format_pattern) => {
assert_eq!(
available_format_pattern
.expect_pattern("pattern should not have plural variants")
.to_string()
.as_str(),
"H:m:s"
)
}
BestSkeleton::NoMatch => {
panic!("No skeleton was found.")
}
};
}
#[test]
fn test_skeleton_matching_missing_fields() {
let components = components::Bag {
week: Some(components::Week::TwoDigitWeekOfYear),
weekday: Some(components::Text::Short),
..Default::default()
};
let requested_fields = components.to_vec_fields(preferences::HourCycle::H23);
let (_, skeletons) = get_data_payload();
match get_best_available_format_pattern(skeletons.get(), &requested_fields, false) {
BestSkeleton::MissingOrExtraFields(available_format_pattern) => {
assert_eq!(
available_format_pattern
.expect_pattern("pattern should not have plural variants")
.to_string()
.as_str(),
"ccc"
)
}
best => panic!("Unexpected {best:?}"),
};
}
#[test]
#[should_panic]
fn test_missing_append_items_support() {
let components = components::Bag {
year: Some(components::Year::Numeric),
month: Some(components::Month::Long),
day: Some(components::Day::NumericDayOfMonth),
time_zone_name: Some(components::TimeZoneName::LongSpecific),
..Default::default()
};
let requested_fields = components.to_vec_fields(preferences::HourCycle::H23);
let (patterns, skeletons) = get_data_payload();
match create_best_pattern_for_fields(
skeletons.get(),
&patterns.get().length_combinations,
&requested_fields,
&Default::default(),
false,
) {
BestSkeleton::AllFieldsMatch(available_format_pattern) => {
assert_eq!(
available_format_pattern
.expect_pattern("pattern should not have plural variants")
.to_string()
.as_str(),
"MMMM d, y vvvv"
)
}
best => panic!("Unexpected {best:?}"),
};
}
#[test]
fn test_skeleton_empty_bag() {
let components: components::Bag = Default::default();
let requested_fields = components.to_vec_fields(preferences::HourCycle::H23);
let (_, skeletons) = get_data_payload();
assert_eq!(
get_best_available_format_pattern(skeletons.get(), &requested_fields, false),
BestSkeleton::NoMatch,
"No match was found"
);
}
#[test]
fn test_skeleton_no_match() {
let components = components::Bag {
hour: Some(components::Numeric::Numeric),
time_zone_name: Some(components::TimeZoneName::LongSpecific),
..Default::default()
};
let requested_fields = components.to_vec_fields(preferences::HourCycle::H23);
let mut skeletons = LiteMap::new();
skeletons.insert(
SkeletonV1::try_from("EEEE").unwrap(),
runtime::Pattern::from_str("weekday EEEE").unwrap().into(),
);
let skeletons = DateSkeletonPatternsV1(skeletons);
assert_eq!(
get_best_available_format_pattern(&skeletons, &requested_fields, false),
BestSkeleton::NoMatch,
"No match was found"
);
}
#[rustfmt::skip]
const SUPPORTED_STRING_SKELETONS: &[&str] = &[
"E", "dEEEE", "EHm", "EHms", "dE", "Ehm", "Ehms", "H", "HHmm", "HHmmss", "Hm", "Hms", "M",
"MdEEEE", "MdE", "MMM", "MMMdEEEE", "MMMdE", "MMMM", "MMMMW",
"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", "yw",
"Gy", "GyM", "GyMMM", "GyMMMdEEEE", "GyMMMdE", "GyMMMM", "GyMMMMdE", "GyMMMMd", "GyMMMd",
"HHmmZ", "Hmsv", "Hmsvvvv", "Hmv", "Hmvvvv", "hmsv", "hmsvvvv", "hmv", "hmvvvv",
];
#[rustfmt::skip]
const UNSUPPORTED_STRING_SKELETONS: &[&str] = &[
"Bh", "Bhm", "Bhms", "EBhm", "EBhms",
"yQ", "yQQQ", "yQQQQ",
];
#[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 {string_skeleton:?} with error, {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 {string_skeleton:?} to the \
supported skeletons, and ensure the skeleton is properly implemented."
)
}
Err(err) => match err {
SkeletonError::SymbolUnimplemented(_) => {
}
_ => panic!("{err}"),
},
}
}
}
#[test]
fn test_skeleton_deserialization() {
assert_eq!(
Skeleton::try_from("MMMMdEEEE").unwrap(),
Skeleton::from(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
},
])
);
}
#[test]
fn test_skeleton_tuple_ordering() {
let skeletons_strings = Vec::from([
"y", "yM", "yMdE", "yMdEEEE", "yMMM", "yw", "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]) != core::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"
);
}
#[test]
fn test_skeleton_matching_weekday_short() {
let components = components::Bag {
weekday: Some(components::Text::Short),
..Default::default()
};
let default_hour_cycle = preferences::HourCycle::H23;
let requested_fields = components.to_vec_fields(default_hour_cycle);
let (_, skeletons) = get_data_payload();
match get_best_available_format_pattern(skeletons.get(), &requested_fields, false) {
BestSkeleton::AllFieldsMatch(available_format_pattern) => {
assert_eq!(
available_format_pattern
.expect_pattern("pattern should not have plural variants")
.to_string()
.as_str(),
"ccc"
)
}
best => panic!("Unexpected {best:?}"),
};
}
#[test]
fn test_skeleton_matching_weekday_long() {
let components = components::Bag {
weekday: Some(components::Text::Long),
..Default::default()
};
let default_hour_cycle = preferences::HourCycle::H23;
let requested_fields = components.to_vec_fields(default_hour_cycle);
let (_, skeletons) = get_data_payload();
match get_best_available_format_pattern(skeletons.get(), &requested_fields, false) {
BestSkeleton::AllFieldsMatch(available_format_pattern) => {
assert_eq!(
available_format_pattern
.expect_pattern("pattern should not have plural variants")
.to_string()
.as_str(),
"cccc"
)
}
best => panic!("Unexpected {best:?}"),
};
}
#[cfg(feature = "datagen")]
fn assert_pattern_to_skeleton(pattern: &str, skeleton: &str, message: &str) {
assert_eq!(
serde_json::to_string(skeleton).expect("Failed to transform skeleton to string."),
serde_json::to_string(&Skeleton::from(
&pattern
.parse::<crate::pattern::reference::Pattern>()
.expect("Failed to create pattern from bytes.")
))
.expect("Failed to transform skeleton to string."),
"{message}"
);
}
#[test]
#[cfg(feature = "datagen")]
fn test_pattern_to_skeleton() {
assert_pattern_to_skeleton("H:mm:ss v", "Hmmssv", "Test a complicated time pattern");
assert_pattern_to_skeleton(
"v ss:mm:H",
"Hmmssv",
"Test the skeleton ordering is consistent",
);
assert_pattern_to_skeleton("K:mm", "hmm", "H11 maps to H12");
assert_pattern_to_skeleton("k:mm", "Hmm", "H23 maps to H24");
assert_pattern_to_skeleton("ha mm", "hmm", "Day periods get removed");
assert_pattern_to_skeleton("h 'at' b mm", "hmm", "Day periods get removed");
assert_pattern_to_skeleton("y", "y", "The year is passed through");
assert_pattern_to_skeleton("Y", "Y", "The year is passed through");
assert_pattern_to_skeleton("LLL", "MMM", "Remove standalone months.");
assert_pattern_to_skeleton("s", "s", "Seconds pass through");
assert_pattern_to_skeleton("S", "S", "Seconds pass through");
assert_pattern_to_skeleton("A", "A", "Seconds pass through");
assert_pattern_to_skeleton("z", "z", "Time zones get passed through");
assert_pattern_to_skeleton("Z", "Z", "Time zones get passed through");
assert_pattern_to_skeleton("O", "O", "Time zones get passed through");
assert_pattern_to_skeleton("v", "v", "Time zones get passed through");
assert_pattern_to_skeleton("V", "V", "Time zones get passed through");
assert_pattern_to_skeleton("X", "X", "Time zones get passed through");
assert_pattern_to_skeleton("x", "x", "Time zones get passed through");
}
}