Skip to content

Commit 9d93fb4

Browse files
Merge branch '6.4' into 7.3
* 6.4: Sync intl scripts [Intl] Add metadata about currencies' validtity dates Bump Symfony version to 6.4.26 Update VERSION for 6.4.25 Update CONTRIBUTORS for 6.4.25 Update CHANGELOG for 6.4.25
2 parents 8cfed83 + 8a54b6e commit 9d93fb4

File tree

216 files changed

+8499
-5653
lines changed

Some content is hidden

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

216 files changed

+8499
-5653
lines changed

CONTRIBUTORS.md

Lines changed: 5659 additions & 5638 deletions
Large diffs are not rendered by default.

src/Symfony/Component/Intl/Data/Bundle/Writer/PhpBundleWriter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ class PhpBundleWriter implements BundleWriterInterface
2525
public function write(string $path, string $locale, mixed $data): void
2626
{
2727
$template = <<<'TEMPLATE'
28-
<?php
28+
<?php
2929
30-
return %s;
30+
return %s;
3131

32-
TEMPLATE;
32+
TEMPLATE;
3333

3434
if ($data instanceof \Traversable) {
3535
$data = iterator_to_array($data);

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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ protected function generateDataForMeta(BundleEntryReaderInterface $reader, strin
102102
$data = [
103103
'Currencies' => $this->currencyCodes,
104104
'Meta' => $this->generateCurrencyMeta($supplementalDataBundle),
105+
'Map' => $this->generateCurrencyMap($supplementalDataBundle),
105106
'Alpha3ToNumeric' => $this->generateAlpha3ToNumericMapping($numericCodesBundle, $this->currencyCodes),
106107
];
107108

@@ -125,6 +126,70 @@ private function generateCurrencyMeta(ArrayAccessibleResourceBundle $supplementa
125126
return iterator_to_array($supplementalDataBundle['CurrencyMeta']);
126127
}
127128

129+
/**
130+
* @return array<string, array>
131+
*/
132+
private function generateCurrencyMap(mixed $supplementalDataBundle): array
133+
{
134+
/**
135+
* @var list<string, list<string, array{from?: string, to?: string, tender?: false}>> $regionsData
136+
*/
137+
$regionsData = [];
138+
139+
foreach ($supplementalDataBundle['CurrencyMap'] as $regionId => $region) {
140+
foreach ($region as $metadata) {
141+
/**
142+
* Note 1: The "to" property (if present) is always greater than "from".
143+
* Note 2: The "to" property may be missing if the currency is still in use.
144+
* Note 3: The "tender" property indicates whether the country legally recognizes the currency within
145+
* its borders. This property is explicitly set to `false` only if that is not the case;
146+
* otherwise, it is `true` by default.
147+
* Note 4: The "from" and "to" dates are not stored as strings; they are stored as a pair of integers.
148+
* Note 5: The "to" property may be missing if "tender" is set to `false`.
149+
*
150+
* @var array{
151+
* from?: array{0: int, 1: int},
152+
* to?: array{0: int, 2: int},
153+
* tender?: bool,
154+
* id: string
155+
* } $metadata
156+
*/
157+
$metadata = iterator_to_array($metadata);
158+
159+
$id = $metadata['id'];
160+
161+
unset($metadata['id']);
162+
163+
if (\array_key_exists($id, self::DENYLIST)) {
164+
continue;
165+
}
166+
167+
if (\array_key_exists('from', $metadata)) {
168+
$metadata['from'] = self::icuPairToDate($metadata['from']);
169+
}
170+
171+
if (\array_key_exists('to', $metadata)) {
172+
$metadata['to'] = self::icuPairToDate($metadata['to']);
173+
}
174+
175+
if (\array_key_exists('tender', $metadata)) {
176+
$metadata['tender'] = filter_var($metadata['tender'], \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE);
177+
178+
if (null === $metadata['tender']) {
179+
throw new \RuntimeException('Unexpected boolean value for tender attribute.');
180+
}
181+
}
182+
183+
$regionsData[$regionId][$id] = $metadata;
184+
}
185+
186+
// Do not exclude countries with no currencies or excluded currencies (e.g. Antartica)
187+
$regionsData[$regionId] ??= [];
188+
}
189+
190+
return $regionsData;
191+
}
192+
128193
private function generateAlpha3ToNumericMapping(ArrayAccessibleResourceBundle $numericCodesBundle, array $currencyCodes): array
129194
{
130195
$alpha3ToNumericMapping = iterator_to_array($numericCodesBundle['codeMap']);
@@ -152,4 +217,41 @@ private function generateNumericToAlpha3Mapping(array $alpha3ToNumericMapping):
152217

153218
return $numericToAlpha3Mapping;
154219
}
220+
221+
/**
222+
* Decodes ICU "date pair" into a DateTimeImmutable (UTC).
223+
*
224+
* ICU stores UDate = milliseconds since 1970-01-01T00:00:00Z in a signed 64-bit.
225+
*
226+
* @param array{0: int, 1: int} $pair
227+
*/
228+
private static function icuPairToDate(array $pair): string
229+
{
230+
[$highBits32, $lowBits32] = $pair;
231+
232+
// Recompose a 64-bit unsigned integer from two 32-bit chunks.
233+
$unsigned64 = ((($highBits32 & 0xFFFFFFFF) << 32) | ($lowBits32 & 0xFFFFFFFF));
234+
235+
// Convert to signed 64-bit (two's complement) if sign bit is set.
236+
if ($unsigned64 >= (1 << 63)) {
237+
$unsigned64 -= (1 << 64);
238+
}
239+
240+
// Split into seconds and milliseconds.
241+
$seconds = intdiv($unsigned64, 1000);
242+
$millisecondsRemainder = $unsigned64 - $seconds * 1000;
243+
244+
// Normalize negative millisecond remainders (e.g., for pre-1970 values)
245+
if (0 > $millisecondsRemainder) {
246+
--$seconds;
247+
}
248+
249+
$datetime = \DateTimeImmutable::createFromFormat('U', $seconds, new \DateTimeZone('Etc/UTC'));
250+
251+
if (false === $datetime) {
252+
throw new \RuntimeException('Unable to parse ICU milliseconds pair.');
253+
}
254+
255+
return $datetime->format('Y-m-d');
256+
}
155257
}

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,14 @@ class RegionDataGenerator extends AbstractDataGenerator
5656
'QO' => true, // Outlying Oceania
5757
'XA' => true, // Pseudo-Accents
5858
'XB' => true, // Pseudo-Bidi
59-
'XK' => true, // Kosovo
6059
// Misc
6160
'ZZ' => true, // Unknown Region
6261
];
6362

63+
private const USER_ASSIGNED = [
64+
'XK' => true, // Kosovo
65+
];
66+
6467
// @see https://en.wikipedia.org/wiki/ISO_3166-1_numeric#Withdrawn_codes
6568
private const WITHDRAWN_CODES = [
6669
128, // Canton and Enderbury Islands
@@ -97,7 +100,7 @@ class RegionDataGenerator extends AbstractDataGenerator
97100

98101
public static function isValidCountryCode(int|string|null $region): bool
99102
{
100-
if (isset(self::DENYLIST[$region])) {
103+
if (isset(self::DENYLIST[$region]) || isset(self::USER_ASSIGNED[$region])) {
101104
return false;
102105
}
103106

@@ -109,6 +112,11 @@ public static function isValidCountryCode(int|string|null $region): bool
109112
return true;
110113
}
111114

115+
public static function isUserAssignedCountryCode(int|string|null $region): bool
116+
{
117+
return isset(self::USER_ASSIGNED[$region]);
118+
}
119+
112120
protected function scanLocales(LocaleScanner $scanner, string $sourceDir): array
113121
{
114122
return $scanner->scanLocales($sourceDir.'/region');
@@ -131,9 +139,7 @@ protected function generateDataForLocale(BundleEntryReaderInterface $reader, str
131139

132140
// isset() on \ResourceBundle returns true even if the value is null
133141
if (isset($localeBundle['Countries']) && null !== $localeBundle['Countries']) {
134-
$data = [
135-
'Names' => $this->generateRegionNames($localeBundle),
136-
];
142+
$data = $this->generateRegionNames($localeBundle);
137143

138144
$this->regionCodes = array_merge($this->regionCodes, array_keys($data['Names']));
139145

@@ -153,23 +159,39 @@ protected function generateDataForMeta(BundleEntryReaderInterface $reader, strin
153159
$metadataBundle = $reader->read($tempDir, 'metadata');
154160

155161
$this->regionCodes = array_unique($this->regionCodes);
156-
157162
sort($this->regionCodes);
158163

159164
$alpha2ToAlpha3 = $this->generateAlpha2ToAlpha3Mapping(array_flip($this->regionCodes), $metadataBundle);
165+
$userAssignedAlpha2ToAlpha3 = $this->generateAlpha2ToAlpha3Mapping(self::USER_ASSIGNED, $metadataBundle);
166+
160167
$alpha3ToAlpha2 = array_flip($alpha2ToAlpha3);
161168
asort($alpha3ToAlpha2);
169+
$userAssignedAlpha3toAlpha2 = array_flip($userAssignedAlpha2ToAlpha3);
170+
asort($userAssignedAlpha3toAlpha2);
162171

163172
$alpha2ToNumeric = $this->generateAlpha2ToNumericMapping(array_flip($this->regionCodes), $metadataBundle);
173+
$userAssignedAlpha2ToNumeric = $this->generateAlpha2ToNumericMapping(self::USER_ASSIGNED, $metadataBundle);
174+
164175
$numericToAlpha2 = [];
165176
foreach ($alpha2ToNumeric as $alpha2 => $numeric) {
166177
// Add underscore prefix to force keys with leading zeros to remain as string keys.
167178
$numericToAlpha2['_'.$numeric] = $alpha2;
168179
}
180+
$userAssignedNumericToAlpha2 = [];
181+
foreach ($userAssignedAlpha2ToNumeric as $alpha2 => $numeric) {
182+
// Add underscore prefix to force keys with leading zeros to remain as string keys.
183+
$userAssignedNumericToAlpha2['_'.$numeric] = $alpha2;
184+
}
169185

170186
asort($numericToAlpha2);
187+
asort($userAssignedNumericToAlpha2);
171188

172189
return [
190+
'UserAssignedRegions' => array_keys(self::USER_ASSIGNED),
191+
'UserAssignedAlpha2ToAlpha3' => $userAssignedAlpha2ToAlpha3,
192+
'UserAssignedAlpha3ToAlpha2' => $userAssignedAlpha3toAlpha2,
193+
'UserAssignedAlpha2ToNumeric' => $userAssignedAlpha2ToNumeric,
194+
'UserAssignedNumericToAlpha2' => $userAssignedNumericToAlpha2,
173195
'Regions' => $this->regionCodes,
174196
'Alpha2ToAlpha3' => $alpha2ToAlpha3,
175197
'Alpha3ToAlpha2' => $alpha3ToAlpha2,
@@ -181,14 +203,19 @@ protected function generateDataForMeta(BundleEntryReaderInterface $reader, strin
181203
protected function generateRegionNames(ArrayAccessibleResourceBundle $localeBundle): array
182204
{
183205
$unfilteredRegionNames = iterator_to_array($localeBundle['Countries']);
184-
$regionNames = [];
206+
$regionNames = ['UserAssignedNames' => [], 'Names' => []];
185207

186208
foreach ($unfilteredRegionNames as $region => $regionName) {
187-
if (!self::isValidCountryCode($region)) {
209+
if (!self::isValidCountryCode($region) && !self::isUserAssignedCountryCode($region)) {
188210
continue;
189211
}
190212

191-
$regionNames[$region] = $regionName;
213+
if (self::isUserAssignedCountryCode($region)) {
214+
$regionNames['UserAssignedNames'][$region] = $regionName;
215+
continue;
216+
}
217+
218+
$regionNames['Names'][$region] = $regionName;
192219
}
193220

194221
return $regionNames;
@@ -204,7 +231,9 @@ private function generateAlpha2ToAlpha3Mapping(array $countries, ArrayAccessible
204231
$country = $data['replacement'];
205232

206233
if (2 === \strlen($country) && 3 === \strlen($alias) && 'overlong' === $data['reason']) {
207-
if (isset(self::PREFERRED_ALPHA2_TO_ALPHA3_MAPPING[$country])) {
234+
if (isset($countries[$country]) && self::isUserAssignedCountryCode($country)) {
235+
$alpha2ToAlpha3[$country] = $alias;
236+
} elseif (isset($countries[$country]) && !self::isUserAssignedCountryCode($country) && isset(self::PREFERRED_ALPHA2_TO_ALPHA3_MAPPING[$country])) {
208237
// Validate to prevent typos
209238
if (!isset($aliases[self::PREFERRED_ALPHA2_TO_ALPHA3_MAPPING[$country]])) {
210239
throw new RuntimeException('The statically set three-letter mapping '.self::PREFERRED_ALPHA2_TO_ALPHA3_MAPPING[$country].' for the country code '.$country.' seems to be invalid. Typo?');

src/Symfony/Component/Intl/Resources/bin/compile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ docker run \
88
-v /tmp/symfony/icu:/tmp \
99
-v $(pwd):/symfony \
1010
-w /symfony \
11-
jakzal/php-intl:8.3-74.1 \
11+
jakzal/php-intl:8.4-77.1 \
1212
php src/Symfony/Component/Intl/Resources/bin/update-data.php

0 commit comments

Comments
 (0)