#[cfg(feature = "experimental")]
use crate::options::components;
use crate::provider::{calendar::*, date_time::PatternSelector};
use crate::{calendar, options::DateTimeFormatterOptions, raw, DateFormatter, TimeFormatter};
use crate::{input::DateTimeInput, DateTimeError, FormattedDateTime};
use alloc::string::String;
use icu_calendar::any_calendar::{AnyCalendar, AnyCalendarKind};
use icu_calendar::provider::{
JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV1Marker,
};
use icu_calendar::{types::Time, DateTime};
use icu_decimal::provider::DecimalSymbolsV1Marker;
use icu_plurals::provider::OrdinalV1Marker;
use icu_provider::prelude::*;
use icu_provider::DataLocale;
use writeable::Writeable;
#[derive(Debug)]
pub struct DateTimeFormatter(pub(crate) raw::DateTimeFormatter, AnyCalendar);
impl DateTimeFormatter {
#[inline]
pub fn try_new_with_any_provider<P>(
data_provider: &P,
locale: &DataLocale,
options: DateTimeFormatterOptions,
) -> Result<Self, DateTimeError>
where
P: AnyProvider,
{
let downcasting = data_provider.as_downcasting();
Self::try_new_unstable(&downcasting, locale, options)
}
#[inline]
#[cfg(feature = "serde")]
pub fn try_new_with_buffer_provider<P>(
data_provider: &P,
locale: &DataLocale,
options: DateTimeFormatterOptions,
) -> Result<Self, DateTimeError>
where
P: BufferProvider,
{
let deserializing = data_provider.as_deserializing();
Self::try_new_unstable(&deserializing, locale, options)
}
#[cfg(feature = "experimental")]
#[inline(never)]
pub fn try_new_experimental_unstable<P>(
data_provider: &P,
locale: &DataLocale,
options: DateTimeFormatterOptions,
) -> Result<Self, DateTimeError>
where
P: DataProvider<TimeSymbolsV1Marker>
+ DataProvider<TimeLengthsV1Marker>
+ DataProvider<crate::provider::calendar::DateSkeletonPatternsV1Marker>
+ DataProvider<OrdinalV1Marker>
+ DataProvider<WeekDataV1Marker>
+ DataProvider<DecimalSymbolsV1Marker>
+ DataProvider<GregorianDateLengthsV1Marker>
+ DataProvider<BuddhistDateLengthsV1Marker>
+ DataProvider<JapaneseDateLengthsV1Marker>
+ DataProvider<JapaneseExtendedDateLengthsV1Marker>
+ DataProvider<CopticDateLengthsV1Marker>
+ DataProvider<IndianDateLengthsV1Marker>
+ DataProvider<EthiopianDateLengthsV1Marker>
+ DataProvider<GregorianDateSymbolsV1Marker>
+ DataProvider<BuddhistDateSymbolsV1Marker>
+ DataProvider<JapaneseDateSymbolsV1Marker>
+ DataProvider<JapaneseExtendedDateSymbolsV1Marker>
+ DataProvider<CopticDateSymbolsV1Marker>
+ DataProvider<IndianDateSymbolsV1Marker>
+ DataProvider<EthiopianDateSymbolsV1Marker>
+ DataProvider<JapaneseErasV1Marker>
+ DataProvider<JapaneseExtendedErasV1Marker>
+ ?Sized,
{
let calendar = AnyCalendar::try_new_for_locale_unstable(data_provider, locale)?;
let kind = calendar.kind();
let patterns = PatternSelector::for_options_experimental(
data_provider,
calendar::load_lengths_for_any_calendar_kind(data_provider, locale, kind)?,
locale,
&kind.as_bcp47_value(),
&options,
)?;
Ok(Self(
raw::DateTimeFormatter::try_new(
data_provider,
patterns,
|| calendar::load_symbols_for_any_calendar_kind(data_provider, locale, kind),
locale,
)?,
calendar,
))
}
#[inline(never)]
pub fn try_new_unstable<P>(
data_provider: &P,
locale: &DataLocale,
options: DateTimeFormatterOptions,
) -> Result<Self, DateTimeError>
where
P: DataProvider<TimeSymbolsV1Marker>
+ DataProvider<TimeLengthsV1Marker>
+ DataProvider<OrdinalV1Marker>
+ DataProvider<WeekDataV1Marker>
+ DataProvider<DecimalSymbolsV1Marker>
+ DataProvider<GregorianDateLengthsV1Marker>
+ DataProvider<BuddhistDateLengthsV1Marker>
+ DataProvider<JapaneseDateLengthsV1Marker>
+ DataProvider<JapaneseExtendedDateLengthsV1Marker>
+ DataProvider<CopticDateLengthsV1Marker>
+ DataProvider<IndianDateLengthsV1Marker>
+ DataProvider<EthiopianDateLengthsV1Marker>
+ DataProvider<GregorianDateSymbolsV1Marker>
+ DataProvider<BuddhistDateSymbolsV1Marker>
+ DataProvider<JapaneseDateSymbolsV1Marker>
+ DataProvider<JapaneseExtendedDateSymbolsV1Marker>
+ DataProvider<CopticDateSymbolsV1Marker>
+ DataProvider<IndianDateSymbolsV1Marker>
+ DataProvider<EthiopianDateSymbolsV1Marker>
+ DataProvider<JapaneseErasV1Marker>
+ DataProvider<JapaneseExtendedErasV1Marker>
+ ?Sized,
{
let calendar = AnyCalendar::try_new_for_locale_unstable(data_provider, locale)?;
let kind = calendar.kind();
let patterns = PatternSelector::for_options(
data_provider,
calendar::load_lengths_for_any_calendar_kind(data_provider, locale, kind)?,
locale,
&options,
)?;
Ok(Self(
raw::DateTimeFormatter::try_new(
data_provider,
patterns,
|| calendar::load_symbols_for_any_calendar_kind(data_provider, locale, kind),
locale,
)?,
calendar,
))
}
#[inline]
pub fn try_from_date_and_time(
date: DateFormatter,
time: TimeFormatter,
) -> Result<Self, DateTimeError>
where {
Ok(Self(
raw::DateTimeFormatter::try_from_date_and_time(date.0, time.0)?,
date.1,
))
}
#[inline]
pub fn format<'l, T>(&'l self, value: &T) -> Result<FormattedDateTime<'l>, DateTimeError>
where
T: DateTimeInput<Calendar = AnyCalendar>,
{
if let Some(converted) = self.convert_if_necessary(value)? {
Ok(self.0.format(&converted))
} else {
Ok(self.0.format(value))
}
}
#[inline]
pub fn format_to_string(
&self,
value: &impl DateTimeInput<Calendar = AnyCalendar>,
) -> Result<String, DateTimeError> {
Ok(self.format(value)?.write_to_string().into_owned())
}
#[cfg(feature = "experimental")]
pub fn resolve_components(&self) -> components::Bag {
self.0.resolve_components()
}
fn convert_if_necessary<'a>(
&'a self,
value: &impl DateTimeInput<Calendar = AnyCalendar>,
) -> Result<Option<DateTime<icu_calendar::Ref<'a, AnyCalendar>>>, DateTimeError> {
let this_calendar = self.1.kind();
let date_calendar = value.any_calendar_kind();
if Some(this_calendar) != date_calendar {
if date_calendar != Some(AnyCalendarKind::Iso) {
return Err(DateTimeError::MismatchedAnyCalendar(
this_calendar,
date_calendar,
));
}
let date = value.to_iso();
let time = Time::new(
value.hour().unwrap_or_default(),
value.minute().unwrap_or_default(),
value.second().unwrap_or_default(),
value.nanosecond().unwrap_or_default(),
);
let datetime = DateTime::new(date, time).to_any();
let converted = self.1.convert_any_datetime(&datetime);
Ok(Some(converted))
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use icu::calendar::{AnyCalendar, DateTime};
use icu::datetime::{options::length, DateTimeFormatter};
use icu::locid::{locale, Locale};
fn test_format(datetime: &DateTime<AnyCalendar>, locale: Locale, expected: &str) {
let options = length::Bag::from_date_time_style(length::Date::Long, length::Time::Short);
let dtf = DateTimeFormatter::try_new_unstable(
&icu_testdata::unstable(),
&locale.into(),
options.into(),
)
.unwrap();
writeable::assert_writeable_eq!(
dtf.format(datetime).expect("Calendars should match"),
expected
);
}
#[test]
fn test_fallback() {
let datetime = DateTime::try_new_iso_datetime(2022, 4, 5, 12, 33, 44).unwrap();
let datetime = datetime.to_any();
test_format(&datetime, locale!("fr"), "5 avril 2022, 12:33");
test_format(
&datetime,
locale!("fr-u-ca-blahblah"),
"5 avril 2022, 12:33",
);
test_format(
&datetime,
locale!("th-u-ca-buddhist"),
"5 เมษายน 2565 12:33",
);
test_format(&datetime, locale!("th"), "5 เมษายน 2565 12:33");
test_format(
&datetime,
locale!("th-u-ca-gregory"),
"5 เมษายน ค.ศ. 2022 12:33",
);
}
#[test]
#[cfg(feature = "serde")]
fn works_with_default_options() {
assert_eq!(
DateTimeFormatter::try_new_with_buffer_provider(
&icu_testdata::buffer(),
Default::default(),
Default::default(),
)
.unwrap()
.format_to_string(
&DateTime::try_new_iso_datetime(2022, 9, 20, 0, 0, 0)
.unwrap()
.to_any()
)
.unwrap(),
"2022 M09 20 00:00:00"
);
}
}