Skip to content

Commit cbbf8b7

Browse files
minor #31434 [Intl] Revise timezone name generation (ro0NL)
This PR was merged into the 4.3 branch. Discussion ---------- [Intl] Revise timezone name generation | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no | Tests pass? | yes (inlcluding intl-data group) | Fixed tickets | #... <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | symfony/symfony-docs#... <!-- required for new features --> This is the final polishing needed for #31294 :) I've realized it's much easier to de-duplicate by processing fallback locales separate, and then only keep the diff compared to a specific locale. More or less the same approach `LocaleDataGenerator` already follows. I was trying to be clever and filter based on inheritance in a single process; bad idea. Includes https://github.com/ro0NL/symfony/commit/31591d0 (ref #31432) Commits ------- bfdb4ed [Intl] Revise timezone name generation
2 parents 76e884d + bfdb4ed commit cbbf8b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+3900
-6778
lines changed

src/Symfony/Component/Intl/Data/Generator/TimezoneDataGenerator.php

+111-96
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,24 @@
2828
*/
2929
class TimezoneDataGenerator extends AbstractDataGenerator
3030
{
31+
use FallbackTrait;
32+
3133
/**
3234
* Collects all available zone IDs.
3335
*
3436
* @var string[]
3537
*/
3638
private $zoneIds = [];
39+
private $zoneToCountryMapping = [];
40+
private $localeAliases = [];
3741

3842
/**
3943
* {@inheritdoc}
4044
*/
4145
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
4246
{
47+
$this->localeAliases = $scanner->scanAliases($sourceDir.'/locales');
48+
4349
return $scanner->scanLocales($sourceDir.'/zone');
4450
}
4551

@@ -63,50 +69,71 @@ protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $s
6369
protected function preGenerate()
6470
{
6571
$this->zoneIds = [];
72+
$this->zoneToCountryMapping = [];
6673
}
6774

6875
/**
6976
* {@inheritdoc}
7077
*/
7178
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
7279
{
73-
$localeBundle = $reader->read($tempDir, $displayLocale);
80+
if (!$this->zoneToCountryMapping) {
81+
$this->zoneToCountryMapping = self::generateZoneToCountryMapping($reader->read($tempDir, 'windowsZones'));
82+
}
7483

75-
if (isset($localeBundle['zoneStrings']) && null !== $localeBundle['zoneStrings']) {
76-
$localeBundles = [$localeBundle];
77-
$fallback = $displayLocale;
78-
while (null !== ($fallback = Locale::getFallback($fallback))) {
79-
$localeBundles[] = $reader->read($tempDir, $fallback);
80-
}
81-
$metadata = [];
82-
$data = [
83-
'Version' => $localeBundle['Version'],
84-
'Names' => $this->generateZones(
85-
$reader,
86-
$tempDir,
87-
$displayLocale,
88-
$localeBundles,
89-
$metadata
90-
),
91-
];
92-
93-
if (!$data['Names'] && !$metadata) {
94-
return;
95-
}
84+
// Don't generate aliases, as they are resolved during runtime
85+
// Unless an alias is needed as fallback for de-duplication purposes
86+
if (isset($this->localeAliases[$displayLocale]) && !$this->generatingFallback) {
87+
return;
88+
}
9689

97-
$data['Meta'] = $metadata;
90+
$localeBundle = $reader->read($tempDir, $displayLocale);
91+
92+
if (!isset($localeBundle['zoneStrings']) || null === $localeBundle['zoneStrings']) {
93+
return;
94+
}
9895

99-
$this->zoneIds = array_merge($this->zoneIds, array_keys($data['Names']));
96+
$data = [
97+
'Version' => $localeBundle['Version'],
98+
'Names' => $this->generateZones($reader, $tempDir, $displayLocale),
99+
'Meta' => self::generateZoneMetadata($localeBundle),
100+
];
100101

102+
// Don't de-duplicate a fallback locale
103+
// Ensures the display locale can be de-duplicated on itself
104+
if ($this->generatingFallback) {
101105
return $data;
102106
}
107+
108+
// Process again to de-duplicate locales and their fallback locales
109+
// Only keep the differences
110+
$fallback = $this->generateFallbackData($reader, $tempDir, $displayLocale);
111+
if (isset($fallback['Names'])) {
112+
$data['Names'] = array_diff($data['Names'], $fallback['Names']);
113+
}
114+
if (isset($fallback['Meta'])) {
115+
$data['Meta'] = array_diff($data['Meta'], $fallback['Meta']);
116+
}
117+
if (!$data['Names'] && !$data['Meta']) {
118+
return;
119+
}
120+
121+
$this->zoneIds = array_merge($this->zoneIds, array_keys($data['Names']));
122+
123+
return $data;
103124
}
104125

105126
/**
106127
* {@inheritdoc}
107128
*/
108129
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
109130
{
131+
$rootBundle = $reader->read($tempDir, 'root');
132+
133+
return [
134+
'Version' => $rootBundle['Version'],
135+
'Meta' => self::generateZoneMetadata($rootBundle),
136+
];
110137
}
111138

112139
/**
@@ -119,66 +146,21 @@ protected function generateDataForMeta(BundleEntryReaderInterface $reader, $temp
119146
$this->zoneIds = array_unique($this->zoneIds);
120147

121148
sort($this->zoneIds);
149+
ksort($this->zoneToCountryMapping);
122150

123151
$data = [
124152
'Version' => $rootBundle['Version'],
125153
'Zones' => $this->zoneIds,
126-
'ZoneToCountry' => self::generateZoneToCountryMapping($reader->read($tempDir, 'windowsZones')),
154+
'ZoneToCountry' => $this->zoneToCountryMapping,
155+
'CountryToZone' => self::generateCountryToZoneMapping($this->zoneToCountryMapping),
127156
];
128157

129-
$data['CountryToZone'] = self::generateCountryToZoneMapping($data['ZoneToCountry']);
130-
131158
return $data;
132159
}
133160

134-
/**
135-
* @param ArrayAccessibleResourceBundle[] $localeBundles
136-
*/
137-
private function generateZones(BundleEntryReaderInterface $reader, string $tempDir, string $locale, array $localeBundles, array &$metadata = []): array
161+
private function generateZones(BundleEntryReaderInterface $reader, string $tempDir, string $locale): array
138162
{
139163
$typeBundle = $reader->read($tempDir, 'timezoneTypes');
140-
$metaBundle = $reader->read($tempDir, 'metaZones');
141-
$windowsZonesBundle = $reader->read($tempDir, 'windowsZones');
142-
$accessor = static function (ArrayAccessibleResourceBundle $resourceBundle, array $indices) {
143-
$result = $resourceBundle;
144-
foreach ($indices as $indice) {
145-
$result = $result[$indice] ?? null;
146-
}
147-
148-
return $result;
149-
};
150-
$accessor = static function (array $indices, &$inherited = false) use ($localeBundles, $accessor) {
151-
$inherited = false;
152-
foreach ($localeBundles as $i => $localeBundle) {
153-
$nextLocaleBundle = $localeBundles[$i + 1] ?? null;
154-
$result = $accessor($localeBundle, $indices);
155-
if (null !== $result && (null === $nextLocaleBundle || $result !== $accessor($nextLocaleBundle, $indices))) {
156-
$inherited = 0 !== $i;
157-
158-
return $result;
159-
}
160-
}
161-
162-
return null;
163-
};
164-
$regionFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'regionFormat']);
165-
$fallbackFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'fallbackFormat']);
166-
$zoneToCountry = self::generateZoneToCountryMapping($windowsZonesBundle);
167-
$resolveName = function (string $id, string $city = null) use ($reader, $tempDir, $locale, $regionFormat, $fallbackFormat, $zoneToCountry): ?string {
168-
if (isset($zoneToCountry[$id])) {
169-
try {
170-
$country = $reader->readEntry($tempDir.'/region', $locale, ['Countries', $zoneToCountry[$id]]);
171-
} catch (MissingResourceException $e) {
172-
return null;
173-
}
174-
175-
return null === $city ? str_replace('{0}', $country, $regionFormat) : str_replace(['{0}', '{1}'], [$city, $country], $fallbackFormat);
176-
} elseif (null !== $city) {
177-
return str_replace('{0}', $city, $regionFormat);
178-
} else {
179-
return str_replace(['/', '_'], ' ', 0 === strrpos($id, 'Etc/') ? substr($id, 4) : $id);
180-
}
181-
};
182164
$available = [];
183165
foreach ($typeBundle['typeMap']['timezone'] as $zone => $_) {
184166
if ('Etc:Unknown' === $zone || preg_match('~^Etc:GMT[-+]\d+$~', $zone)) {
@@ -188,64 +170,97 @@ private function generateZones(BundleEntryReaderInterface $reader, string $tempD
188170
$available[$zone] = true;
189171
}
190172

173+
$metaBundle = $reader->read($tempDir, 'metaZones');
191174
$metazones = [];
192175
foreach ($metaBundle['metazoneInfo'] as $zone => $info) {
193176
foreach ($info as $metazone) {
194177
$metazones[$zone] = $metazone->get(0);
195178
}
196179
}
197180

198-
$isBase = false === strpos($locale, '_');
181+
$regionFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'regionFormat']);
182+
$fallbackFormat = $reader->readEntry($tempDir, $locale, ['zoneStrings', 'fallbackFormat']);
183+
$resolveName = function (string $id, string $city = null) use ($reader, $tempDir, $locale, $regionFormat, $fallbackFormat): ?string {
184+
// Resolve default name as described per http://cldr.unicode.org/translation/timezones
185+
if (isset($this->zoneToCountryMapping[$id])) {
186+
try {
187+
$country = $reader->readEntry($tempDir.'/region', $locale, ['Countries', $this->zoneToCountryMapping[$id]]);
188+
} catch (MissingResourceException $e) {
189+
return null;
190+
}
191+
192+
$name = str_replace('{0}', $country, $regionFormat);
193+
194+
return null === $city ? $name : str_replace(['{0}', '{1}'], [$city, $name], $fallbackFormat);
195+
}
196+
if (null !== $city) {
197+
return str_replace('{0}', $city, $regionFormat);
198+
}
199+
200+
return null;
201+
};
202+
$accessor = static function (array $indices, array ...$fallbackIndices) use ($locale, $reader, $tempDir) {
203+
foreach (\func_get_args() as $indices) {
204+
try {
205+
return $reader->readEntry($tempDir, $locale, $indices);
206+
} catch (MissingResourceException $e) {
207+
}
208+
}
209+
210+
return null;
211+
};
199212
$zones = [];
200213
foreach (array_keys($available) as $zone) {
201214
// lg: long generic, e.g. "Central European Time"
202215
// ls: long specific (not DST), e.g. "Central European Standard Time"
203216
// ld: long DST, e.g. "Central European Summer Time"
204217
// ec: example city, e.g. "Amsterdam"
205-
$name = $accessor(['zoneStrings', $zone, 'lg'], $nameInherited) ?? $accessor(['zoneStrings', $zone, 'ls'], $nameInherited);
206-
$city = $accessor(['zoneStrings', $zone, 'ec'], $cityInherited);
218+
$name = $accessor(['zoneStrings', $zone, 'lg'], ['zoneStrings', $zone, 'ls']);
219+
$city = $accessor(['zoneStrings', $zone, 'ec']);
207220
$id = str_replace(':', '/', $zone);
208221

209222
if (null === $name && isset($metazones[$zone])) {
210223
$meta = 'meta:'.$metazones[$zone];
211-
$name = $accessor(['zoneStrings', $meta, 'lg'], $nameInherited) ?? $accessor(['zoneStrings', $meta, 'ls'], $nameInherited);
224+
$name = $accessor(['zoneStrings', $meta, 'lg'], ['zoneStrings', $meta, 'ls']);
212225
}
226+
227+
// Infer a default English named city for all locales
228+
// Ensures each timezone ID has a distinctive name
213229
if (null === $city && 0 !== strrpos($zone, 'Etc:') && false !== $i = strrpos($zone, ':')) {
214230
$city = str_replace('_', ' ', substr($zone, $i + 1));
215-
$cityInherited = !$isBase;
216231
}
217-
if ($isBase && null === $name) {
232+
if (null === $name) {
218233
$name = $resolveName($id, $city);
219234
$city = null;
220235
}
221-
if (
222-
($nameInherited && $cityInherited)
223-
|| (null === $name && null === $city)
224-
|| ($nameInherited && null === $city)
225-
|| ($cityInherited && null === $name)
226-
) {
236+
if (null === $name) {
227237
continue;
228238
}
229-
if (null === $name) {
230-
$name = $resolveName($id, $city);
231-
} elseif (null !== $city && false === mb_stripos(str_replace('-', ' ', $name), str_replace('-', ' ', $city))) {
239+
240+
// Ensure no duplicated content is generated
241+
if (null !== $city && false === mb_stripos(str_replace('-', ' ', $name), str_replace('-', ' ', $city))) {
232242
$name = str_replace(['{0}', '{1}'], [$city, $name], $fallbackFormat);
233243
}
234244

235245
$zones[$id] = $name;
236246
}
237247

238-
$gmtFormat = $accessor(['zoneStrings', 'gmtFormat'], $gmtFormatInherited) ?? 'GMT{0}';
239-
if (!$gmtFormatInherited || $isBase) {
240-
$metadata['GmtFormat'] = str_replace('{0}', '%s', $gmtFormat);
241-
}
248+
return $zones;
249+
}
242250

243-
$hourFormat = $accessor(['zoneStrings', 'hourFormat'], $hourFormatInherited) ?? '+HH:mm;-HH:mm';
244-
if (!$hourFormatInherited || $isBase) {
245-
$metadata['HourFormat'] = explode(';', str_replace(['HH', 'mm', 'H', 'm'], ['%02d', '%02d', '%d', '%d'], $hourFormat), 2);
251+
private static function generateZoneMetadata(ArrayAccessibleResourceBundle $localeBundle): array
252+
{
253+
$metadata = [];
254+
if (isset($localeBundle['zoneStrings']['gmtFormat'])) {
255+
$metadata['GmtFormat'] = str_replace('{0}', '%s', $localeBundle['zoneStrings']['gmtFormat']);
256+
}
257+
if (isset($localeBundle['zoneStrings']['hourFormat'])) {
258+
$hourFormat = explode(';', str_replace(['HH', 'mm', 'H', 'm'], ['%02d', '%02d', '%d', '%d'], $localeBundle['zoneStrings']['hourFormat']), 2);
259+
$metadata['HourFormatPos'] = $hourFormat[0];
260+
$metadata['HourFormatNeg'] = $hourFormat[1];
246261
}
247262

248-
return $zones;
263+
return $metadata;
249264
}
250265

251266
private static function generateZoneToCountryMapping(ArrayAccessibleResourceBundle $windowsZoneBundle): array

src/Symfony/Component/Intl/Resources/data/timezones/af.json

+9-15
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"America\/Detroit": "Noord-Amerikaanse oostelike tyd (Detroit)",
100100
"America\/Dominica": "Atlantiese tyd (Dominica)",
101101
"America\/Edmonton": "Noord-Amerikaanse bergtyd (Edmonton)",
102-
"America\/Eirunepe": "Brasilië (Eirunepe)",
102+
"America\/Eirunepe": "Brasilië-tyd (Eirunepe)",
103103
"America\/El_Salvador": "Noord-Amerikaanse sentrale tyd (El Salvador)",
104104
"America\/Fort_Nelson": "Noord-Amerikaanse bergtyd (Fort Nelson)",
105105
"America\/Fortaleza": "Brasilia-tyd (Fortaleza)",
@@ -151,7 +151,7 @@
151151
"America\/Moncton": "Atlantiese tyd (Moncton)",
152152
"America\/Monterrey": "Noord-Amerikaanse sentrale tyd (Monterrey)",
153153
"America\/Montevideo": "Uruguay-tyd (Montevideo)",
154-
"America\/Montreal": "Kanada (Montreal)",
154+
"America\/Montreal": "Kanada-tyd (Montreal)",
155155
"America\/Montserrat": "Atlantiese tyd (Montserrat)",
156156
"America\/Nassau": "Noord-Amerikaanse oostelike tyd (Nassau)",
157157
"America\/New_York": "Noord-Amerikaanse oostelike tyd (New York)",
@@ -176,7 +176,7 @@
176176
"America\/Recife": "Brasilia-tyd (Recife)",
177177
"America\/Regina": "Noord-Amerikaanse sentrale tyd (Regina)",
178178
"America\/Resolute": "Noord-Amerikaanse sentrale tyd (Resolute)",
179-
"America\/Rio_Branco": "Brasilië (Rio Branco)",
179+
"America\/Rio_Branco": "Brasilië-tyd (Rio Branco)",
180180
"America\/Santa_Isabel": "Noordwes-Meksiko-tyd (Santa Isabel)",
181181
"America\/Santarem": "Brasilia-tyd (Santarem)",
182182
"America\/Santiago": "Chili-tyd (Santiago)",
@@ -226,7 +226,7 @@
226226
"Asia\/Bahrain": "Arabiese tyd (Bahrein)",
227227
"Asia\/Baku": "Aserbeidjan-tyd (Bakoe)",
228228
"Asia\/Bangkok": "Indosjina-tyd (Bangkok)",
229-
"Asia\/Barnaul": "Rusland (Barnaul)",
229+
"Asia\/Barnaul": "Rusland-tyd (Barnaul)",
230230
"Asia\/Beirut": "Oos-Europese tyd (Beiroet)",
231231
"Asia\/Bishkek": "Kirgistan-tyd (Bisjkek)",
232232
"Asia\/Brunei": "Broenei Darussalam-tyd",
@@ -288,9 +288,9 @@
288288
"Asia\/Tehran": "Iran-tyd (Tehran)",
289289
"Asia\/Thimphu": "Bhoetan-tyd (Thimphu)",
290290
"Asia\/Tokyo": "Japan-tyd (Tokio)",
291-
"Asia\/Tomsk": "Rusland (Tomsk)",
291+
"Asia\/Tomsk": "Rusland-tyd (Tomsk)",
292292
"Asia\/Ulaanbaatar": "Ulaanbaatar-tyd",
293-
"Asia\/Urumqi": "Sjina (Urumqi)",
293+
"Asia\/Urumqi": "Sjina-tyd (Urumqi)",
294294
"Asia\/Ust-Nera": "Wladiwostok-tyd (Ust-Nera)",
295295
"Asia\/Vientiane": "Indosjina-tyd (Vientiane)",
296296
"Asia\/Vladivostok": "Wladiwostok-tyd",
@@ -341,11 +341,11 @@
341341
"Europe\/Guernsey": "Greenwich-tyd (Guernsey)",
342342
"Europe\/Helsinki": "Oos-Europese tyd (Helsinki)",
343343
"Europe\/Isle_of_Man": "Greenwich-tyd (Eiland Man)",
344-
"Europe\/Istanbul": "Turkye (Istanbul)",
344+
"Europe\/Istanbul": "Turkye-tyd (Istanbul)",
345345
"Europe\/Jersey": "Greenwich-tyd (Jersey)",
346346
"Europe\/Kaliningrad": "Oos-Europese tyd (Kaliningrad)",
347347
"Europe\/Kiev": "Oos-Europese tyd (Kiëf)",
348-
"Europe\/Kirov": "Rusland (Kirov)",
348+
"Europe\/Kirov": "Rusland-tyd (Kirov)",
349349
"Europe\/Lisbon": "Wes-Europese tyd (Lissabon)",
350350
"Europe\/Ljubljana": "Sentraal-Europese tyd (Ljubljana)",
351351
"Europe\/London": "Greenwich-tyd (Londen)",
@@ -436,11 +436,5 @@
436436
"Pacific\/Wake": "Wake-eiland-tyd",
437437
"Pacific\/Wallis": "Wallis en Futuna-tyd (Mata-Utu)"
438438
},
439-
"Meta": {
440-
"GmtFormat": "GMT%s",
441-
"HourFormat": [
442-
"+%02d:%02d",
443-
"-%02d:%02d"
444-
]
445-
}
439+
"Meta": []
446440
}

0 commit comments

Comments
 (0)