Skip to content

Commit 1488e36

Browse files
Merge branch '7.3' into 7.4
* 7.3: Sync intl scripts [Intl] Add metadata about currencies' validtity dates Bump Symfony version to 7.3.4 Update VERSION for 7.3.3 Update CHANGELOG for 7.3.3 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 6d5082c + 1f12b00 commit 1488e36

File tree

3 files changed

+2328
-1
lines changed

3 files changed

+2328
-1
lines changed

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
}

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)