use crate::provider::time_zones::TimeZoneBcp47Id;
use alloc::borrow::Cow;
use alloc::string::String;
use core::fmt;
use icu_timezone::GmtOffset;
use smallvec::SmallVec;
use tinystr::tinystr;
use crate::{
error::DateTimeError,
fields::{FieldSymbol, TimeZone},
format::time_zone::FormattedTimeZone,
input::TimeZoneInput,
pattern::{PatternError, PatternItem},
provider::{self, calendar::patterns::PatternPluralsFromPatternsV1Marker},
};
use core::fmt::Write;
use icu_provider::prelude::*;
use writeable::{adapters::CoreWriteAsPartsWrite, Part, Writeable};
fn load<D, P>(
locale: &DataLocale,
destination: &mut Option<DataPayload<D>>,
provider: &P,
) -> Result<(), DateTimeError>
where
D: KeyedDataMarker,
P: DataProvider<D> + ?Sized,
{
if destination.is_none() {
*destination = Some(
provider
.load(DataRequest {
locale,
metadata: Default::default(),
})?
.take_payload()?,
);
}
Ok(())
}
#[derive(Debug)]
pub struct TimeZoneFormatter {
pub(super) locale: DataLocale,
pub(super) data_payloads: TimeZoneDataPayloads,
pub(super) format_units: SmallVec<[TimeZoneFormatterUnit; 3]>,
pub(super) fallback_unit: FallbackTimeZoneFormatterUnit,
}
#[derive(Debug)]
pub(super) struct TimeZoneDataPayloads {
pub(super) zone_formats: DataPayload<provider::time_zones::TimeZoneFormatsV1Marker>,
pub(super) exemplar_cities: Option<DataPayload<provider::time_zones::ExemplarCitiesV1Marker>>,
pub(super) mz_generic_long:
Option<DataPayload<provider::time_zones::MetazoneGenericNamesLongV1Marker>>,
pub(super) mz_generic_short:
Option<DataPayload<provider::time_zones::MetazoneGenericNamesShortV1Marker>>,
pub(super) mz_specific_long:
Option<DataPayload<provider::time_zones::MetazoneSpecificNamesLongV1Marker>>,
pub(super) mz_specific_short:
Option<DataPayload<provider::time_zones::MetazoneSpecificNamesShortV1Marker>>,
}
impl TimeZoneFormatter {
pub(super) fn try_new_for_pattern<ZP>(
zone_provider: &ZP,
locale: &DataLocale,
patterns: DataPayload<PatternPluralsFromPatternsV1Marker>,
options: &TimeZoneFormatterOptions,
) -> Result<Self, DateTimeError>
where
ZP: DataProvider<provider::time_zones::TimeZoneFormatsV1Marker>
+ DataProvider<provider::time_zones::ExemplarCitiesV1Marker>
+ DataProvider<provider::time_zones::MetazoneGenericNamesLongV1Marker>
+ DataProvider<provider::time_zones::MetazoneGenericNamesShortV1Marker>
+ DataProvider<provider::time_zones::MetazoneSpecificNamesLongV1Marker>
+ DataProvider<provider::time_zones::MetazoneSpecificNamesShortV1Marker>
+ ?Sized,
{
let format_units = SmallVec::<[TimeZoneFormatterUnit; 3]>::new();
let data_payloads = TimeZoneDataPayloads {
zone_formats: zone_provider
.load(DataRequest {
locale,
metadata: Default::default(),
})?
.take_payload()?,
exemplar_cities: None,
mz_generic_long: None,
mz_generic_short: None,
mz_specific_long: None,
mz_specific_short: None,
};
let zone_symbols = patterns
.get()
.0
.patterns_iter()
.flat_map(|pattern| pattern.items.iter())
.filter_map(|item| match item {
PatternItem::Field(field) => Some(field),
_ => None,
})
.filter_map(|field| match field.symbol {
FieldSymbol::TimeZone(zone) => Some((field.length.idx(), zone)),
_ => None,
});
let mut tz_format: TimeZoneFormatter = Self {
data_payloads,
locale: locale.clone(),
format_units,
fallback_unit: options.fallback_format.into(),
};
let mut prev_length = None;
let mut prev_symbol = None;
for (length, symbol) in zone_symbols {
if prev_length.is_none() && prev_symbol.is_none() {
prev_length = Some(length);
prev_symbol = Some(symbol);
} else if prev_length != Some(length) && prev_symbol != Some(symbol) {
return Err(DateTimeError::Pattern(PatternError::UnsupportedPluralPivot));
}
match symbol {
TimeZone::LowerZ => match length {
1..=3 => {
tz_format.load_specific_non_location_short(zone_provider)?;
}
4 => {
tz_format.load_specific_non_location_long(zone_provider)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::LowerV => match length {
1 => {
tz_format.load_generic_non_location_short(zone_provider)?;
}
4 => {
tz_format.load_generic_non_location_long(zone_provider)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::UpperV => match length {
1 => (), 2 => (), 3 => {
tz_format.load_exemplar_city_format(zone_provider)?;
}
4 => {
tz_format.load_generic_location_format(zone_provider)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::UpperZ => match length {
1..=3 => {
tz_format.include_iso_8601_format(
IsoFormat::Basic,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
4 => {
tz_format.include_localized_gmt_format()?;
}
5 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcExtended,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::LowerX => match length {
1 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcBasic,
IsoMinutes::Optional,
IsoSeconds::Never,
)?;
}
2 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcBasic,
IsoMinutes::Required,
IsoSeconds::Never,
)?;
}
3 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcExtended,
IsoMinutes::Required,
IsoSeconds::Never,
)?;
}
4 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcBasic,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
5 => {
tz_format.include_iso_8601_format(
IsoFormat::UtcExtended,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::UpperX => match length {
1 => {
tz_format.include_iso_8601_format(
IsoFormat::Basic,
IsoMinutes::Optional,
IsoSeconds::Never,
)?;
}
2 => {
tz_format.include_iso_8601_format(
IsoFormat::Basic,
IsoMinutes::Required,
IsoSeconds::Never,
)?;
}
3 => {
tz_format.include_iso_8601_format(
IsoFormat::Extended,
IsoMinutes::Required,
IsoSeconds::Never,
)?;
}
4 => {
tz_format.include_iso_8601_format(
IsoFormat::Basic,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
5 => {
tz_format.include_iso_8601_format(
IsoFormat::Extended,
IsoMinutes::Required,
IsoSeconds::Optional,
)?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
TimeZone::UpperO => match length {
1..=4 => {
tz_format.include_localized_gmt_format()?;
}
_ => {
return Err(DateTimeError::Pattern(PatternError::FieldLengthInvalid(
FieldSymbol::TimeZone(symbol),
)))
}
},
}
}
Ok(tz_format)
}
icu_provider::gen_any_buffer_data_constructors!(
locale: include,
options: TimeZoneFormatterOptions,
error: DateTimeError,
);
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
pub fn try_new_unstable<P>(
provider: &P,
locale: &DataLocale,
options: TimeZoneFormatterOptions,
) -> Result<Self, DateTimeError>
where
P: DataProvider<provider::time_zones::TimeZoneFormatsV1Marker> + ?Sized,
{
let format_units = SmallVec::<[TimeZoneFormatterUnit; 3]>::new();
let data_payloads = TimeZoneDataPayloads {
zone_formats: provider
.load(DataRequest {
locale,
metadata: Default::default(),
})?
.take_payload()?,
exemplar_cities: None,
mz_generic_long: None,
mz_generic_short: None,
mz_specific_long: None,
mz_specific_short: None,
};
Ok(Self {
data_payloads,
locale: locale.clone(),
format_units,
fallback_unit: options.fallback_format.into(),
})
}
#[cfg(feature = "compiled_data")]
pub fn include_generic_non_location_long(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.load_generic_non_location_long(&crate::provider::Baked)
}
#[cfg(feature = "compiled_data")]
pub fn include_generic_non_location_short(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.load_generic_non_location_short(&crate::provider::Baked)
}
#[cfg(feature = "compiled_data")]
pub fn include_specific_non_location_long(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.load_specific_non_location_long(&crate::provider::Baked)
}
#[cfg(feature = "compiled_data")]
pub fn include_specific_non_location_short(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.load_specific_non_location_short(&crate::provider::Baked)
}
#[cfg(feature = "compiled_data")]
pub fn include_generic_location_format(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.load_generic_location_format(&crate::provider::Baked)
}
pub fn include_localized_gmt_format(
&mut self,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.format_units.push(TimeZoneFormatterUnit::WithFallback(
FallbackTimeZoneFormatterUnit::LocalizedGmt(LocalizedGmtFormat {}),
));
Ok(self)
}
pub fn include_iso_8601_format(
&mut self,
format: IsoFormat,
minutes: IsoMinutes,
seconds: IsoSeconds,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.format_units.push(TimeZoneFormatterUnit::WithFallback(
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
format,
minutes,
seconds,
}),
));
Ok(self)
}
pub fn load_generic_non_location_long<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::MetazoneGenericNamesLongV1Marker> + ?Sized,
{
if self.data_payloads.mz_generic_long.is_none() {
load(
&self.locale,
&mut self.data_payloads.mz_generic_long,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::GenericNonLocationLong(
GenericNonLocationLongFormat {},
));
Ok(self)
}
pub fn load_generic_non_location_short<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::MetazoneGenericNamesShortV1Marker> + ?Sized,
{
if self.data_payloads.mz_generic_short.is_none() {
load(
&self.locale,
&mut self.data_payloads.mz_generic_short,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::GenericNonLocationShort(
GenericNonLocationShortFormat {},
));
Ok(self)
}
pub fn load_specific_non_location_long<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::MetazoneSpecificNamesLongV1Marker> + ?Sized,
{
if self.data_payloads.mz_specific_long.is_none() {
load(
&self.locale,
&mut self.data_payloads.mz_specific_long,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::SpecificNonLocationLong(
SpecificNonLocationLongFormat {},
));
Ok(self)
}
pub fn load_specific_non_location_short<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::MetazoneSpecificNamesShortV1Marker> + ?Sized,
{
if self.data_payloads.mz_specific_short.is_none() {
load(
&self.locale,
&mut self.data_payloads.mz_specific_short,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::SpecificNonLocationShort(
SpecificNonLocationShortFormat {},
));
Ok(self)
}
pub fn load_generic_location_format<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::ExemplarCitiesV1Marker> + ?Sized,
{
if self.data_payloads.exemplar_cities.is_none() {
load(
&self.locale,
&mut self.data_payloads.exemplar_cities,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::GenericLocation(
GenericLocationFormat {},
));
Ok(self)
}
fn load_exemplar_city_format<ZP>(
&mut self,
zone_provider: &ZP,
) -> Result<&mut TimeZoneFormatter, DateTimeError>
where
ZP: DataProvider<provider::time_zones::ExemplarCitiesV1Marker> + ?Sized,
{
if self.data_payloads.exemplar_cities.is_none() {
load(
&self.locale,
&mut self.data_payloads.exemplar_cities,
zone_provider,
)?;
}
self.format_units
.push(TimeZoneFormatterUnit::ExemplarCity(ExemplarCityFormat {}));
Ok(self)
}
#[deprecated(since = "1.3.0", note = "renamed to `include_localized_gmt_format`")]
pub fn load_localized_gmt_format(&mut self) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.include_localized_gmt_format()
}
#[deprecated(since = "1.3.0", note = "renamed to `include_iso_8601_format`")]
pub fn load_iso_8601_format(
&mut self,
format: IsoFormat,
minutes: IsoMinutes,
seconds: IsoSeconds,
) -> Result<&mut TimeZoneFormatter, DateTimeError> {
self.include_iso_8601_format(format, minutes, seconds)
}
pub fn format<'l, T>(&'l self, value: &'l T) -> FormattedTimeZone<'l, T>
where
T: TimeZoneInput,
{
FormattedTimeZone {
time_zone_format: self,
time_zone: value,
}
}
pub fn format_to_string(&self, value: &impl TimeZoneInput) -> String {
self.format(value).write_to_string().into_owned()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::exhaustive_enums)] pub enum IsoFormat {
Basic,
Extended,
UtcBasic,
UtcExtended,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::exhaustive_enums)] pub enum IsoMinutes {
Required,
Optional,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::exhaustive_enums)] pub enum IsoSeconds {
Optional,
Never,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::exhaustive_enums)] pub(crate) enum ZeroPadding {
On,
Off,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
#[derive(Default)]
pub enum FallbackFormat {
Iso8601(IsoFormat, IsoMinutes, IsoSeconds),
#[default]
LocalizedGmt,
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct TimeZoneFormatterOptions {
pub fallback_format: FallbackFormat,
}
impl From<FallbackFormat> for TimeZoneFormatterOptions {
fn from(fallback_format: FallbackFormat) -> Self {
Self { fallback_format }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct GenericNonLocationLongFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct GenericNonLocationShortFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct SpecificNonLocationLongFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct SpecificNonLocationShortFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct GenericLocationFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct LocalizedGmtFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct Iso8601Format {
format: IsoFormat,
minutes: IsoMinutes,
seconds: IsoSeconds,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct ExemplarCityFormat {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum TimeZoneFormatterUnit {
GenericNonLocationLong(GenericNonLocationLongFormat),
GenericNonLocationShort(GenericNonLocationShortFormat),
SpecificNonLocationLong(SpecificNonLocationLongFormat),
SpecificNonLocationShort(SpecificNonLocationShortFormat),
GenericLocation(GenericLocationFormat),
ExemplarCity(ExemplarCityFormat),
WithFallback(FallbackTimeZoneFormatterUnit),
}
impl Default for TimeZoneFormatterUnit {
fn default() -> Self {
TimeZoneFormatterUnit::WithFallback(FallbackTimeZoneFormatterUnit::LocalizedGmt(
LocalizedGmtFormat {},
))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum FallbackTimeZoneFormatterUnit {
LocalizedGmt(LocalizedGmtFormat),
Iso8601(Iso8601Format),
}
impl From<FallbackFormat> for FallbackTimeZoneFormatterUnit {
fn from(value: FallbackFormat) -> Self {
match value {
FallbackFormat::LocalizedGmt => Self::LocalizedGmt(LocalizedGmtFormat {}),
FallbackFormat::Iso8601(format, minutes, seconds) => Self::Iso8601(Iso8601Format {
format,
minutes,
seconds,
}),
}
}
}
pub(super) trait FormatTimeZone {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError>;
}
pub(super) trait FormatTimeZoneWithFallback {
fn format_gmt_offset<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
gmt_offset: GmtOffset,
_data_payloads: &TimeZoneDataPayloads,
) -> fmt::Result;
fn format_with_last_resort_fallback<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<Result<(), DateTimeError>, fmt::Error> {
Ok(if let Some(gmt_offset) = time_zone.gmt_offset() {
self.format_gmt_offset(sink, gmt_offset, data_payloads)?;
Ok(())
} else {
sink.with_part(Part::ERROR, |sink| {
sink.write_str(&data_payloads.zone_formats.get().gmt_offset_fallback)
})?;
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
})
}
}
impl FormatTimeZone for TimeZoneFormatterUnit {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
match self {
Self::GenericNonLocationLong(unit) => unit.format(sink, time_zone, data_payloads),
Self::GenericNonLocationShort(unit) => unit.format(sink, time_zone, data_payloads),
Self::SpecificNonLocationLong(unit) => unit.format(sink, time_zone, data_payloads),
Self::SpecificNonLocationShort(unit) => unit.format(sink, time_zone, data_payloads),
Self::GenericLocation(unit) => unit.format(sink, time_zone, data_payloads),
Self::WithFallback(unit) => unit.format(sink, time_zone, data_payloads),
Self::ExemplarCity(unit) => unit.format(sink, time_zone, data_payloads),
}
}
}
impl FormatTimeZone for FallbackTimeZoneFormatterUnit {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
match self {
Self::LocalizedGmt(unit) => unit.format(sink, time_zone, data_payloads),
Self::Iso8601(unit) => unit.format(sink, time_zone, data_payloads),
}
}
}
impl FormatTimeZoneWithFallback for FallbackTimeZoneFormatterUnit {
fn format_gmt_offset<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
gmt_offset: GmtOffset,
data_payloads: &TimeZoneDataPayloads,
) -> fmt::Result {
match self {
Self::LocalizedGmt(unit) => unit.format_gmt_offset(sink, gmt_offset, data_payloads),
Self::Iso8601(unit) => unit.format_gmt_offset(sink, gmt_offset, data_payloads),
}
}
}
impl FormatTimeZone for GenericNonLocationLongFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_time_zone: Option<&str> = data_payloads
.mz_generic_long
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone
.time_zone_id()
.and_then(|tz| metazones.overrides.get(&tz))
})
.or_else(|| {
data_payloads
.mz_generic_long
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone
.metazone_id()
.and_then(|mz| metazones.defaults.get(&mz))
})
});
match formatted_time_zone {
Some(ftz) => Ok(sink.write_str(ftz)),
None => Err(DateTimeError::UnsupportedOptions),
}
}
}
impl FormatTimeZone for GenericNonLocationShortFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_time_zone: Option<&str> = data_payloads
.mz_generic_short
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone
.time_zone_id()
.and_then(|tz| metazones.overrides.get(&tz))
})
.or_else(|| {
data_payloads
.mz_generic_short
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone
.metazone_id()
.and_then(|mz| metazones.defaults.get(&mz))
})
});
match formatted_time_zone {
Some(ftz) => Ok(sink.write_str(ftz)),
None => Err(DateTimeError::UnsupportedOptions),
}
}
}
impl FormatTimeZone for SpecificNonLocationShortFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_time_zone: Option<&str> = data_payloads
.mz_specific_short
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone.time_zone_id().and_then(|tz| {
time_zone
.zone_variant()
.and_then(|variant| metazones.overrides.get_2d(&tz, &variant))
})
})
.or_else(|| {
data_payloads
.mz_specific_short
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone.metazone_id().and_then(|mz| {
time_zone
.zone_variant()
.and_then(|variant| metazones.defaults.get_2d(&mz, &variant))
})
})
});
match formatted_time_zone {
Some(ftz) => Ok(sink.write_str(ftz)),
None => Err(DateTimeError::UnsupportedOptions),
}
}
}
impl FormatTimeZone for SpecificNonLocationLongFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_time_zone: Option<&str> = data_payloads
.mz_specific_long
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone.time_zone_id().and_then(|tz| {
time_zone
.zone_variant()
.and_then(|variant| metazones.overrides.get_2d(&tz, &variant))
})
})
.or_else(|| {
data_payloads
.mz_specific_long
.as_ref()
.map(|p| p.get())
.and_then(|metazones| {
time_zone.metazone_id().and_then(|mz| {
time_zone
.zone_variant()
.and_then(|variant| metazones.defaults.get_2d(&mz, &variant))
})
})
});
match formatted_time_zone {
Some(ftz) => Ok(sink.write_str(ftz)),
None => Err(DateTimeError::UnsupportedOptions),
}
}
}
impl FormatTimeZoneWithFallback for LocalizedGmtFormat {
fn format_gmt_offset<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
gmt_offset: GmtOffset,
data_payloads: &TimeZoneDataPayloads,
) -> fmt::Result {
if gmt_offset.is_zero() {
sink.write_str(&data_payloads.zone_formats.get().gmt_zero_format.clone())
} else {
let mut scratch = String::new();
sink.write_str(
&data_payloads
.zone_formats
.get()
.gmt_format
.replace(
"{0}",
if gmt_offset.is_positive() {
&data_payloads.zone_formats.get().hour_format.0
} else {
&data_payloads.zone_formats.get().hour_format.1
},
)
.replace("HH", {
scratch.clear();
let _infallible = format_offset_hours(
&mut CoreWriteAsPartsWrite(&mut scratch),
gmt_offset,
ZeroPadding::On,
);
&scratch
})
.replace("mm", {
scratch.clear();
let _infallible = format_offset_minutes(
&mut CoreWriteAsPartsWrite(&mut scratch),
gmt_offset,
);
&scratch
})
.replace('H', {
scratch.clear();
let _infallible = format_offset_hours(
&mut CoreWriteAsPartsWrite(&mut scratch),
gmt_offset,
ZeroPadding::Off,
);
&scratch
}),
)
}
}
}
impl FormatTimeZone for LocalizedGmtFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
Ok(self.format_gmt_offset(
sink,
time_zone
.gmt_offset()
.ok_or(DateTimeError::MissingInputField(Some("gmt_offset")))?,
data_payloads,
))
}
}
impl FormatTimeZone for GenericLocationFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_time_zone: Option<alloc::string::String> = data_payloads
.exemplar_cities
.as_ref()
.map(|p| p.get())
.and_then(|cities| time_zone.time_zone_id().and_then(|id| cities.0.get(&id)))
.map(|location| {
data_payloads
.zone_formats
.get()
.region_format
.replace("{0}", location)
});
match formatted_time_zone {
Some(ftz) => Ok(sink.write_str(&ftz)),
None => Err(DateTimeError::UnsupportedOptions),
}
}
}
impl FormatTimeZoneWithFallback for Iso8601Format {
fn format_gmt_offset<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
gmt_offset: GmtOffset,
_data_payloads: &TimeZoneDataPayloads,
) -> fmt::Result {
if gmt_offset.is_zero()
&& matches!(self.format, IsoFormat::UtcBasic | IsoFormat::UtcExtended)
{
sink.write_char('Z')?;
}
let extended_format = matches!(self.format, IsoFormat::Extended | IsoFormat::UtcExtended);
sink.write_char(if gmt_offset.is_positive() { '+' } else { '-' })?;
format_offset_hours(sink, gmt_offset, ZeroPadding::On)?;
if self.minutes == IsoMinutes::Required
|| (self.minutes == IsoMinutes::Optional && gmt_offset.has_minutes())
{
if extended_format {
sink.write_char(':')?;
}
format_offset_minutes(sink, gmt_offset)?;
}
if self.seconds == IsoSeconds::Optional && gmt_offset.has_seconds() {
if extended_format {
sink.write_char(':')?;
}
format_offset_seconds(sink, gmt_offset)?;
}
Ok(())
}
}
impl FormatTimeZone for Iso8601Format {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
Ok(self.format_gmt_offset(
sink,
time_zone
.gmt_offset()
.ok_or(DateTimeError::MissingInputField(Some("gmt_offset")))?,
data_payloads,
))
}
}
impl FormatTimeZone for ExemplarCityFormat {
fn format<W: writeable::PartsWrite + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
let formatted_exemplar_city = data_payloads
.exemplar_cities
.as_ref()
.map(|p| p.get())
.and_then(|cities| time_zone.time_zone_id().and_then(|id| cities.0.get(&id)));
match formatted_exemplar_city {
Some(ftz) => Ok(sink.write_str(ftz)),
None => {
let formatted_unknown_city = data_payloads
.exemplar_cities
.as_ref()
.map(|p| p.get())
.and_then(|cities| cities.0.get(&TimeZoneBcp47Id(tinystr!(8, "unk"))))
.unwrap_or(&Cow::Borrowed("Unknown"));
Ok(sink.write_str(formatted_unknown_city))
}
}
}
}
fn format_time_segment<W: writeable::PartsWrite + ?Sized>(
sink: &mut W,
n: u8,
padding: ZeroPadding,
) -> fmt::Result {
debug_assert!((0..60).contains(&n));
if padding == ZeroPadding::On && n < 10 {
sink.write_char('0')?;
}
n.write_to(sink)
}
fn format_offset_hours<W: writeable::PartsWrite + ?Sized>(
sink: &mut W,
gmt_offset: GmtOffset,
padding: ZeroPadding,
) -> fmt::Result {
format_time_segment(
sink,
(gmt_offset.offset_seconds() / 3600).unsigned_abs() as u8,
padding,
)
}
fn format_offset_minutes<W: writeable::PartsWrite + ?Sized>(
sink: &mut W,
gmt_offset: GmtOffset,
) -> fmt::Result {
format_time_segment(
sink,
(gmt_offset.offset_seconds() % 3600 / 60).unsigned_abs() as u8,
ZeroPadding::On,
)
}
fn format_offset_seconds<W: writeable::PartsWrite + ?Sized>(
sink: &mut W,
gmt_offset: GmtOffset,
) -> fmt::Result {
format_time_segment(
sink,
(gmt_offset.offset_seconds() % 3600 % 60).unsigned_abs() as u8,
ZeroPadding::On,
)
}