use crate::fieldsets::builder;
use crate::fieldsets::enums::{CompositeFieldSet, TimeFieldSet, ZoneFieldSet};
use crate::format::DateTimeInputUnchecked;
use crate::options::*;
use crate::pattern::DateTimePattern;
use crate::provider::fields::{self, Field, FieldLength, FieldSymbol};
use crate::provider::pattern::{
runtime::{self, PatternMetadata},
GenericPatternItem, PatternItem,
};
use crate::provider::{
packed_pattern::{ErasedPackedPatterns, PackedSkeletonVariant},
semantic_skeletons::{marker_attrs, DatetimePatternsGlueV1, GluePattern},
};
use crate::DateTimeFormatterPreferences;
use icu_calendar::types::YearAmbiguity;
use icu_provider::prelude::*;
use icu_provider::DataPayloadOr;
use marker_attrs::GlueType;
use zerovec::ule::AsULE;
use zerovec::ZeroSlice;
#[derive(Debug, Copy, Clone)]
pub(crate) struct RawOptions {
pub(crate) length: Option<Length>,
pub(crate) date_fields: Option<builder::DateFields>,
pub(crate) alignment: Option<Alignment>,
pub(crate) year_style: Option<YearStyle>,
pub(crate) time_precision: Option<TimePrecision>,
}
impl RawOptions {
#[inline]
pub(crate) fn length(self) -> Length {
self.length.unwrap_or_else(|| {
debug_assert!(false, "length not set in a formatter that needs it");
Default::default()
})
}
}
#[derive(Debug, Copy, Clone, Default)]
pub(crate) struct RawPreferences {
pub(crate) hour_cycle: Option<fields::Hour>,
}
impl RawPreferences {
#[inline]
pub(crate) fn from_prefs(prefs: DateTimeFormatterPreferences) -> Self {
Self {
hour_cycle: fields::Hour::from_prefs(prefs),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct DatePatternSelectionData {
payload: DataPayloadOr<ErasedPackedPatterns, ()>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum DatePatternDataBorrowed<'a> {
Resolved(runtime::PatternBorrowed<'a>, Option<Alignment>),
}
#[derive(Debug, Clone)]
pub(crate) struct TimePatternSelectionData {
payload: DataPayloadOr<ErasedPackedPatterns, ()>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum TimePatternDataBorrowed<'a> {
Resolved(
runtime::PatternBorrowed<'a>,
Option<Alignment>,
Option<fields::Hour>,
Option<SubsecondDigits>,
),
}
#[derive(Debug, Clone)]
pub(crate) enum ZonePatternSelectionData {
SinglePatternItem(ZoneFieldSet, <PatternItem as AsULE>::ULE),
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum ZonePatternDataBorrowed<'a> {
SinglePatternItem(&'a <PatternItem as AsULE>::ULE),
}
#[derive(Debug, Copy, Clone, Default)]
pub(crate) struct ItemsAndOptions<'a> {
pub(crate) items: &'a ZeroSlice<PatternItem>,
pub(crate) alignment: Option<Alignment>,
pub(crate) hour_cycle: Option<fields::Hour>,
pub(crate) subsecond_digits: Option<SubsecondDigits>,
}
impl ItemsAndOptions<'_> {
const fn new_empty() -> Self {
Self {
items: ZeroSlice::new_empty(),
alignment: None,
hour_cycle: None,
subsecond_digits: None,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct DateTimeZonePatternSelectionData {
options: RawOptions,
prefs: RawPreferences,
date: DatePatternSelectionData,
time: TimePatternSelectionData,
zone: Option<ZonePatternSelectionData>,
glue: Option<DataPayload<DatetimePatternsGlueV1>>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct DateTimeZonePatternDataBorrowed<'a> {
date: Option<DatePatternDataBorrowed<'a>>,
time: Option<TimePatternDataBorrowed<'a>>,
zone: Option<ZonePatternDataBorrowed<'a>>,
glue: Option<&'a GluePattern<'a>>,
}
impl DatePatternSelectionData {
pub(crate) fn none() -> Self {
Self {
payload: DataPayloadOr::none(),
}
}
pub(crate) fn try_new_with_skeleton(
provider: &(impl BoundDataProvider<ErasedPackedPatterns> + ?Sized),
prefs: DateTimeFormatterPreferences,
attributes: &DataMarkerAttributes,
) -> Result<Self, DataError> {
let locale = provider
.bound_marker()
.make_locale(prefs.locale_preferences);
let payload = provider
.load_bound(DataRequest {
id: DataIdentifierBorrowed::for_marker_attributes_and_locale(attributes, &locale),
..Default::default()
})?
.payload;
Ok(Self {
payload: DataPayloadOr::from_payload(payload),
})
}
#[inline]
pub(crate) fn pattern_items_for_data_loading(
&self,
options: RawOptions,
) -> Option<impl Iterator<Item = PatternItem> + '_> {
let payload = self.payload.get_option()?;
Some(
payload
.get(options.length(), PackedSkeletonVariant::Variant1)
.items
.iter(),
)
}
pub(crate) fn select(
&self,
input: &DateTimeInputUnchecked,
options: RawOptions,
) -> Option<DatePatternDataBorrowed<'_>> {
let payload = self.payload.get_option()?;
let year_style = options.year_style.unwrap_or_default();
let ambiguity = input
.year
.as_ref()
.map(|y| {
y.era()
.map(|e| e.ambiguity)
.unwrap_or(YearAmbiguity::EraRequired)
})
.unwrap_or(YearAmbiguity::EraAndCenturyRequired);
let variant = match (year_style, ambiguity) {
(YearStyle::WithEra, _) => PackedSkeletonVariant::Variant1,
(
YearStyle::Full,
YearAmbiguity::EraAndCenturyRequired | YearAmbiguity::EraRequired,
) => PackedSkeletonVariant::Variant1,
(YearStyle::Full, YearAmbiguity::CenturyRequired | YearAmbiguity::Unambiguous) => {
PackedSkeletonVariant::Variant0
}
(
YearStyle::Auto | YearStyle::NoEra,
YearAmbiguity::Unambiguous | YearAmbiguity::EraRequired,
) => PackedSkeletonVariant::Standard,
(YearStyle::Auto, YearAmbiguity::CenturyRequired) => PackedSkeletonVariant::Variant0,
(YearStyle::Auto, YearAmbiguity::EraAndCenturyRequired) => {
PackedSkeletonVariant::Variant1
}
(
YearStyle::NoEra,
YearAmbiguity::CenturyRequired | YearAmbiguity::EraAndCenturyRequired,
) => PackedSkeletonVariant::Variant0,
};
Some(DatePatternDataBorrowed::Resolved(
payload.get(options.length(), variant),
options.alignment,
))
}
}
impl DateTimeInputUnchecked {
fn resolve_time_precision(
&self,
time_precision: TimePrecision,
) -> (PackedSkeletonVariant, Option<SubsecondDigits>) {
match time_precision {
TimePrecision::Hour => (PackedSkeletonVariant::Standard, None),
TimePrecision::Minute => (PackedSkeletonVariant::Variant0, None),
TimePrecision::Second => (PackedSkeletonVariant::Variant1, None),
TimePrecision::Subsecond(f) => (PackedSkeletonVariant::Variant1, Some(f)),
TimePrecision::MinuteOptional => {
let minute = self.minute.unwrap_or_default();
if minute.is_zero() {
(PackedSkeletonVariant::Standard, None)
} else {
(PackedSkeletonVariant::Variant0, None)
}
}
}
}
}
impl<'a> DatePatternDataBorrowed<'a> {
pub(crate) fn items_and_options(self) -> ItemsAndOptions<'a> {
let Self::Resolved(pattern, alignment) = self;
ItemsAndOptions {
items: pattern.items,
alignment,
..Default::default()
}
}
}
impl TimePatternSelectionData {
pub(crate) fn none() -> Self {
Self {
payload: DataPayloadOr::none(),
}
}
pub(crate) fn try_new_with_skeleton(
provider: &(impl BoundDataProvider<ErasedPackedPatterns> + ?Sized),
prefs: DateTimeFormatterPreferences,
components: TimeFieldSet,
) -> Result<Self, DataError> {
let locale = provider
.bound_marker()
.make_locale(prefs.locale_preferences);
let prefs = RawPreferences::from_prefs(prefs);
let mut maybe_payload = None;
if let Some(hour_cycle) = prefs.hour_cycle {
maybe_payload = provider
.load_bound(DataRequest {
id: DataIdentifierBorrowed::for_marker_attributes_and_locale(
components.id_str_for_hour_cycle(Some(hour_cycle)),
&locale,
),
..Default::default()
})
.allow_identifier_not_found()?
.map(|r| r.payload);
}
let payload = match maybe_payload {
Some(payload) => payload,
None => {
provider
.load_bound(DataRequest {
id: DataIdentifierBorrowed::for_marker_attributes_and_locale(
components.id_str_for_hour_cycle(None),
&locale,
),
..Default::default()
})?
.payload
}
};
Ok(Self {
payload: DataPayloadOr::from_payload(payload),
})
}
pub(crate) fn try_new_overlap_with_skeleton(
provider: &(impl BoundDataProvider<ErasedPackedPatterns> + ?Sized),
prefs: DateTimeFormatterPreferences,
attributes: &DataMarkerAttributes,
options: RawOptions,
) -> Result<Self, DataError> {
debug_assert!(options.year_style.is_none());
let locale = provider
.bound_marker()
.make_locale(prefs.locale_preferences);
let payload = provider
.load_bound(DataRequest {
id: DataIdentifierBorrowed::for_marker_attributes_and_locale(attributes, &locale),
..Default::default()
})?
.payload;
Ok(Self {
payload: DataPayloadOr::from_payload(payload),
})
}
#[inline]
pub(crate) fn pattern_items_for_data_loading(
&self,
options: RawOptions,
) -> Option<impl Iterator<Item = PatternItem> + '_> {
let payload = self.payload.get_option()?;
Some(
payload
.get(options.length(), PackedSkeletonVariant::Variant1)
.items
.iter(),
)
}
pub(crate) fn select(
&self,
input: &DateTimeInputUnchecked,
options: RawOptions,
prefs: RawPreferences,
) -> Option<TimePatternDataBorrowed<'_>> {
let payload = self.payload.get_option()?;
let time_precision = options.time_precision.unwrap_or_default();
let (variant, subsecond_digits) = input.resolve_time_precision(time_precision);
Some(TimePatternDataBorrowed::Resolved(
payload.get(options.length(), variant),
options.alignment,
prefs.hour_cycle,
subsecond_digits,
))
}
}
impl<'a> TimePatternDataBorrowed<'a> {
pub(crate) fn items_and_options(self) -> ItemsAndOptions<'a> {
let Self::Resolved(pattern, alignment, hour_cycle, subsecond_digits) = self;
ItemsAndOptions {
items: pattern.items,
alignment,
hour_cycle,
subsecond_digits,
}
}
}
impl ZonePatternSelectionData {
pub(crate) fn new_with_skeleton(field_set: ZoneFieldSet) -> Self {
let (symbol, length) = field_set.to_field();
let pattern_item = PatternItem::Field(Field {
symbol: FieldSymbol::TimeZone(symbol),
length,
});
Self::SinglePatternItem(field_set, pattern_item.to_unaligned())
}
#[inline]
pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator<Item = PatternItem> + '_ {
let Self::SinglePatternItem(_, pattern_item) = self;
let pattern_item = PatternItem::from_unaligned(*pattern_item);
[pattern_item].into_iter()
}
pub(crate) fn select(&self, _input: &DateTimeInputUnchecked) -> ZonePatternDataBorrowed<'_> {
let Self::SinglePatternItem(_, pattern_item) = self;
ZonePatternDataBorrowed::SinglePatternItem(pattern_item)
}
}
impl<'a> ZonePatternDataBorrowed<'a> {
pub(crate) fn items_and_options(self) -> ItemsAndOptions<'a> {
let Self::SinglePatternItem(item) = self;
ItemsAndOptions {
items: ZeroSlice::from_ule_slice(core::slice::from_ref(item)),
..Default::default()
}
}
}
impl DateTimeZonePatternSelectionData {
pub(crate) fn try_new_with_skeleton(
date_provider: &(impl BoundDataProvider<ErasedPackedPatterns> + ?Sized),
time_provider: &(impl BoundDataProvider<ErasedPackedPatterns> + ?Sized),
glue_provider: &(impl BoundDataProvider<DatetimePatternsGlueV1> + ?Sized),
prefs: DateTimeFormatterPreferences,
skeleton: CompositeFieldSet,
) -> Result<Self, DataError> {
match skeleton {
CompositeFieldSet::Date(field_set) => {
let options = field_set.to_raw_options();
let selection = DatePatternSelectionData::try_new_with_skeleton(
date_provider,
prefs,
field_set.id_str(),
)?;
Ok(Self {
options,
prefs: Default::default(), date: selection,
time: TimePatternSelectionData::none(),
zone: None,
glue: None,
})
}
CompositeFieldSet::CalendarPeriod(field_set) => {
let options = field_set.to_raw_options();
let selection = DatePatternSelectionData::try_new_with_skeleton(
date_provider,
prefs,
field_set.id_str(),
)?;
Ok(Self {
options,
prefs: Default::default(), date: selection,
time: TimePatternSelectionData::none(),
zone: None,
glue: None,
})
}
CompositeFieldSet::Time(field_set) => {
let options = field_set.to_raw_options();
let selection = TimePatternSelectionData::try_new_with_skeleton(
time_provider,
prefs,
field_set,
)?;
let prefs = RawPreferences::from_prefs(prefs);
Ok(Self {
options,
prefs,
date: DatePatternSelectionData::none(),
time: selection,
zone: None,
glue: None,
})
}
CompositeFieldSet::Zone(field_set) => {
let selection = ZonePatternSelectionData::new_with_skeleton(field_set);
Ok(Self {
options: RawOptions {
length: None,
date_fields: None,
year_style: None,
alignment: None,
time_precision: None,
},
prefs: Default::default(), date: DatePatternSelectionData::none(),
time: TimePatternSelectionData::none(),
zone: Some(selection),
glue: None,
})
}
CompositeFieldSet::DateTime(field_set) => {
let options = field_set.to_raw_options();
if let (Some(attributes), None) = (field_set.id_str(), prefs.hour_cycle) {
if let Some(overlap) = TimePatternSelectionData::try_new_overlap_with_skeleton(
date_provider,
prefs,
attributes,
options,
)
.allow_identifier_not_found()?
{
let prefs = RawPreferences::from_prefs(prefs);
return Ok(Self {
options,
prefs,
date: DatePatternSelectionData::none(),
time: overlap,
zone: None,
glue: None,
});
}
}
let date = DatePatternSelectionData::try_new_with_skeleton(
date_provider,
prefs,
field_set.to_date_field_set().id_str(),
)?;
let time = TimePatternSelectionData::try_new_with_skeleton(
time_provider,
prefs,
field_set.to_time_field_set(),
)?;
let glue = Self::load_glue(glue_provider, prefs, options, GlueType::DateTime)?;
let prefs = RawPreferences::from_prefs(prefs);
Ok(Self {
options,
prefs,
date,
time,
zone: None,
glue: Some(glue),
})
}
CompositeFieldSet::DateZone(combo) => {
let options = combo.dt().to_raw_options();
let date = DatePatternSelectionData::try_new_with_skeleton(
date_provider,
prefs,
combo.dt().id_str(),
)?;
let zone = ZonePatternSelectionData::new_with_skeleton(combo.z());
let glue = Self::load_glue(glue_provider, prefs, options, GlueType::DateZone)?;
Ok(Self {
options,
prefs: Default::default(), date,
time: TimePatternSelectionData::none(),
zone: Some(zone),
glue: Some(glue),
})
}
CompositeFieldSet::TimeZone(combo) => {
let options = combo.dt().to_raw_options();
let time = TimePatternSelectionData::try_new_with_skeleton(
time_provider,
prefs,
combo.dt(),
)?;
let zone = ZonePatternSelectionData::new_with_skeleton(combo.z());
let glue = Self::load_glue(glue_provider, prefs, options, GlueType::TimeZone)?;
let prefs = RawPreferences::from_prefs(prefs);
Ok(Self {
options,
prefs,
date: DatePatternSelectionData::none(),
time,
zone: Some(zone),
glue: Some(glue),
})
}
CompositeFieldSet::DateTimeZone(combo) => {
let options = combo.dt().to_raw_options();
let date = DatePatternSelectionData::try_new_with_skeleton(
date_provider,
prefs,
combo.dt().to_date_field_set().id_str(),
)?;
let time = TimePatternSelectionData::try_new_with_skeleton(
time_provider,
prefs,
combo.dt().to_time_field_set(),
)?;
let zone = ZonePatternSelectionData::new_with_skeleton(combo.z());
let glue = Self::load_glue(glue_provider, prefs, options, GlueType::DateTimeZone)?;
let prefs = RawPreferences::from_prefs(prefs);
Ok(Self {
options,
prefs,
date,
time,
zone: Some(zone),
glue: Some(glue),
})
}
}
}
fn load_glue(
provider: &(impl BoundDataProvider<DatetimePatternsGlueV1> + ?Sized),
prefs: DateTimeFormatterPreferences,
options: RawOptions,
glue_type: GlueType,
) -> Result<DataPayload<DatetimePatternsGlueV1>, DataError> {
let locale = provider
.bound_marker()
.make_locale(prefs.locale_preferences);
provider
.load_bound(DataRequest {
id: DataIdentifierBorrowed::for_marker_attributes_and_locale(
marker_attrs::pattern_marker_attr_for_glue(
match options.length() {
Length::Long => marker_attrs::PatternLength::Long,
Length::Medium => marker_attrs::PatternLength::Medium,
Length::Short => marker_attrs::PatternLength::Short,
},
glue_type,
),
&locale,
),
..Default::default()
})
.map(|r| r.payload)
}
#[inline]
pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator<Item = PatternItem> + '_ {
let Self {
date, time, zone, ..
} = self;
let date_items = date
.pattern_items_for_data_loading(self.options)
.into_iter()
.flatten();
let time_items = time
.pattern_items_for_data_loading(self.options)
.into_iter()
.flatten();
let zone_items = zone.iter().flat_map(|x| x.pattern_items_for_data_loading());
date_items.chain(time_items).chain(zone_items)
}
pub(crate) fn select(
&self,
input: &DateTimeInputUnchecked,
) -> DateTimeZonePatternDataBorrowed<'_> {
DateTimeZonePatternDataBorrowed {
date: self.date.select(input, self.options),
time: self.time.select(input, self.options, self.prefs),
zone: self.zone.as_ref().map(|zone| zone.select(input)),
glue: self.glue.as_ref().map(|glue| glue.get()),
}
}
pub(crate) fn to_builder(&self) -> builder::FieldSetBuilder {
let time_precision = if self.time.payload.is_payload() {
Some(self.options.time_precision.unwrap_or_default())
} else {
None
};
let zone_style = self.zone.as_ref().map(|zone| {
let ZonePatternSelectionData::SinglePatternItem(field_set, _) = zone;
field_set.to_zone_style()
});
builder::FieldSetBuilder {
length: self.options.length,
date_fields: self.options.date_fields,
time_precision,
zone_style,
alignment: self.options.alignment,
year_style: self.options.year_style,
}
}
}
impl<'a> DateTimeZonePatternDataBorrowed<'a> {
#[inline]
fn date_pattern(self) -> Option<DatePatternDataBorrowed<'a>> {
self.date
}
#[inline]
fn time_pattern(self) -> Option<TimePatternDataBorrowed<'a>> {
self.time
}
#[inline]
fn zone_pattern(self) -> Option<ZonePatternDataBorrowed<'a>> {
self.zone
}
#[inline]
fn glue_pattern(self) -> Option<&'a ZeroSlice<GenericPatternItem>> {
self.glue.map(|glue| glue.pattern.items.as_slice())
}
#[inline]
pub(crate) fn metadata(self) -> PatternMetadata {
match (self.date, self.time) {
(Some(DatePatternDataBorrowed::Resolved(pb, _)), None) => pb.metadata,
(None, Some(TimePatternDataBorrowed::Resolved(pb, _, _, _))) => pb.metadata,
(
Some(DatePatternDataBorrowed::Resolved(date, _)),
Some(TimePatternDataBorrowed::Resolved(time, _, _, _)),
) => PatternMetadata::merge_date_and_time_metadata(date.metadata, time.metadata),
(None, None) => Default::default(),
}
}
pub(crate) fn iter_items(self) -> impl Iterator<Item = PatternItem> + 'a {
let glue_pattern_slice = match self.glue_pattern() {
Some(glue) => glue.as_ule_slice(),
None => runtime::ZERO_ONE_TWO_SLICE.as_ule_slice(),
};
glue_pattern_slice
.iter()
.map(
move |generic_item_ule| match generic_item_ule.as_pattern_item_ule() {
Ok(pattern_item_ule) => ItemsAndOptions {
items: ZeroSlice::from_ule_slice(core::slice::from_ref(pattern_item_ule)),
..Default::default()
},
Err(1) => self
.date_pattern()
.map(|p| p.items_and_options())
.unwrap_or(const { ItemsAndOptions::new_empty() }),
Err(0) => self
.time_pattern()
.map(|p| p.items_and_options())
.unwrap_or(const { ItemsAndOptions::new_empty() }),
Err(2) => self
.zone_pattern()
.map(|p| p.items_and_options())
.unwrap_or(const { ItemsAndOptions::new_empty() }),
_ => ItemsAndOptions::new_empty(),
},
)
.flat_map(|items_and_options| items_and_options.iter_items())
}
pub(crate) fn to_pattern(self) -> DateTimePattern {
let pattern = self.iter_items().collect::<runtime::Pattern>();
DateTimePattern::from(pattern)
}
}
impl<'a> ItemsAndOptions<'a> {
pub(crate) fn iter_items(self) -> impl Iterator<Item = PatternItem> + 'a {
self.items.iter().map(move |mut pattern_item| {
#[expect(clippy::single_match)] match &mut pattern_item {
PatternItem::Field(ref mut field) => {
let alignment = self.alignment.unwrap_or_default();
if matches!(alignment, Alignment::Column)
&& field.length == FieldLength::One
&& matches!(
field.symbol,
FieldSymbol::Month(_)
| FieldSymbol::Day(_)
| FieldSymbol::Week(_)
| FieldSymbol::Hour(_)
)
{
field.length = FieldLength::Two;
}
if let Some(hour_cycle) = self.hour_cycle {
if let FieldSymbol::Hour(_) = field.symbol {
field.symbol = FieldSymbol::Hour(hour_cycle);
}
}
if let Some(subsecond_digits) = self.subsecond_digits {
if matches!(
field.symbol,
FieldSymbol::Second(fields::Second::Second)
| FieldSymbol::DecimalSecond(_)
) {
field.symbol = FieldSymbol::from_subsecond_digits(subsecond_digits);
}
}
}
_ => (),
}
pattern_item
})
}
}