Skip to content

Commit 6f0f29b

Browse files
Add short offset, specific location, and generic partial location formats (#5641)
1 parent 951ca99 commit 6f0f29b

File tree

11 files changed

+843
-585
lines changed

11 files changed

+843
-585
lines changed

components/datetime/src/format/datetime.rs

Lines changed: 156 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ use crate::provider::date_time::{
1212
MonthPlaceholderValue, TimeSymbols, ZoneSymbols,
1313
};
1414
use crate::time_zone::{
15-
Bcp47IdFormat, ExemplarCityFormat, FallbackTimeZoneFormatterUnit, FormatTimeZone,
16-
FormatTimeZoneError, GenericLocationFormat, GenericNonLocationLongFormat,
17-
GenericNonLocationShortFormat, Iso8601Format, LocalizedOffsetFormat,
18-
SpecificNonLocationLongFormat, SpecificNonLocationShortFormat, TimeZoneDataPayloadsBorrowed,
15+
FormatTimeZone, FormatTimeZoneError, Iso8601Format, TimeZoneDataPayloadsBorrowed,
1916
TimeZoneFormatterUnit,
2017
};
2118
use crate::time_zone::{IsoFormat, IsoMinutes, IsoSeconds, ResolvedNeoTimeZoneSkeleton};
@@ -658,7 +655,7 @@ pub(crate) fn try_write_zone<'data, W, ZS>(
658655
field_length: FieldLength,
659656
input: &ExtractedInput,
660657
zone_symbols: Option<&ZS>,
661-
_fdf: Option<&FixedDecimalFormatter>,
658+
fdf: Option<&FixedDecimalFormatter>,
662659
w: &mut W,
663660
) -> Result<Result<(), DateTimeWriteError>, fmt::Error>
664661
where
@@ -671,7 +668,12 @@ where
671668
) -> fmt::Result {
672669
match offset {
673670
Some(offset) => w.with_part(Part::ERROR, |w| {
674-
Iso8601Format::default_for_fallback().format_infallible(w, offset)
671+
Iso8601Format {
672+
format: IsoFormat::Basic,
673+
minutes: IsoMinutes::Required,
674+
seconds: IsoSeconds::Optional,
675+
}
676+
.format_infallible(w, offset)
675677
}),
676678
None => w.with_part(Part::ERROR, |w| "{GMT+?}".write_to(w)),
677679
}
@@ -697,7 +699,7 @@ where
697699
Some(time_zone) => {
698700
let payloads = zs.get_payloads();
699701
let units = select_zone_units(time_zone);
700-
match do_write_zone(units, input, payloads, w)? {
702+
match do_write_zone(units, input, payloads, fdf, w)? {
701703
Ok(()) => Ok(()),
702704
Err(()) => {
703705
write_time_zone_missing(input.offset, w)?;
@@ -712,193 +714,175 @@ where
712714

713715
/// Given a [`ResolvedNeoTimeZoneSkeleton`], select the formatter units
714716
fn select_zone_units(time_zone: ResolvedNeoTimeZoneSkeleton) -> [Option<TimeZoneFormatterUnit>; 3] {
715-
// Select which formatters to try based on the field.
716-
let mut formatters = (
717-
None,
718-
None,
719-
// Friendly Localized offset Format (requires "essentials" data)
720-
Some(TimeZoneFormatterUnit::WithFallback(
721-
FallbackTimeZoneFormatterUnit::LocalizedOffset(LocalizedOffsetFormat {}),
722-
)),
723-
);
724717
match time_zone {
725718
// `z..zzz`
726-
ResolvedNeoTimeZoneSkeleton::SpecificShort => {
727-
formatters.0 = Some(TimeZoneFormatterUnit::SpecificNonLocationShort(
728-
SpecificNonLocationShortFormat {},
729-
));
730-
}
719+
ResolvedNeoTimeZoneSkeleton::SpecificShort => [
720+
Some(TimeZoneFormatterUnit::SpecificNonLocationShort),
721+
Some(TimeZoneFormatterUnit::LocalizedOffsetShort),
722+
None,
723+
],
731724
// `zzzz`
732-
ResolvedNeoTimeZoneSkeleton::SpecificLong => {
733-
formatters.0 = Some(TimeZoneFormatterUnit::SpecificNonLocationLong(
734-
SpecificNonLocationLongFormat {},
735-
));
736-
}
725+
ResolvedNeoTimeZoneSkeleton::SpecificLong => [
726+
Some(TimeZoneFormatterUnit::SpecificNonLocationLong),
727+
Some(TimeZoneFormatterUnit::LocalizedOffsetLong),
728+
None,
729+
],
737730
// 'v'
738-
ResolvedNeoTimeZoneSkeleton::GenericShort => {
739-
formatters.0 = Some(TimeZoneFormatterUnit::GenericNonLocationShort(
740-
GenericNonLocationShortFormat {},
741-
));
742-
formatters.1 = Some(TimeZoneFormatterUnit::GenericLocation(
743-
GenericLocationFormat {},
744-
));
745-
}
731+
ResolvedNeoTimeZoneSkeleton::GenericShort => [
732+
Some(TimeZoneFormatterUnit::GenericNonLocationShort),
733+
Some(TimeZoneFormatterUnit::GenericLocation),
734+
Some(TimeZoneFormatterUnit::LocalizedOffsetShort),
735+
],
746736
// 'vvvv'
747-
ResolvedNeoTimeZoneSkeleton::GenericLong => {
748-
formatters.0 = Some(TimeZoneFormatterUnit::GenericNonLocationLong(
749-
GenericNonLocationLongFormat {},
750-
));
751-
formatters.1 = Some(TimeZoneFormatterUnit::GenericLocation(
752-
GenericLocationFormat {},
753-
));
754-
}
737+
ResolvedNeoTimeZoneSkeleton::GenericLong => [
738+
Some(TimeZoneFormatterUnit::GenericNonLocationLong),
739+
Some(TimeZoneFormatterUnit::GenericLocation),
740+
Some(TimeZoneFormatterUnit::LocalizedOffsetLong),
741+
],
755742
// 'VVV'
756-
ResolvedNeoTimeZoneSkeleton::City => {
757-
formatters.0 = Some(TimeZoneFormatterUnit::ExemplarCity(ExemplarCityFormat {}));
758-
}
743+
ResolvedNeoTimeZoneSkeleton::City => [
744+
Some(TimeZoneFormatterUnit::ExemplarCity),
745+
Some(TimeZoneFormatterUnit::LocalizedOffsetLong),
746+
None,
747+
],
759748
// 'VVVV'
760-
ResolvedNeoTimeZoneSkeleton::Location => {
761-
formatters.0 = Some(TimeZoneFormatterUnit::GenericLocation(
762-
GenericLocationFormat {},
763-
));
764-
}
765-
// `O`
766-
ResolvedNeoTimeZoneSkeleton::OffsetShort => {
767-
// TODO: For now, use the long format. This should be GMT-8
768-
}
769-
// `OOOO`, `ZZZZ`
749+
ResolvedNeoTimeZoneSkeleton::Location => [
750+
Some(TimeZoneFormatterUnit::GenericLocation),
751+
Some(TimeZoneFormatterUnit::LocalizedOffsetLong),
752+
None,
753+
],
754+
// `O`, `ZZZZ`
755+
ResolvedNeoTimeZoneSkeleton::OffsetShort => [
756+
Some(TimeZoneFormatterUnit::LocalizedOffsetShort),
757+
None,
758+
None,
759+
],
760+
// `OOOO`
770761
ResolvedNeoTimeZoneSkeleton::OffsetLong => {
771-
// no-op
762+
[Some(TimeZoneFormatterUnit::LocalizedOffsetLong), None, None]
772763
}
773764
// 'V'
774-
ResolvedNeoTimeZoneSkeleton::Bcp47Id => {
775-
formatters.0 = Some(TimeZoneFormatterUnit::Bcp47Id(Bcp47IdFormat {}));
776-
}
765+
ResolvedNeoTimeZoneSkeleton::Bcp47Id => [
766+
Some(TimeZoneFormatterUnit::Bcp47Id),
767+
Some(TimeZoneFormatterUnit::LocalizedOffsetLong),
768+
None,
769+
],
777770
// 'X'
778-
ResolvedNeoTimeZoneSkeleton::IsoX => {
779-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
780-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
781-
format: IsoFormat::UtcBasic,
782-
minutes: IsoMinutes::Optional,
783-
seconds: IsoSeconds::Never,
784-
}),
785-
));
786-
}
771+
ResolvedNeoTimeZoneSkeleton::IsoX => [
772+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
773+
format: IsoFormat::UtcBasic,
774+
minutes: IsoMinutes::Optional,
775+
seconds: IsoSeconds::Never,
776+
})),
777+
None,
778+
None,
779+
],
787780
// 'XX'
788-
ResolvedNeoTimeZoneSkeleton::IsoXX => {
789-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
790-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
791-
format: IsoFormat::UtcBasic,
792-
minutes: IsoMinutes::Required,
793-
seconds: IsoSeconds::Never,
794-
}),
795-
));
796-
}
781+
ResolvedNeoTimeZoneSkeleton::IsoXX => [
782+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
783+
format: IsoFormat::UtcBasic,
784+
minutes: IsoMinutes::Required,
785+
seconds: IsoSeconds::Never,
786+
})),
787+
None,
788+
None,
789+
],
797790
// 'XXX'
798-
ResolvedNeoTimeZoneSkeleton::IsoXXX => {
799-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
800-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
801-
format: IsoFormat::UtcExtended,
802-
minutes: IsoMinutes::Required,
803-
seconds: IsoSeconds::Never,
804-
}),
805-
));
806-
}
791+
ResolvedNeoTimeZoneSkeleton::IsoXXX => [
792+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
793+
format: IsoFormat::UtcExtended,
794+
minutes: IsoMinutes::Required,
795+
seconds: IsoSeconds::Never,
796+
})),
797+
None,
798+
None,
799+
],
807800
// 'XXXX'
808-
ResolvedNeoTimeZoneSkeleton::IsoXXXX => {
809-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
810-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
811-
format: IsoFormat::UtcBasic,
812-
minutes: IsoMinutes::Required,
813-
seconds: IsoSeconds::Optional,
814-
}),
815-
));
816-
}
801+
ResolvedNeoTimeZoneSkeleton::IsoXXXX => [
802+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
803+
format: IsoFormat::UtcBasic,
804+
minutes: IsoMinutes::Required,
805+
seconds: IsoSeconds::Optional,
806+
})),
807+
None,
808+
None,
809+
],
817810
// 'XXXXX', 'ZZZZZ'
818-
ResolvedNeoTimeZoneSkeleton::IsoXXXXX => {
819-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
820-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
821-
format: IsoFormat::UtcExtended,
822-
minutes: IsoMinutes::Required,
823-
seconds: IsoSeconds::Optional,
824-
}),
825-
));
826-
}
811+
ResolvedNeoTimeZoneSkeleton::IsoXXXXX => [
812+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
813+
format: IsoFormat::UtcExtended,
814+
minutes: IsoMinutes::Required,
815+
seconds: IsoSeconds::Optional,
816+
})),
817+
None,
818+
None,
819+
],
827820
// 'x'
828-
ResolvedNeoTimeZoneSkeleton::Isox => {
829-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
830-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
831-
format: IsoFormat::Basic,
832-
minutes: IsoMinutes::Optional,
833-
seconds: IsoSeconds::Never,
834-
}),
835-
));
836-
}
821+
ResolvedNeoTimeZoneSkeleton::Isox => [
822+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
823+
format: IsoFormat::Basic,
824+
minutes: IsoMinutes::Optional,
825+
seconds: IsoSeconds::Never,
826+
})),
827+
None,
828+
None,
829+
],
837830
// 'xx'
838-
ResolvedNeoTimeZoneSkeleton::Isoxx => {
839-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
840-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
841-
format: IsoFormat::Basic,
842-
minutes: IsoMinutes::Required,
843-
seconds: IsoSeconds::Never,
844-
}),
845-
));
846-
}
831+
ResolvedNeoTimeZoneSkeleton::Isoxx => [
832+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
833+
format: IsoFormat::Basic,
834+
minutes: IsoMinutes::Required,
835+
seconds: IsoSeconds::Never,
836+
})),
837+
None,
838+
None,
839+
],
847840
// 'xxx'
848-
ResolvedNeoTimeZoneSkeleton::Isoxxx => {
849-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
850-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
851-
format: IsoFormat::Extended,
852-
minutes: IsoMinutes::Required,
853-
seconds: IsoSeconds::Never,
854-
}),
855-
));
856-
}
841+
ResolvedNeoTimeZoneSkeleton::Isoxxx => [
842+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
843+
format: IsoFormat::Extended,
844+
minutes: IsoMinutes::Required,
845+
seconds: IsoSeconds::Never,
846+
})),
847+
None,
848+
None,
849+
],
857850
// 'xxxx', 'Z', 'ZZ', 'ZZZ'
858-
ResolvedNeoTimeZoneSkeleton::Isoxxxx => {
859-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
860-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
861-
format: IsoFormat::Basic,
862-
minutes: IsoMinutes::Required,
863-
seconds: IsoSeconds::Optional,
864-
}),
865-
));
866-
}
851+
ResolvedNeoTimeZoneSkeleton::Isoxxxx => [
852+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
853+
format: IsoFormat::Basic,
854+
minutes: IsoMinutes::Required,
855+
seconds: IsoSeconds::Optional,
856+
})),
857+
None,
858+
None,
859+
],
867860
// 'xxxxx', 'ZZZZZ'
868-
ResolvedNeoTimeZoneSkeleton::Isoxxxxx => {
869-
formatters.2 = Some(TimeZoneFormatterUnit::WithFallback(
870-
FallbackTimeZoneFormatterUnit::Iso8601(Iso8601Format {
871-
format: IsoFormat::Extended,
872-
minutes: IsoMinutes::Required,
873-
seconds: IsoSeconds::Optional,
874-
}),
875-
));
876-
}
877-
};
878-
// TODO:
879-
// `VV` "America/Los_Angeles"
880-
// Generic Partial Location: "Pacific Time (Los Angeles)"
881-
// All `x` and `X` formats
882-
[formatters.0, formatters.1, formatters.2]
861+
ResolvedNeoTimeZoneSkeleton::Isoxxxxx => [
862+
Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format {
863+
format: IsoFormat::Extended,
864+
minutes: IsoMinutes::Required,
865+
seconds: IsoSeconds::Optional,
866+
})),
867+
None,
868+
None,
869+
],
870+
}
883871
}
884872

885873
/// Perform the formatting given all of the resolved parameters
886874
fn do_write_zone<W>(
887875
units: [Option<TimeZoneFormatterUnit>; 3],
888876
input: &ExtractedInput,
889877
payloads: TimeZoneDataPayloadsBorrowed,
878+
fdf: Option<&FixedDecimalFormatter>,
890879
w: &mut W,
891880
) -> Result<Result<(), ()>, fmt::Error>
892881
where
893882
W: writeable::PartsWrite + ?Sized,
894883
{
895-
let [mut f0, mut f1, mut f2] = units;
896-
Ok(loop {
897-
let Some(formatter) = f0.take().or_else(|| f1.take()).or_else(|| f2.take()) else {
898-
break Err(());
899-
};
900-
match formatter.format(w, input, payloads)? {
901-
Ok(()) => break Ok(()),
884+
for formatter in units.into_iter().flatten() {
885+
match formatter.format(w, input, payloads, fdf)? {
902886
Err(FormatTimeZoneError::MissingInputField(_)) => {
903887
// The time zone input doesn't have the fields for this formatter.
904888
// TODO: What behavior makes the most sense here?
@@ -916,8 +900,17 @@ where
916900
// We can keep trying other formatters.
917901
continue;
918902
}
903+
Err(FormatTimeZoneError::MissingFixedDecimalFormatter) => {
904+
// We don't have the necessary data for this formatter.
905+
// TODO: What behavior makes the most sense here?
906+
// We can keep trying other formatters.
907+
continue;
908+
}
909+
Ok(()) => return Ok(Ok(())),
919910
}
920-
})
911+
}
912+
913+
Ok(Err(()))
921914
}
922915

923916
#[cfg(test)]

0 commit comments

Comments
 (0)