use super::datetime::{try_write_pattern, DateTimeWriteError};
use crate::calendar::CldrCalendar;
use crate::external_loaders::*;
use crate::fields::{self, Field, FieldLength, FieldSymbol};
use crate::helpers::size_test;
use crate::input;
use crate::input::DateInput;
use crate::input::DateTimeInput;
use crate::input::ExtractedDateTimeInput;
use crate::input::IsoTimeInput;
use crate::neo_pattern::{DateTimePattern, DateTimePatternBorrowed};
use crate::pattern::PatternItem;
use crate::provider::date_time::{
DateSymbols, GetSymbolForDayPeriodError, GetSymbolForEraError, GetSymbolForMonthError,
GetSymbolForWeekdayError, MonthPlaceholderValue, TimeSymbols,
};
use crate::provider::neo::*;
use core::fmt;
use core::marker::PhantomData;
use icu_calendar::provider::WeekDataV2Marker;
use icu_calendar::types::Era;
use icu_calendar::types::MonthCode;
use icu_calendar::week::WeekCalculator;
use icu_decimal::options::FixedDecimalFormatterOptions;
use icu_decimal::options::GroupingStrategy;
use icu_decimal::provider::DecimalSymbolsV1Marker;
use icu_decimal::FixedDecimalFormatter;
use icu_provider::{prelude::*, NeverMarker};
use writeable::TryWriteable;
use yoke::Yokeable;
#[derive(Debug, Copy, Clone)]
enum OptionalNames<S, T> {
None,
SingleLength(S, FieldLength, T),
}
enum NamePresence {
NotLoaded,
Loaded,
Mismatched,
}
impl<S, T> OptionalNames<S, T>
where
S: Copy + PartialEq,
{
pub(crate) fn check_with_length(
&self,
field_symbol: S,
field_length: FieldLength,
) -> NamePresence {
match self {
Self::SingleLength(actual_field_symbol, actual_length, _)
if field_symbol == *actual_field_symbol && field_length == *actual_length =>
{
NamePresence::Loaded
}
OptionalNames::SingleLength(_, _, _) => NamePresence::Mismatched,
OptionalNames::None => NamePresence::NotLoaded,
}
}
}
impl<S, T> OptionalNames<S, T>
where
S: Copy + PartialEq,
T: Copy,
{
pub(crate) fn get_with_length(&self, field_symbol: S, field_length: FieldLength) -> Option<T> {
match self {
Self::None => None,
Self::SingleLength(actual_field_symbol, actual_length, t)
if field_symbol == *actual_field_symbol && field_length == *actual_length =>
{
Some(*t)
}
_ => None,
}
}
}
impl<S, T> OptionalNames<S, T>
where
S: Copy,
{
pub(crate) fn as_borrowed<'a, Y>(&'a self) -> OptionalNames<S, &'a <Y as Yokeable<'a>>::Output>
where
T: MaybePayload<Y>,
Y: for<'y> Yokeable<'y>,
{
match self {
Self::None => OptionalNames::None,
Self::SingleLength(field_symbol, field_length, payload) => match payload.maybe_get() {
Some(data) => OptionalNames::SingleLength(*field_symbol, *field_length, data),
None => OptionalNames::None,
},
}
}
}
pub(crate) struct PhantomProvider;
impl<M: KeyedDataMarker> DataProvider<M> for PhantomProvider {
#[inline]
fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
debug_assert!(false);
Err(DataErrorKind::MissingDataKey.with_req(M::KEY, req))
}
}
impl<M: DataMarker> BoundDataProvider<M> for PhantomProvider {
#[inline]
fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
debug_assert!(false);
let key = BoundDataProvider::<M>::bound_key(self);
Err(DataErrorKind::MissingDataKey
.into_error()
.with_req(key, req))
}
#[inline]
fn bound_key(&self) -> DataKey {
NeverMarker::<M::Yokeable>::KEY
}
}
size_test!(
TypedDateTimeNames<icu_calendar::Gregorian, DateTimeMarker>,
typed_date_time_names_size,
488
);
#[doc = typed_date_time_names_size!()]
#[derive(Debug)]
pub struct TypedDateTimeNames<C: CldrCalendar, R: DateTimeNamesMarker = DateTimeMarker> {
locale: DataLocale,
inner: RawDateTimeNames<R>,
_calendar: PhantomData<C>,
}
pub trait DateTimeNamesMarker {
type YearNames: MaybePayload<YearNamesV1<'static>> + fmt::Debug;
type MonthNames: MaybePayload<MonthNamesV1<'static>> + fmt::Debug;
type WeekdayNames: MaybePayload<LinearNamesV1<'static>> + fmt::Debug;
type DayPeriodNames: MaybePayload<LinearNamesV1<'static>> + fmt::Debug;
}
pub trait MaybePayload<Y: for<'a> Yokeable<'a>> {
fn maybe_from_payload<M>(payload: DataPayload<M>) -> Option<Self>
where
M: DataMarker<Yokeable = Y>,
Self: Sized;
fn load_from<P, M>(provider: &P, req: DataRequest) -> Option<Result<Self, DataError>>
where
M: DataMarker<Yokeable = Y>,
P: BoundDataProvider<M> + ?Sized,
Self: Sized;
#[allow(clippy::needless_lifetimes)] fn maybe_get<'a>(&'a self) -> Option<&'a <Y as Yokeable<'a>>::Output>;
}
impl<M0, Y: for<'a> Yokeable<'a>> MaybePayload<Y> for DataPayload<M0>
where
M0: DataMarker<Yokeable = Y>,
{
#[inline]
fn maybe_from_payload<M>(payload: DataPayload<M>) -> Option<Self>
where
M: DataMarker<Yokeable = Y>,
{
Some(payload.cast())
}
#[inline]
fn load_from<P, M>(provider: &P, req: DataRequest) -> Option<Result<Self, DataError>>
where
M: DataMarker<Yokeable = Y>,
P: BoundDataProvider<M> + ?Sized,
Self: Sized,
{
Some(
provider
.load_bound(req)
.and_then(DataResponse::take_payload)
.map(DataPayload::cast),
)
}
#[allow(clippy::needless_lifetimes)] #[inline]
fn maybe_get<'a>(&'a self) -> Option<&'a <M0::Yokeable as Yokeable<'a>>::Output> {
Some(self.get())
}
}
impl<Y: for<'a> Yokeable<'a>> MaybePayload<Y> for () {
#[inline]
fn maybe_from_payload<M>(_payload: DataPayload<M>) -> Option<Self>
where
M: DataMarker<Yokeable = Y>,
{
None
}
#[inline]
fn load_from<P, M>(_provider: &P, _req: DataRequest) -> Option<Result<Self, DataError>>
where
M: DataMarker<Yokeable = Y>,
P: BoundDataProvider<M> + ?Sized,
Self: Sized,
{
None
}
#[allow(clippy::needless_lifetimes)] #[inline]
fn maybe_get<'a>(&'a self) -> Option<&'a <Y as Yokeable<'a>>::Output> {
None
}
}
#[derive(Debug)]
pub struct DateMarker {}
impl DateTimeNamesMarker for DateMarker {
type YearNames = DataPayload<YearNamesV1Marker>;
type MonthNames = DataPayload<MonthNamesV1Marker>;
type WeekdayNames = DataPayload<WeekdayNamesV1Marker>;
type DayPeriodNames = ();
}
#[derive(Debug)]
pub struct TimeMarker {}
impl DateTimeNamesMarker for TimeMarker {
type YearNames = ();
type MonthNames = ();
type WeekdayNames = ();
type DayPeriodNames = DataPayload<DayPeriodNamesV1Marker>;
}
#[derive(Debug)]
pub struct DateTimeMarker {}
impl DateTimeNamesMarker for DateTimeMarker {
type YearNames = DataPayload<YearNamesV1Marker>;
type MonthNames = DataPayload<MonthNamesV1Marker>;
type WeekdayNames = DataPayload<WeekdayNamesV1Marker>;
type DayPeriodNames = DataPayload<DayPeriodNamesV1Marker>;
}
impl From<RawDateTimeNames<DateMarker>> for RawDateTimeNames<DateTimeMarker> {
fn from(other: RawDateTimeNames<DateMarker>) -> Self {
Self {
year_symbols: other.year_symbols,
month_symbols: other.month_symbols,
weekday_symbols: other.weekday_symbols,
dayperiod_symbols: OptionalNames::None,
fixed_decimal_formatter: other.fixed_decimal_formatter,
week_calculator: other.week_calculator,
_marker: PhantomData,
}
}
}
impl From<RawDateTimeNames<TimeMarker>> for RawDateTimeNames<DateTimeMarker> {
fn from(other: RawDateTimeNames<TimeMarker>) -> Self {
Self {
year_symbols: OptionalNames::None,
month_symbols: OptionalNames::None,
weekday_symbols: OptionalNames::None,
dayperiod_symbols: other.dayperiod_symbols,
fixed_decimal_formatter: other.fixed_decimal_formatter,
week_calculator: other.week_calculator,
_marker: PhantomData,
}
}
}
#[derive(Debug)]
pub(crate) struct RawDateTimeNames<R: DateTimeNamesMarker> {
year_symbols: OptionalNames<(), R::YearNames>,
month_symbols: OptionalNames<fields::Month, R::MonthNames>,
weekday_symbols: OptionalNames<fields::Weekday, R::WeekdayNames>,
dayperiod_symbols: OptionalNames<(), R::DayPeriodNames>,
fixed_decimal_formatter: Option<FixedDecimalFormatter>,
week_calculator: Option<WeekCalculator>,
_marker: PhantomData<R>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct RawDateTimeNamesBorrowed<'l> {
year_names: OptionalNames<(), &'l YearNamesV1<'l>>,
month_names: OptionalNames<fields::Month, &'l MonthNamesV1<'l>>,
weekday_names: OptionalNames<fields::Weekday, &'l LinearNamesV1<'l>>,
dayperiod_names: OptionalNames<(), &'l LinearNamesV1<'l>>,
pub(crate) fixed_decimal_formatter: Option<&'l FixedDecimalFormatter>,
pub(crate) week_calculator: Option<&'l WeekCalculator>,
}
impl<C: CldrCalendar, R: DateTimeNamesMarker> TypedDateTimeNames<C, R> {
#[cfg(feature = "compiled_data")]
pub fn try_new(locale: &DataLocale) -> Result<Self, DataError> {
let mut names = Self {
locale: locale.clone(),
inner: RawDateTimeNames::new_without_fixed_decimal_formatter(),
_calendar: PhantomData,
};
names.include_fixed_decimal_formatter()?;
Ok(names)
}
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
pub fn try_new_unstable<P>(provider: &P, locale: &DataLocale) -> Result<Self, DataError>
where
P: DataProvider<DecimalSymbolsV1Marker> + ?Sized,
{
let mut names = Self {
locale: locale.clone(),
inner: RawDateTimeNames::new_without_fixed_decimal_formatter(),
_calendar: PhantomData,
};
names.load_fixed_decimal_formatter(provider)?;
Ok(names)
}
pub fn load_year_names<P>(
&mut self,
provider: &P,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
P: DataProvider<C::YearNamesV1Marker> + ?Sized,
{
self.inner.load_year_names(
&C::YearNamesV1Marker::bind(provider),
&self.locale,
field_length,
)?;
Ok(self)
}
#[cfg(feature = "compiled_data")]
pub fn include_year_names(
&mut self,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
crate::provider::Baked: icu_provider::DataProvider<<C as CldrCalendar>::YearNamesV1Marker>,
{
self.load_year_names(&crate::provider::Baked, field_length)
}
pub fn load_month_names<P>(
&mut self,
provider: &P,
field_symbol: fields::Month,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
P: DataProvider<C::MonthNamesV1Marker> + ?Sized,
{
self.inner.load_month_names(
&C::MonthNamesV1Marker::bind(provider),
&self.locale,
field_symbol,
field_length,
)?;
Ok(self)
}
#[cfg(feature = "compiled_data")]
pub fn include_month_names(
&mut self,
field_symbol: fields::Month,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
crate::provider::Baked: icu_provider::DataProvider<<C as CldrCalendar>::MonthNamesV1Marker>,
{
self.load_month_names(&crate::provider::Baked, field_symbol, field_length)
}
pub fn load_day_period_names<P>(
&mut self,
provider: &P,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
P: DataProvider<DayPeriodNamesV1Marker> + ?Sized,
{
let provider = DayPeriodNamesV1Marker::bind(provider);
self.inner
.load_day_period_names(&provider, &self.locale, field_length)?;
Ok(self)
}
#[cfg(feature = "compiled_data")]
pub fn include_day_period_names(
&mut self,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
crate::provider::Baked: icu_provider::DataProvider<DayPeriodNamesV1Marker>,
{
self.load_day_period_names(&crate::provider::Baked, field_length)
}
pub fn load_weekday_names<P>(
&mut self,
provider: &P,
field_symbol: fields::Weekday,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
P: DataProvider<WeekdayNamesV1Marker> + ?Sized,
{
self.inner.load_weekday_names(
&WeekdayNamesV1Marker::bind(provider),
&self.locale,
field_symbol,
field_length,
)?;
Ok(self)
}
#[cfg(feature = "compiled_data")]
pub fn include_weekday_names(
&mut self,
field_symbol: fields::Weekday,
field_length: FieldLength,
) -> Result<&mut Self, SingleLoadError>
where
crate::provider::Baked: icu_provider::DataProvider<WeekdayNamesV1Marker>,
{
self.load_weekday_names(&crate::provider::Baked, field_symbol, field_length)
}
#[inline]
pub fn set_week_calculator(&mut self, week_calculator: WeekCalculator) -> &mut Self {
self.inner.set_week_calculator(week_calculator);
self
}
#[inline]
fn load_fixed_decimal_formatter<P>(&mut self, provider: &P) -> Result<&mut Self, DataError>
where
P: DataProvider<DecimalSymbolsV1Marker> + ?Sized,
{
self.inner
.load_fixed_decimal_formatter(&ExternalLoaderUnstable(provider), &self.locale)?;
Ok(self)
}
#[cfg(feature = "compiled_data")]
#[inline]
fn include_fixed_decimal_formatter(&mut self) -> Result<&mut Self, DataError> {
self.inner
.load_fixed_decimal_formatter(&ExternalLoaderCompiledData, &self.locale)?;
Ok(self)
}
#[inline]
pub fn with_pattern<'l>(
&'l self,
pattern: &'l DateTimePattern,
) -> DateTimePatternFormatter<'l, C> {
DateTimePatternFormatter {
inner: self.inner.with_pattern(pattern.as_borrowed()),
_calendar: PhantomData,
}
}
pub fn load_for_pattern<'l, P>(
&'l mut self,
provider: &P,
pattern: &'l DateTimePattern,
) -> Result<DateTimePatternFormatter<'l, C>, LoadError>
where
P: DataProvider<C::YearNamesV1Marker>
+ DataProvider<C::MonthNamesV1Marker>
+ DataProvider<WeekdayNamesV1Marker>
+ DataProvider<DayPeriodNamesV1Marker>
+ DataProvider<DecimalSymbolsV1Marker>
+ DataProvider<WeekDataV2Marker>
+ ?Sized,
{
let locale = &self.locale;
self.inner.load_for_pattern(
&C::YearNamesV1Marker::bind(provider),
&C::MonthNamesV1Marker::bind(provider),
&WeekdayNamesV1Marker::bind(provider),
&DayPeriodNamesV1Marker::bind(provider),
Some(&ExternalLoaderUnstable(provider)),
Some(&ExternalLoaderUnstable(provider)),
locale,
pattern.iter_items(),
)?;
Ok(DateTimePatternFormatter {
inner: self.inner.with_pattern(pattern.as_borrowed()),
_calendar: PhantomData,
})
}
#[cfg(feature = "compiled_data")]
pub fn include_for_pattern<'l>(
&'l mut self,
pattern: &'l DateTimePattern,
) -> Result<DateTimePatternFormatter<'l, C>, LoadError>
where
crate::provider::Baked: DataProvider<C::YearNamesV1Marker>
+ DataProvider<C::MonthNamesV1Marker>
+ DataProvider<WeekdayNamesV1Marker>
+ DataProvider<DayPeriodNamesV1Marker>,
{
let locale = &self.locale;
self.inner.load_for_pattern(
&C::YearNamesV1Marker::bind(&crate::provider::Baked),
&C::MonthNamesV1Marker::bind(&crate::provider::Baked),
&WeekdayNamesV1Marker::bind(&crate::provider::Baked),
&DayPeriodNamesV1Marker::bind(&crate::provider::Baked),
Some(&ExternalLoaderCompiledData),
Some(&ExternalLoaderCompiledData),
locale,
pattern.iter_items(),
)?;
Ok(DateTimePatternFormatter {
inner: self.inner.with_pattern(pattern.as_borrowed()),
_calendar: PhantomData,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, displaydoc::Display)]
#[non_exhaustive]
pub enum SingleLoadError {
DuplicateField(Field),
UnsupportedField(Field),
TypeTooNarrow(Field),
Data(DataError),
}
#[derive(Debug, Clone, Copy, PartialEq, displaydoc::Display)]
#[non_exhaustive]
pub enum LoadError {
DuplicateField(Field),
UnsupportedField(Field),
TypeTooNarrow(Field),
Data(DataError),
MissingNames(Field),
}
impl From<SingleLoadError> for LoadError {
fn from(value: SingleLoadError) -> Self {
match value {
SingleLoadError::Data(e) => LoadError::Data(e),
SingleLoadError::UnsupportedField(f) => LoadError::UnsupportedField(f),
SingleLoadError::TypeTooNarrow(f) => LoadError::TypeTooNarrow(f),
SingleLoadError::DuplicateField(f) => LoadError::DuplicateField(f),
}
}
}
impl<R: DateTimeNamesMarker> RawDateTimeNames<R> {
pub(crate) fn new_without_fixed_decimal_formatter() -> Self {
Self {
year_symbols: OptionalNames::None,
month_symbols: OptionalNames::None,
weekday_symbols: OptionalNames::None,
dayperiod_symbols: OptionalNames::None,
fixed_decimal_formatter: None,
week_calculator: None,
_marker: PhantomData,
}
}
pub(crate) fn as_borrowed(&self) -> RawDateTimeNamesBorrowed {
RawDateTimeNamesBorrowed {
year_names: self.year_symbols.as_borrowed(),
month_names: self.month_symbols.as_borrowed(),
weekday_names: self.weekday_symbols.as_borrowed(),
dayperiod_names: self.dayperiod_symbols.as_borrowed(),
fixed_decimal_formatter: self.fixed_decimal_formatter.as_ref(),
week_calculator: self.week_calculator.as_ref(),
}
}
pub(crate) fn load_year_names<P>(
&mut self,
provider: &P,
locale: &DataLocale,
field_length: FieldLength,
) -> Result<(), SingleLoadError>
where
P: BoundDataProvider<YearNamesV1Marker> + ?Sized,
{
let field = fields::Field {
symbol: FieldSymbol::Era,
length: field_length,
};
let field_length = field_length.numeric_to_abbr();
match self.year_symbols.check_with_length((), field_length) {
NamePresence::Loaded => return Ok(()),
NamePresence::NotLoaded => (),
NamePresence::Mismatched => return Err(SingleLoadError::DuplicateField(field)),
};
let mut locale = locale.clone();
locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for(
aux::Context::Format,
match field_length {
FieldLength::Abbreviated => aux::Length::Abbr,
FieldLength::Narrow => aux::Length::Narrow,
FieldLength::Wide => aux::Length::Wide,
_ => return Err(SingleLoadError::UnsupportedField(field)),
},
)));
let payload = provider
.load_bound(DataRequest {
locale: &locale,
metadata: Default::default(),
})
.and_then(DataResponse::take_payload)
.map_err(SingleLoadError::Data)?;
self.year_symbols = OptionalNames::SingleLength(
(),
field_length,
R::YearNames::maybe_from_payload(payload)
.ok_or(SingleLoadError::TypeTooNarrow(field))?,
);
Ok(())
}
pub(crate) fn load_month_names<P>(
&mut self,
provider: &P,
locale: &DataLocale,
field_symbol: fields::Month,
field_length: FieldLength,
) -> Result<(), SingleLoadError>
where
P: BoundDataProvider<MonthNamesV1Marker> + ?Sized,
{
let field = fields::Field {
symbol: FieldSymbol::Month(field_symbol),
length: field_length,
};
match self
.month_symbols
.check_with_length(field_symbol, field_length)
{
NamePresence::Loaded => return Ok(()),
NamePresence::NotLoaded => (),
NamePresence::Mismatched => return Err(SingleLoadError::DuplicateField(field)),
};
let mut locale = locale.clone();
locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for(
match field_symbol {
fields::Month::Format => aux::Context::Format,
fields::Month::StandAlone => aux::Context::Standalone,
},
match field_length {
FieldLength::Abbreviated => aux::Length::Abbr,
FieldLength::Narrow => aux::Length::Narrow,
FieldLength::Wide => aux::Length::Wide,
_ => return Err(SingleLoadError::UnsupportedField(field)),
},
)));
let payload = provider
.load_bound(DataRequest {
locale: &locale,
metadata: Default::default(),
})
.and_then(DataResponse::take_payload)
.map_err(SingleLoadError::Data)?;
self.month_symbols = OptionalNames::SingleLength(
field_symbol,
field_length,
R::MonthNames::maybe_from_payload(payload)
.ok_or(SingleLoadError::TypeTooNarrow(field))?,
);
Ok(())
}
pub(crate) fn load_day_period_names<P>(
&mut self,
provider: &P,
locale: &DataLocale,
field_length: FieldLength,
) -> Result<(), SingleLoadError>
where
P: BoundDataProvider<DayPeriodNamesV1Marker> + ?Sized,
{
let field = fields::Field {
symbol: FieldSymbol::DayPeriod(fields::DayPeriod::NoonMidnight),
length: field_length,
};
let field_length = field_length.numeric_to_abbr();
match self.dayperiod_symbols.check_with_length((), field_length) {
NamePresence::Loaded => return Ok(()),
NamePresence::NotLoaded => (),
NamePresence::Mismatched => return Err(SingleLoadError::DuplicateField(field)),
};
let mut locale = locale.clone();
locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for(
aux::Context::Format,
match field_length {
FieldLength::Abbreviated => aux::Length::Abbr,
FieldLength::Narrow => aux::Length::Narrow,
FieldLength::Wide => aux::Length::Wide,
_ => return Err(SingleLoadError::UnsupportedField(field)),
},
)));
let payload = R::DayPeriodNames::load_from(
provider,
DataRequest {
locale: &locale,
metadata: Default::default(),
},
)
.ok_or(SingleLoadError::TypeTooNarrow(field))?
.map_err(SingleLoadError::Data)?;
self.dayperiod_symbols = OptionalNames::SingleLength((), field_length, payload);
Ok(())
}
pub(crate) fn load_weekday_names<P>(
&mut self,
provider: &P,
locale: &DataLocale,
field_symbol: fields::Weekday,
field_length: FieldLength,
) -> Result<(), SingleLoadError>
where
P: BoundDataProvider<WeekdayNamesV1Marker> + ?Sized,
{
let field = fields::Field {
symbol: FieldSymbol::Weekday(field_symbol),
length: field_length,
};
let field_length = if matches!(field_symbol, fields::Weekday::Format) {
field_length.numeric_to_abbr()
} else {
field_length
};
match self
.weekday_symbols
.check_with_length(field_symbol, field_length)
{
NamePresence::Loaded => return Ok(()),
NamePresence::NotLoaded => (),
NamePresence::Mismatched => return Err(SingleLoadError::DuplicateField(field)),
};
let mut locale = locale.clone();
locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for(
match field_symbol {
fields::Weekday::Format | fields::Weekday::Local => aux::Context::Format,
fields::Weekday::StandAlone => aux::Context::Standalone,
},
match field_length {
FieldLength::Abbreviated => aux::Length::Abbr,
FieldLength::Narrow => aux::Length::Narrow,
FieldLength::Wide => aux::Length::Wide,
FieldLength::Six => aux::Length::Short,
_ => return Err(SingleLoadError::UnsupportedField(field)),
},
)));
let payload = provider
.load_bound(DataRequest {
locale: &locale,
metadata: Default::default(),
})
.and_then(DataResponse::take_payload)
.map_err(SingleLoadError::Data)?;
self.weekday_symbols = OptionalNames::SingleLength(
field_symbol,
field_length,
R::WeekdayNames::maybe_from_payload(payload)
.ok_or(SingleLoadError::TypeTooNarrow(field))?,
);
Ok(())
}
#[inline]
pub(crate) fn set_week_calculator(&mut self, week_calculator: WeekCalculator) {
self.week_calculator = Some(week_calculator);
}
pub(crate) fn load_fixed_decimal_formatter(
&mut self,
loader: &impl FixedDecimalFormatterLoader,
locale: &DataLocale,
) -> Result<(), DataError> {
let mut options = FixedDecimalFormatterOptions::default();
options.grouping_strategy = GroupingStrategy::Never;
self.fixed_decimal_formatter =
Some(FixedDecimalFormatterLoader::load(loader, locale, options)?);
Ok(())
}
#[inline]
pub(crate) fn with_pattern<'l>(
&'l self,
pattern: DateTimePatternBorrowed<'l>,
) -> RawDateTimePatternFormatter<'l> {
RawDateTimePatternFormatter {
pattern,
names: self.as_borrowed(),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn load_for_pattern(
&mut self,
year_provider: &(impl BoundDataProvider<YearNamesV1Marker> + ?Sized),
month_provider: &(impl BoundDataProvider<MonthNamesV1Marker> + ?Sized),
weekday_provider: &(impl BoundDataProvider<WeekdayNamesV1Marker> + ?Sized),
dayperiod_provider: &(impl BoundDataProvider<DayPeriodNamesV1Marker> + ?Sized),
fixed_decimal_formatter_loader: Option<&impl FixedDecimalFormatterLoader>,
week_calculator_loader: Option<&impl WeekCalculatorLoader>,
locale: &DataLocale,
pattern_items: impl Iterator<Item = PatternItem>,
) -> Result<(), LoadError> {
let fields = pattern_items.filter_map(|p| match p {
PatternItem::Field(field) => Some(field),
_ => None,
});
let mut numeric_field = None;
let mut week_field = None;
for field in fields {
match field.symbol {
FieldSymbol::Era => {
self.load_year_names(year_provider, locale, field.length)?;
}
FieldSymbol::Month(symbol) => match field.length {
FieldLength::One => numeric_field = Some(field),
FieldLength::TwoDigit => numeric_field = Some(field),
_ => {
self.load_month_names(month_provider, locale, symbol, field.length)?;
}
},
FieldSymbol::Weekday(symbol) => match field.length {
FieldLength::One | FieldLength::TwoDigit
if !matches!(symbol, fields::Weekday::Format) =>
{
numeric_field = Some(field)
}
_ => {
self.load_weekday_names(weekday_provider, locale, symbol, field.length)?;
}
},
FieldSymbol::DayPeriod(_) => {
self.load_day_period_names(dayperiod_provider, locale, field.length)?;
}
FieldSymbol::Year(fields::Year::WeekOf) => week_field = Some(field),
FieldSymbol::Year(_) => numeric_field = Some(field),
FieldSymbol::Week(_) => week_field = Some(field),
FieldSymbol::Day(_) => numeric_field = Some(field),
FieldSymbol::Hour(_) => numeric_field = Some(field),
FieldSymbol::Minute => numeric_field = Some(field),
FieldSymbol::Second(_) => numeric_field = Some(field),
FieldSymbol::TimeZone(_) => {
return Err(LoadError::UnsupportedField(field));
}
};
}
if let Some(field) = week_field {
self.set_week_calculator(
WeekCalculatorLoader::load(
week_calculator_loader.ok_or(LoadError::MissingNames(field))?,
locale,
)
.map_err(LoadError::Data)?,
);
}
if let Some(field) = numeric_field.or(week_field) {
self.load_fixed_decimal_formatter(
fixed_decimal_formatter_loader.ok_or(LoadError::MissingNames(field))?,
locale,
)
.map_err(LoadError::Data)?;
}
Ok(())
}
}
#[derive(Debug, Copy, Clone)]
pub struct DateTimePatternFormatter<'a, C: CldrCalendar> {
inner: RawDateTimePatternFormatter<'a>,
_calendar: PhantomData<C>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct RawDateTimePatternFormatter<'a> {
pattern: DateTimePatternBorrowed<'a>,
names: RawDateTimeNamesBorrowed<'a>,
}
impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> {
pub fn format<T>(&self, datetime: &'a T) -> FormattedDateTimePattern<'a>
where
T: DateTimeInput<Calendar = C>,
{
FormattedDateTimePattern {
pattern: self.inner.pattern,
datetime: ExtractedDateTimeInput::extract_from(datetime),
names: self.inner.names,
}
}
pub fn format_date<T>(&self, datetime: &'a T) -> FormattedDateTimePattern<'a>
where
T: DateInput<Calendar = C>,
{
FormattedDateTimePattern {
pattern: self.inner.pattern,
datetime: ExtractedDateTimeInput::extract_from_date(datetime),
names: self.inner.names,
}
}
pub fn format_time<T>(&self, datetime: &'a T) -> FormattedDateTimePattern<'a>
where
T: IsoTimeInput,
{
FormattedDateTimePattern {
pattern: self.inner.pattern,
datetime: ExtractedDateTimeInput::extract_from_time(datetime),
names: self.inner.names,
}
}
}
#[derive(Debug)]
pub struct FormattedDateTimePattern<'a> {
pattern: DateTimePatternBorrowed<'a>,
datetime: ExtractedDateTimeInput,
names: RawDateTimeNamesBorrowed<'a>,
}
impl<'a> TryWriteable for FormattedDateTimePattern<'a> {
type Error = DateTimeWriteError;
fn try_write_to_parts<S: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut S,
) -> Result<Result<(), Self::Error>, fmt::Error> {
try_write_pattern(
self.pattern.0.as_borrowed(),
&self.datetime,
Some(&self.names),
Some(&self.names),
self.names.week_calculator,
self.names.fixed_decimal_formatter,
sink,
)
}
}
impl<'data> DateSymbols<'data> for RawDateTimeNamesBorrowed<'data> {
fn get_symbol_for_month(
&self,
field_symbol: fields::Month,
field_length: FieldLength,
code: MonthCode,
) -> Result<MonthPlaceholderValue, GetSymbolForMonthError> {
let field = fields::Field {
symbol: FieldSymbol::Month(field_symbol),
length: field_length,
};
let month_symbols = self
.month_names
.get_with_length(field_symbol, field_length)
.ok_or(GetSymbolForMonthError::MissingNames(field))?;
let Some((month_number, is_leap)) = code.parsed() else {
return Err(GetSymbolForMonthError::Missing);
};
let Some(month_index) = month_number.checked_sub(1) else {
return Err(GetSymbolForMonthError::Missing);
};
let month_index = usize::from(month_index);
let symbol = match month_symbols {
MonthNamesV1::Linear(linear) => {
if is_leap {
None
} else {
linear.get(month_index)
}
}
MonthNamesV1::LeapLinear(leap_linear) => {
let num_months = leap_linear.len() / 2;
if is_leap {
leap_linear.get(month_index + num_months)
} else if month_index < num_months {
leap_linear.get(month_index)
} else {
None
}
}
MonthNamesV1::LeapNumeric(leap_numeric) => {
if is_leap {
return Ok(MonthPlaceholderValue::NumericPattern(leap_numeric));
} else {
return Ok(MonthPlaceholderValue::Numeric);
}
}
};
symbol
.map(MonthPlaceholderValue::PlainString)
.ok_or(GetSymbolForMonthError::Missing)
}
fn get_symbol_for_weekday(
&self,
field_symbol: fields::Weekday,
field_length: FieldLength,
day: input::IsoWeekday,
) -> Result<&str, GetSymbolForWeekdayError> {
let field = fields::Field {
symbol: FieldSymbol::Weekday(field_symbol),
length: field_length,
};
let field_symbol = field_symbol.to_format_symbol();
let field_length = if matches!(field_symbol, fields::Weekday::Format) {
field_length.numeric_to_abbr()
} else {
field_length
};
let weekday_symbols = self
.weekday_names
.get_with_length(field_symbol, field_length)
.ok_or(GetSymbolForWeekdayError::MissingNames(field))?;
weekday_symbols
.symbols
.get((day as usize) % 7)
.ok_or(GetSymbolForWeekdayError::Missing)
}
fn get_symbol_for_era<'a>(
&'a self,
field_length: FieldLength,
era_code: &'a Era,
) -> Result<&str, GetSymbolForEraError> {
let field = fields::Field {
symbol: FieldSymbol::Era,
length: field_length,
};
let field_length = field_length.numeric_to_abbr();
let year_symbols = self
.year_names
.get_with_length((), field_length)
.ok_or(GetSymbolForEraError::MissingNames(field))?;
let YearNamesV1::Eras(era_symbols) = year_symbols else {
return Err(GetSymbolForEraError::MissingNames(field));
};
era_symbols
.get(era_code.0.as_str().into())
.ok_or(GetSymbolForEraError::Missing)
}
}
impl<'data> TimeSymbols for RawDateTimeNamesBorrowed<'data> {
fn get_symbol_for_day_period(
&self,
field_symbol: fields::DayPeriod,
field_length: FieldLength,
hour: input::IsoHour,
is_top_of_hour: bool,
) -> Result<&str, GetSymbolForDayPeriodError> {
use fields::DayPeriod::NoonMidnight;
let field = fields::Field {
symbol: FieldSymbol::DayPeriod(field_symbol),
length: field_length,
};
let field_length = field_length.numeric_to_abbr();
let dayperiod_symbols = self
.dayperiod_names
.get_with_length((), field_length)
.ok_or(GetSymbolForDayPeriodError::MissingNames(field))?;
let option_value: Option<&str> = match (field_symbol, u8::from(hour), is_top_of_hour) {
(NoonMidnight, 00, true) => dayperiod_symbols
.midnight()
.or_else(|| dayperiod_symbols.am()),
(NoonMidnight, 12, true) => dayperiod_symbols.noon().or_else(|| dayperiod_symbols.pm()),
(_, hour, _) if hour < 12 => dayperiod_symbols.am(),
_ => dayperiod_symbols.pm(),
};
option_value.ok_or(GetSymbolForDayPeriodError::MissingNames(field))
}
}
#[cfg(test)]
#[cfg(feature = "compiled_data")]
mod tests {
use super::*;
use icu_calendar::{DateTime, Gregorian};
use icu_locid::locale;
use writeable::assert_try_writeable_eq;
#[test]
fn test_basic_pattern_formatting() {
let locale = locale!("en").into();
let mut names: TypedDateTimeNames<Gregorian> =
TypedDateTimeNames::try_new(&locale).unwrap();
names
.load_month_names(
&crate::provider::Baked,
fields::Month::Format,
fields::FieldLength::Wide,
)
.unwrap()
.load_weekday_names(
&crate::provider::Baked,
fields::Weekday::Format,
fields::FieldLength::Abbreviated,
)
.unwrap()
.load_year_names(&crate::provider::Baked, FieldLength::Narrow)
.unwrap()
.load_day_period_names(&crate::provider::Baked, FieldLength::Abbreviated)
.unwrap();
let pattern: DateTimePattern = "'It is' E, MMMM d, y GGGGG 'at' hh:mm a'!'"
.parse()
.unwrap();
let datetime = DateTime::try_new_gregorian_datetime(2023, 10, 25, 15, 0, 55).unwrap();
let formatted_pattern = names.with_pattern(&pattern).format(&datetime);
assert_try_writeable_eq!(
formatted_pattern,
"It is Wed, October 25, 2023 A at 03:00 PM!",
Ok(()),
);
}
#[test]
fn test_era_coverage() {
let locale = locale!("uk").into();
#[derive(Debug)]
struct TestCase {
pattern: &'static str,
field_length: FieldLength,
expected: &'static str,
}
let cases = [
TestCase {
pattern: "<G>",
field_length: FieldLength::Abbreviated,
expected: "<н. е.>",
},
TestCase {
pattern: "<GG>",
field_length: FieldLength::Abbreviated,
expected: "<н. е.>",
},
TestCase {
pattern: "<GGG>",
field_length: FieldLength::Abbreviated,
expected: "<н. е.>",
},
TestCase {
pattern: "<GGGG>",
field_length: FieldLength::Wide,
expected: "<нашої ери>",
},
TestCase {
pattern: "<GGGGG>",
field_length: FieldLength::Narrow,
expected: "<н.е.>",
},
];
for cas in cases {
let TestCase {
pattern,
field_length,
expected,
} = cas;
let mut names: TypedDateTimeNames<Gregorian> =
TypedDateTimeNames::try_new(&locale).unwrap();
names
.load_year_names(&crate::provider::Baked, field_length)
.unwrap();
let pattern: DateTimePattern = pattern.parse().unwrap();
let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap();
let formatted_pattern = names.with_pattern(&pattern).format(&datetime);
assert_try_writeable_eq!(formatted_pattern, expected, Ok(()), "{cas:?}");
}
}
#[test]
fn test_month_coverage() {
let locale = locale!("uk").into();
#[derive(Debug)]
struct TestCase {
pattern: &'static str,
field_symbol: fields::Month,
field_length: FieldLength,
expected: &'static str,
}
let cases = [
TestCase {
pattern: "<MMM>",
field_symbol: fields::Month::Format,
field_length: FieldLength::Abbreviated,
expected: "<лист.>",
},
TestCase {
pattern: "<MMMM>",
field_symbol: fields::Month::Format,
field_length: FieldLength::Wide,
expected: "<листопада>",
},
TestCase {
pattern: "<MMMMM>",
field_symbol: fields::Month::Format,
field_length: FieldLength::Narrow,
expected: "<л>",
},
TestCase {
pattern: "<LLL>",
field_symbol: fields::Month::StandAlone,
field_length: FieldLength::Abbreviated,
expected: "<лист.>",
},
TestCase {
pattern: "<LLLL>",
field_symbol: fields::Month::StandAlone,
field_length: FieldLength::Wide,
expected: "<листопад>",
},
TestCase {
pattern: "<LLLLL>",
field_symbol: fields::Month::StandAlone,
field_length: FieldLength::Narrow,
expected: "<Л>",
},
];
for cas in cases {
let TestCase {
pattern,
field_symbol,
field_length,
expected,
} = cas;
let mut names: TypedDateTimeNames<Gregorian> =
TypedDateTimeNames::try_new(&locale).unwrap();
names
.load_month_names(&crate::provider::Baked, field_symbol, field_length)
.unwrap();
let pattern: DateTimePattern = pattern.parse().unwrap();
let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap();
let formatted_pattern = names.with_pattern(&pattern).format(&datetime);
assert_try_writeable_eq!(formatted_pattern, expected, Ok(()), "{cas:?}");
}
}
#[test]
fn test_weekday_coverage() {
let locale = locale!("uk").into();
#[derive(Debug)]
struct TestCase {
pattern: &'static str,
field_symbol: fields::Weekday,
field_length: FieldLength,
expected: &'static str,
}
let cases = [
TestCase {
pattern: "<E>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Abbreviated,
expected: "<пт>",
},
TestCase {
pattern: "<EE>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Abbreviated,
expected: "<пт>",
},
TestCase {
pattern: "<EEE>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Abbreviated,
expected: "<пт>",
},
TestCase {
pattern: "<EEEE>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Wide,
expected: "<пʼятницю>",
},
TestCase {
pattern: "<EEEEE>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Narrow,
expected: "<П>",
},
TestCase {
pattern: "<EEEEEE>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Six,
expected: "<пт>",
},
TestCase {
pattern: "<eee>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Abbreviated,
expected: "<пт>",
},
TestCase {
pattern: "<eeee>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Wide,
expected: "<пʼятницю>",
},
TestCase {
pattern: "<eeeee>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Narrow,
expected: "<П>",
},
TestCase {
pattern: "<eeeeee>",
field_symbol: fields::Weekday::Format,
field_length: FieldLength::Six,
expected: "<пт>",
},
TestCase {
pattern: "<ccc>",
field_symbol: fields::Weekday::StandAlone,
field_length: FieldLength::Abbreviated,
expected: "<пт>",
},
TestCase {
pattern: "<cccc>",
field_symbol: fields::Weekday::StandAlone,
field_length: FieldLength::Wide,
expected: "<пʼятниця>",
},
TestCase {
pattern: "<ccccc>",
field_symbol: fields::Weekday::StandAlone,
field_length: FieldLength::Narrow,
expected: "<П>",
},
TestCase {
pattern: "<cccccc>",
field_symbol: fields::Weekday::StandAlone,
field_length: FieldLength::Six,
expected: "<пт>",
},
];
for cas in cases {
let TestCase {
pattern,
field_symbol,
field_length,
expected,
} = cas;
let mut names: TypedDateTimeNames<Gregorian> =
TypedDateTimeNames::try_new(&locale).unwrap();
names
.load_weekday_names(&crate::provider::Baked, field_symbol, field_length)
.unwrap();
let pattern: DateTimePattern = pattern.parse().unwrap();
let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap();
let formatted_pattern = names.with_pattern(&pattern).format(&datetime);
assert_try_writeable_eq!(formatted_pattern, expected, Ok(()), "{cas:?}");
}
}
#[test]
fn test_dayperiod_coverage() {
let locale = locale!("th").into();
#[derive(Debug)]
struct TestCase {
pattern: &'static str,
field_length: FieldLength,
expected: &'static str,
}
let cases = [
TestCase {
pattern: "<a>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<aa>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<aaa>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<aaaa>",
field_length: FieldLength::Wide,
expected: "<หลังเที่ยง>",
},
TestCase {
pattern: "<aaaaa>",
field_length: FieldLength::Narrow,
expected: "<p>",
},
TestCase {
pattern: "<b>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<bb>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<bbb>",
field_length: FieldLength::Abbreviated,
expected: "<PM>",
},
TestCase {
pattern: "<bbbb>",
field_length: FieldLength::Wide,
expected: "<หลังเที่ยง>",
},
TestCase {
pattern: "<bbbbb>",
field_length: FieldLength::Narrow,
expected: "<p>",
},
];
for cas in cases {
let TestCase {
pattern,
field_length,
expected,
} = cas;
let mut names: TypedDateTimeNames<Gregorian> =
TypedDateTimeNames::try_new(&locale).unwrap();
names
.load_day_period_names(&crate::provider::Baked, field_length)
.unwrap();
let pattern: DateTimePattern = pattern.parse().unwrap();
let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap();
let formatted_pattern = names.with_pattern(&pattern).format(&datetime);
assert_try_writeable_eq!(formatted_pattern, expected, Ok(()), "{cas:?}");
}
}
}