use crate::provider::time_zones::TimeZoneBcp47Id;
use alloc::borrow::Cow;
use alloc::format;
use alloc::string::String;
use core::fmt;
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 icu_provider::prelude::*;
use writeable::Writeable;
#[cfg(doc)]
use crate::ZonedDateTimeFormatter;
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: TimeZoneFormatterUnit,
}
#[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: TimeZoneFormatter::get_fallback_unit(options.fallback_format),
};
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: TimeZoneFormatter::get_fallback_unit(options.fallback_format),
})
}
#[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::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::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(super) fn get_fallback_unit(fallback_format: FallbackFormat) -> TimeZoneFormatterUnit {
match fallback_format {
FallbackFormat::LocalizedGmt => {
TimeZoneFormatterUnit::LocalizedGmt(LocalizedGmtFormat {})
}
FallbackFormat::Iso8601(format, minutes, seconds) => {
TimeZoneFormatterUnit::Iso8601(Iso8601Format {
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()
}
fn format_time_segment(n: u8, padding: ZeroPadding) -> String {
debug_assert!((0..60).contains(&n));
match padding {
ZeroPadding::On => format!("{n:>02}"),
ZeroPadding::Off => format!("{n}"),
}
}
fn format_offset_hours(
time_zone: &impl TimeZoneInput,
padding: ZeroPadding,
) -> Result<String, DateTimeError> {
if let Some(gmt_offset) = time_zone.gmt_offset() {
Ok(TimeZoneFormatter::format_time_segment(
(gmt_offset.offset_seconds() / 3600).unsigned_abs() as u8,
padding,
))
} else {
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
}
}
fn format_offset_minutes(time_zone: &impl TimeZoneInput) -> Result<String, DateTimeError> {
if let Some(gmt_offset) = time_zone.gmt_offset() {
Ok(TimeZoneFormatter::format_time_segment(
(gmt_offset.offset_seconds() % 3600 / 60).unsigned_abs() as u8,
ZeroPadding::On,
))
} else {
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
}
}
fn format_offset_seconds<W: fmt::Write + ?Sized>(
sink: &mut W,
time_zone: &impl TimeZoneInput,
) -> Result<fmt::Result, DateTimeError> {
if let Some(gmt_offset) = time_zone.gmt_offset() {
Ok(sink.write_str(&TimeZoneFormatter::format_time_segment(
(gmt_offset.offset_seconds() % 3600 % 60).unsigned_abs() as u8,
ZeroPadding::On,
)))
} else {
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
}
}
}
#[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),
LocalizedGmt(LocalizedGmtFormat),
Iso8601(Iso8601Format),
ExemplarCity(ExemplarCityFormat),
}
impl Default for TimeZoneFormatterUnit {
fn default() -> Self {
TimeZoneFormatterUnit::LocalizedGmt(LocalizedGmtFormat {})
}
}
pub(super) trait FormatTimeZone {
fn format<W: fmt::Write + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError>;
}
impl FormatTimeZone for TimeZoneFormatterUnit {
fn format<W: fmt::Write + ?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::LocalizedGmt(unit) => unit.format(sink, time_zone, data_payloads),
Self::Iso8601(unit) => unit.format(sink, time_zone, data_payloads),
Self::ExemplarCity(unit) => unit.format(sink, time_zone, data_payloads),
}
}
}
impl FormatTimeZone for GenericNonLocationLongFormat {
fn format<W: fmt::Write + ?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: fmt::Write + ?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: fmt::Write + ?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: fmt::Write + ?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 FormatTimeZone for LocalizedGmtFormat {
fn format<W: fmt::Write + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
if let Some(gmt_offset) = time_zone.gmt_offset() {
return if gmt_offset.is_zero() {
Ok(sink.write_str(&data_payloads.zone_formats.get().gmt_zero_format.clone()))
} else {
Ok(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",
if let Ok(offset_hours) =
&TimeZoneFormatter::format_offset_hours(time_zone, ZeroPadding::On)
{
offset_hours
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
},
)
.replace(
"mm",
if let Ok(offset_minutes) =
&TimeZoneFormatter::format_offset_minutes(time_zone)
{
offset_minutes
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
},
)
.replace(
'H',
if let Ok(offset_hours) =
&TimeZoneFormatter::format_offset_hours(time_zone, ZeroPadding::Off)
{
offset_hours
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
},
),
))
};
};
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
}
}
impl FormatTimeZone for GenericLocationFormat {
fn format<W: fmt::Write + ?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 FormatTimeZone for Iso8601Format {
fn format<W: fmt::Write + ?Sized>(
&self,
sink: &mut W,
time_zone: &impl TimeZoneInput,
_data_payloads: &TimeZoneDataPayloads,
) -> Result<fmt::Result, DateTimeError> {
if let Some(gmt_offset) = time_zone.gmt_offset() {
if gmt_offset.is_zero()
&& matches!(self.format, IsoFormat::UtcBasic | IsoFormat::UtcExtended)
{
if let Err(e) = sink.write_char('Z') {
return Ok(Err(e));
}
}
let extended_format =
matches!(self.format, IsoFormat::Extended | IsoFormat::UtcExtended);
if let Err(e) = sink.write_char(if gmt_offset.is_positive() { '+' } else { '-' }) {
return Ok(Err(e));
}
if let Ok(offset_hours) =
&TimeZoneFormatter::format_offset_hours(time_zone, ZeroPadding::On)
{
if let Err(e) = sink.write_str(offset_hours) {
return Ok(Err(e));
}
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
}
match self.minutes {
IsoMinutes::Required => {
if extended_format {
if let Err(e) = sink.write_char(':') {
return Ok(Err(e));
}
}
if let Ok(offset_minutes) = &TimeZoneFormatter::format_offset_minutes(time_zone)
{
if let Err(e) = sink.write_str(offset_minutes) {
return Ok(Err(e));
}
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
}
}
IsoMinutes::Optional => {
if gmt_offset.has_minutes() {
if extended_format {
if let Err(e) = sink.write_char(':') {
return Ok(Err(e));
}
}
if let Ok(offset_minutes) =
&TimeZoneFormatter::format_offset_minutes(time_zone)
{
if let Err(e) = sink.write_str(offset_minutes) {
return Ok(Err(e));
}
} else {
return Err(DateTimeError::MissingInputField(Some("gmt_offset")));
}
}
}
}
if let IsoSeconds::Optional = self.seconds {
if gmt_offset.has_seconds() {
if extended_format {
if let Err(e) = sink.write_char(':') {
return Ok(Err(e));
}
}
return TimeZoneFormatter::format_offset_seconds(sink, time_zone);
}
}
return Ok(Ok(()));
};
Err(DateTimeError::MissingInputField(Some("gmt_offset")))
}
}
impl FormatTimeZone for ExemplarCityFormat {
fn format<W: fmt::Write + ?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))
}
}
}
}