@@ -139,6 +139,140 @@ public static function forNumericCode(int $numericCode): array
139139 return self ::readEntry (['NumericToAlpha3 ' , (string ) $ numericCode ], 'meta ' );
140140 }
141141
142+ /**
143+ * @param string $country e.g. 'FR'
144+ * @param ?bool $legalTender If the currency must be a legal tender; null to not filter anything
145+ * @param ?bool $active Indicates whether the currency should always be active for the given $date; null to not filter anything
146+ * @param \DateTimeInterface $date The date on which the check will be performed
147+ *
148+ * @return list<string> a list of unique currencies
149+ *
150+ * @throws MissingResourceException if the given $country does not exist
151+ */
152+ public static function forCountry (string $ country , ?bool $ legalTender = true , ?bool $ active = true , \DateTimeInterface $ date = new \DateTimeImmutable ('today ' , new \DateTimeZone ('Etc/UTC ' ))): array
153+ {
154+ $ currencies = [];
155+
156+ foreach (self ::readEntry (['Map ' , $ country ], 'meta ' ) as $ currency => $ currencyMetadata ) {
157+ if (null !== $ legalTender && $ legalTender !== self ::isLegalTender ($ currencyMetadata )) {
158+ continue ;
159+ }
160+
161+ if (null === $ active ) {
162+ $ currencies [] = $ currency ;
163+
164+ continue ;
165+ }
166+
167+ if (self ::isDateActive ($ country , $ currency , $ currencyMetadata , $ date ) !== $ active ) {
168+ continue ;
169+ }
170+
171+ $ currencies [] = $ currency ;
172+ }
173+
174+ return $ currencies ;
175+ }
176+
177+ /**
178+ * @param string $country e.g. 'FR'
179+ * @param string $currency e.g. 'USD'
180+ * @param ?bool $legalTender If the currency must be a legal tender; null to not filter anything
181+ * @param ?bool $active Indicates whether the currency should always be active for the given $date; null to not filter anything
182+ * @param \DateTimeInterface $date The date that will be checked when $active is set to true
183+ */
184+ public static function isValidInCountry (string $ country , string $ currency , ?bool $ legalTender = true , ?bool $ active = true , \DateTimeInterface $ date = new \DateTimeImmutable ('today ' , new \DateTimeZone ('Etc/UTC ' ))): bool
185+ {
186+ if (!self ::exists ($ currency )) {
187+ throw new \InvalidArgumentException ("The currency $ currency does not exist. " );
188+ }
189+
190+ try {
191+ $ currencyMetadata = self ::readEntry (['Map ' , $ country , $ currency ], 'meta ' );
192+ } catch (MissingResourceException ) {
193+ return false ;
194+ }
195+
196+ if (null !== $ legalTender && $ legalTender !== self ::isLegalTender ($ currencyMetadata )) {
197+ return false ;
198+ }
199+
200+ if (null === $ active ) {
201+ return true ;
202+ }
203+
204+ return self ::isDateActive ($ country , $ currency , $ currencyMetadata , $ date ) === $ active ;
205+ }
206+
207+ /**
208+ * @param array{tender?: bool} $currencyMetadata When the `tender` property does not exist, it means it is a legal tender
209+ */
210+ private static function isLegalTender (array $ currencyMetadata ): bool
211+ {
212+ return !\array_key_exists ('tender ' , $ currencyMetadata ) || false !== $ currencyMetadata ['tender ' ];
213+ }
214+
215+ /**
216+ * @param string $country e.g. 'FR'
217+ * @param string $currency e.g. 'USD'
218+ * @param array{from?: string, to?: string} $currencyMetadata
219+ * @param \DateTimeInterface $date The date on which the check will be performed
220+ */
221+ private static function isDateActive (string $ country , string $ currency , array $ currencyMetadata , \DateTimeInterface $ date ): bool
222+ {
223+ if (!\array_key_exists ('from ' , $ currencyMetadata )) {
224+ // Note: currencies that are not legal tender don't have often validity dates.
225+ throw new \RuntimeException ("Cannot check whether the currency $ currency is active or not in $ country because they are no validity dates available. " );
226+ }
227+
228+ $ from = \DateTimeImmutable::createFromFormat ('Y-m-d ' , $ currencyMetadata ['from ' ]);
229+
230+ if (\array_key_exists ('to ' , $ currencyMetadata )) {
231+ $ to = \DateTimeImmutable::createFromFormat ('Y-m-d ' , $ currencyMetadata ['to ' ]);
232+ } else {
233+ $ to = null ;
234+ }
235+
236+ return $ from <= $ date && (null === $ to || $ to >= $ date );
237+ }
238+
239+ /**
240+ * @param string $currency e.g. 'USD'
241+ * @param ?bool $legalTender If the currency must be a legal tender; null to not filter anything
242+ * @param ?bool $active Indicates whether the currency should always be active for the given $date; null to not filter anything
243+ * @param \DateTimeInterface $date the date on which the check will be performed if $active is set to true
244+ */
245+ public static function isValidInAnyCountry (string $ currency , ?bool $ legalTender = true , ?bool $ active = true , \DateTimeInterface $ date = new \DateTimeImmutable ('today ' , new \DateTimeZone ('Etc/UTC ' ))): bool
246+ {
247+ if (!self ::exists ($ currency )) {
248+ throw new \InvalidArgumentException ("The currency $ currency does not exist. " );
249+ }
250+
251+ foreach (self ::readEntry (['Map ' ], 'meta ' ) as $ countryCode => $ country ) {
252+ foreach ($ country as $ currencyCode => $ currencyMetadata ) {
253+ if ($ currencyCode !== $ currency ) {
254+ continue ;
255+ }
256+
257+ if (null !== $ legalTender && $ legalTender !== self ::isLegalTender ($ currencyMetadata )) {
258+ continue ;
259+ }
260+
261+ if (null === $ active ) {
262+ return true ;
263+ }
264+
265+ if (self ::isDateActive ($ countryCode , $ currencyCode , $ currencyMetadata , $ date ) !== $ active ) {
266+ continue ;
267+ }
268+
269+ return true ;
270+ }
271+ }
272+
273+ return false ;
274+ }
275+
142276 protected static function getPath (): string
143277 {
144278 return Intl::getDataDirectory ().'/ ' .Intl::CURRENCY_DIR ;
0 commit comments