diff --git a/components/experimental/src/measure/mixed_measureunit.rs b/components/experimental/src/measure/mixed_measureunit.rs new file mode 100644 index 00000000000..68289051d37 --- /dev/null +++ b/components/experimental/src/measure/mixed_measureunit.rs @@ -0,0 +1,134 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use crate::measure::{measureunit::MeasureUnit, parser::InvalidUnitError}; + +use super::single_unit_vec::SingleUnitVec; + +/// The [`MixedMeasureUnit`] struct represents a CLDR mixed unit, +/// which is a combination of one or more single units used together to express a measurement. +/// +/// # Examples +/// - `foot-and-inch` +/// - `kilometer-and-meter` +/// - `meter` - a special case of a mixed unit that contains only one single unit. +/// +/// Note: Compound units (e.g. `meter-per-second`) or units with a constant denominator (e.g. `liter-per-100-kilometer`) are not supported in mixed units. +#[derive(Debug, Eq, Clone, PartialEq)] +pub struct MixedMeasureUnit { + /// Contains the single mixed units. + pub(crate) mixed_units: SingleUnitVec, +} + +impl MixedMeasureUnit { + /// Returns a slice of the mixed units contained within this mixed unit. + pub fn try_from_str(mixed_units_str: &str) -> Result { + // '-and-' is the separator for the mixed units and it is allowed to appear in the start or end of the string. + let mixed_units_strs = mixed_units_str.split("-and-"); + let mut mixed_units = SingleUnitVec::Zero; + for unit in mixed_units_strs { + let unit = MeasureUnit::try_from_str(unit)?; + let internal_single_units = unit.single_units(); + if internal_single_units.len() > 1 { + return Err(InvalidUnitError); + } + if unit.constant_denominator != 0 { + return Err(InvalidUnitError); + } + let single_unit = match internal_single_units.first() { + Some(single_unit) => single_unit, + None => return Err(InvalidUnitError), + }; + mixed_units.push(*single_unit); + } + Ok(MixedMeasureUnit { mixed_units }) + } +} + +#[cfg(test)] +mod tests { + use crate::measure::provider::{ + si_prefix::{Base, SiPrefix}, + single_unit::SingleUnit, + }; + + use super::*; + + #[test] + fn test_mixed_measure_unit_from_str() { + // Meter + let mixed_measure_unit = MixedMeasureUnit::try_from_str("meter").unwrap(); + assert_eq!(mixed_measure_unit.mixed_units.as_slice().len(), 1); + assert_eq!( + mixed_measure_unit.mixed_units.as_slice()[0], + SingleUnit { + power: 1, + si_prefix: SiPrefix { + power: 0, + base: Base::Decimal, + }, + unit_id: *crate::provider::Baked::UNIT_IDS_V1_UND_METER, + } + ); + + // Foot and Inch + let mixed_measure_unit = MixedMeasureUnit::try_from_str("foot-and-inch").unwrap(); + assert_eq!(mixed_measure_unit.mixed_units.as_slice().len(), 2); + assert_eq!( + mixed_measure_unit.mixed_units.as_slice()[0], + SingleUnit { + power: 1, + si_prefix: SiPrefix { + power: 0, + base: Base::Decimal, + }, + unit_id: *crate::provider::Baked::UNIT_IDS_V1_UND_FOOT, + } + ); + assert_eq!( + mixed_measure_unit.mixed_units.as_slice()[1], + SingleUnit { + power: 1, + si_prefix: SiPrefix { + power: 0, + base: Base::Decimal, + }, + unit_id: *crate::provider::Baked::UNIT_IDS_V1_UND_INCH, + } + ); + + // Kilometer and Meter + let mixed_measure_unit = MixedMeasureUnit::try_from_str("kilometer-and-meter").unwrap(); + assert_eq!(mixed_measure_unit.mixed_units.as_slice().len(), 2); + assert_eq!( + mixed_measure_unit.mixed_units.as_slice()[0], + SingleUnit { + power: 1, + si_prefix: SiPrefix { + power: 3, + base: Base::Decimal, + }, + unit_id: *crate::provider::Baked::UNIT_IDS_V1_UND_METER, + } + ); + assert_eq!( + mixed_measure_unit.mixed_units.as_slice()[1], + SingleUnit { + power: 1, + si_prefix: SiPrefix { + power: 0, + base: Base::Decimal, + }, + unit_id: *crate::provider::Baked::UNIT_IDS_V1_UND_METER, + } + ); + } + + #[test] + fn test_invalid_mixed_measure_unit_from_str() { + let mixed_measure_unit = + MixedMeasureUnit::try_from_str("meter-per-second-and-mile-per-hour"); + assert!(mixed_measure_unit.is_err()); + } +} diff --git a/components/experimental/src/measure/mod.rs b/components/experimental/src/measure/mod.rs index 72ebe50d1fe..f6e452be6ac 100644 --- a/components/experimental/src/measure/mod.rs +++ b/components/experimental/src/measure/mod.rs @@ -4,6 +4,7 @@ pub mod category; pub mod measureunit; +pub mod mixed_measureunit; pub mod parser; pub mod provider; pub mod single_unit_vec;