diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ead8ae..158aa03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Changelog Strictly follows [Semantic Versioning 2.0.0.](https://semver.org/) +## v1.14.0 +`2023-12-11`\ +\ +:rocket: Features: +- support for `XCG` currency (future replacement for `ANG`): [`findIso4217Currency()`](DOCUMENTATION.md#findIso4217Currency) and [`findIso4217CurrencyForIso3166Country()`](DOCUMENTATION.md#findIso4217CurrencyForIso3166Country) + +```typescript + { + alpha3Code: "ANG", + currencyName: "Netherlands Antillean Guilder", + numericCode: 532, + minorUnit: 2, + historicalFrom: "2025-07-01" // CHANGED (before: undefined) + }, + // ... other currencies + { + alpha3Code: "XCG", // NEW + currencyName: "Caribbean Guilder", // NEW + numericCode: 532, // NEW + minorUnit: 2, // NEW + introducedIn: "2025-03-31" // NEW + }, +``` +- [`findIso4217CurrencyForIso3166Country()`](DOCUMENTATION.md#findIso4217CurrencyForIso3166Country): new argument `statusForTheDay` has been introduced + ## v1.13.0 `2023-02-21`\ \ diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 5a80b6c..bd31f0e 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -147,7 +147,7 @@ findIso3166Country("XX"); // returns undefined ``` ### findIso4217Currency() -Returns specific currency information. Please check [`getIso4217Currencies()`](DOCUMENTATION.md#getIso4217Currencies) to learn more about the list of currencies. +Returns specific currency information. Please check [`getIso4217Currencies()`](DOCUMENTATION.md#getIso4217Currencies) to learn more about the list of currencies. Please be aware that only `alpha3Code` is unique for all currencies (e.g. `numericCode` is the same for `AND` and `XCG` currencies). ```typescript import { findIso4217Currency } from '@dnvgl/i18n'; @@ -178,6 +178,9 @@ findIso4217CurrencyForIso3166Country("usa"); // returns undefined; invalid code findIso4217CurrencyForIso3166Country("ATA"); // returns undefined; ATA = Antarctica; no universal currency findIso4217CurrencyForIso3166Country("pl"); // returns undefined findIso4217CurrencyForIso3166Country("XX"); // returns undefined +findIso4217CurrencyForIso3166Country("HRV"); // returns { alpha3Code: "EUR", currencyName: "Euro... } +findIso4217CurrencyForIso3166Country("HRV", "2022-05-05"); // returns { alpha3Code: "HRK", currencyName: "Kuna... } + ``` ### formatCountry() diff --git a/__tests__/findIso4217Currency.test.ts b/__tests__/findIso4217Currency.test.ts index be5163f..782b334 100644 --- a/__tests__/findIso4217Currency.test.ts +++ b/__tests__/findIso4217Currency.test.ts @@ -21,4 +21,11 @@ describe('findIso4217Currency', () => { ])('does not find currency by %p code', (code) => { expect(findIso4217Currency(code)).toBeUndefined(); }); + + test('finds currency for numeric code that has two currencies', () => { + expect(findIso4217Currency(532)).toMatchObject({ + numericCode: 532, + minorUnit: 2 + }); + }); }); \ No newline at end of file diff --git a/__tests__/findIso4217CurrencyForIso3166Country.test.ts b/__tests__/findIso4217CurrencyForIso3166Country.test.ts index 6a176ea..f1bf916 100644 --- a/__tests__/findIso4217CurrencyForIso3166Country.test.ts +++ b/__tests__/findIso4217CurrencyForIso3166Country.test.ts @@ -24,18 +24,45 @@ describe('findIso4217CurrencyForIso3166Country.', () => { expect(findIso4217CurrencyForIso3166Country(code)).toBeUndefined(); }); + test('finds old currency for Croatia', () => { + expect(findIso4217CurrencyForIso3166Country("HRV")).toMatchObject( { + alpha3Code: "EUR", + currencyName: "Euro", + numericCode: 978, + minorUnit: 2 + }); + + expect(findIso4217CurrencyForIso3166Country("HRV", "2023-01-01")).toMatchObject( { + alpha3Code: "EUR", + currencyName: "Euro", + numericCode: 978, + minorUnit: 2 + }); + }); + + test('finds new currency for Croatia', () => { + expect(findIso4217CurrencyForIso3166Country("HRV", "2022-05-05")).toMatchObject( { + alpha3Code: "HRK", + currencyName: "Kuna", + numericCode: 191, + minorUnit: 2 + }); + }); + test('Map: all countries are covered', () => { expect(iso3166CountryToIso4217Currency.size).toEqual(iso3166Countries.length); }); test('Map: all currencies are found', () => { iso3166Countries.forEach(country => { - const currencyCode = iso3166CountryToIso4217Currency.get(country.alpha3Code); + const resolver = iso3166CountryToIso4217Currency.get(country.alpha3Code); - if (currencyCode) { - const currency = findIso4217Currency(currencyCode); + if (resolver) { + const currency = findIso4217Currency(typeof resolver === "string" ? resolver : resolver(new Date())); expect(currency).toBeDefined(); - expect(currency?.historicalFrom).toBeUndefined(); + if (!!currency?.historicalFrom) { + expect(new Date(currency.historicalFrom).getTime()).toBeGreaterThan(new Date().getTime()); + } } }) }); diff --git a/__tests__/getIso4217Currencies.test.ts b/__tests__/getIso4217Currencies.test.ts index 978457d..8a11b42 100644 --- a/__tests__/getIso4217Currencies.test.ts +++ b/__tests__/getIso4217Currencies.test.ts @@ -1,11 +1,9 @@ -import { getIso4217Currencies, Iso4217Currency } from "../src"; +import { getIso4217Currencies } from "../src"; import { iso4217Currencies } from "../src/internal/iso4217Currencies"; const isArrayUnique = (arr: any[]) => Array.isArray(arr) && new Set(arr).size === arr.length; -const nameof = (name: keyof T) => name; - describe('getIso4217Currencies', () => { test('all properties are properly defined', () => { const countries = getIso4217Currencies("2022-12-31"); @@ -24,22 +22,39 @@ describe('getIso4217Currencies', () => { expect(countries.find(x => x.alpha3Code === "HRK")).toBeUndefined(); }); - test('Kuna currency is returned', () => { + test('Kuna (HRK) currency is returned', () => { const countries = getIso4217Currencies("2022-12-31"); expect(countries).toHaveLength(181); expect(countries.find(x => x.alpha3Code === "HRK")).toBeDefined(); }); + test('Caribbean Guilder (XCG) and Netherlands Antillean Guilder (ANG) currencies are returned', () => { + const countries = getIso4217Currencies("2025-03-31"); + expect(countries).toHaveLength(181); + expect(countries.find(x => x.alpha3Code === "XCG")).toBeDefined(); + expect(countries.find(x => x.alpha3Code === "ANG")).toBeDefined(); + }); + + test('Netherlands Antillean Guilder (ANG) currency is no longer returned', () => { + const countries = getIso4217Currencies("2025-07-01"); + expect(countries).toHaveLength(180); + expect(countries.find(x => x.alpha3Code === "ANG")).toBeUndefined(); + }); + test('function should return the same reference', () => { const countries1 = getIso4217Currencies("2022-12-31"); const countries2 = getIso4217Currencies("2022-12-31"); expect(countries1 === countries2).toBeTruthy(); }); - test.each([ - [nameof("alpha3Code")], - [nameof("numericCode")], - ])('no duplicates for property %p', (propName) => { - expect(isArrayUnique(iso4217Currencies.map(x => x[propName]))).toBeTruthy(); + test('no duplicates for property alpha3Code', () => { + expect(isArrayUnique(iso4217Currencies.map(x => x.alpha3Code))).toBeTruthy(); + }); + + test('no duplicates for property numericCode', () => { + const currenciesWithoutKnownNumericCodeDuplicates = iso4217Currencies + .filter(x => x.alpha3Code !== "ANG" && x.alpha3Code !== "XCG"); + + expect(isArrayUnique(currenciesWithoutKnownNumericCodeDuplicates.map(x => x.numericCode))).toBeTruthy(); }); }); \ No newline at end of file diff --git a/package.json b/package.json index 2f8c097..79adb5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dnvgl/i18n", - "version": "1.13.0", + "version": "1.14.0", "description": "A set of functions to support multiple languages/cultures in a browser or Node.js", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/src/findIso4217Currency.ts b/src/findIso4217Currency.ts index 8248c0f..3279502 100644 --- a/src/findIso4217Currency.ts +++ b/src/findIso4217Currency.ts @@ -2,6 +2,25 @@ import { iso4217Currencies } from "./internal/iso4217Currencies"; import { Iso4217Alpha3Code, Iso4217Currency, Iso4217NumericCode } from "./types/iso4217"; export function findIso4217Currency(code: Iso4217Alpha3Code | Iso4217NumericCode): Iso4217Currency | undefined { + if (code === 532) { + // HINT: edge case with duplicated numeric code (for some period both are active currencies) + const xcgCurrency = iso4217Currencies.find(x => x.alpha3Code === "XCG")!; + const angCurrency = iso4217Currencies.find(x => x.alpha3Code === "ANG")!; + const now = new Date(); + + if (now >= new Date(xcgCurrency.introducedIn!)) { + if (new Date(angCurrency.historicalFrom!) <= now) { + console?.warn?.("In the current period there are two active currencies with the same currency numerical code." + + " The new XCG currency is returned for this period, which will ultimately replace ANG." + + " To make sure you select the intended currency please use alpha 3 code."); + } + + return xcgCurrency; + } + + return angCurrency; + } + const predicate: (value: Iso4217Currency) => boolean = typeof code === "string" ? x => x.alpha3Code === code diff --git a/src/findIso4217CurrencyForIso3166Country.ts b/src/findIso4217CurrencyForIso3166Country.ts index 53d7154..1275ddf 100644 --- a/src/findIso4217CurrencyForIso3166Country.ts +++ b/src/findIso4217CurrencyForIso3166Country.ts @@ -1,10 +1,14 @@ +import { convertToDate } from "./internal/convertToDate"; import { iso3166Countries } from "./internal/iso3166Countries"; import { iso3166CountryToIso4217Currency } from "./internal/iso3166CountryToIso4217Currency"; import { iso4217Currencies } from "./internal/iso4217Currencies"; +import { DateIsoString } from "./types/dateIsoString"; import { Iso3166Alpha2Code, Iso3166Alpha3Code, Iso3166NumericCode } from "./types/iso3166"; import { Iso4217Currency } from "./types/iso4217"; -export function findIso4217CurrencyForIso3166Country(code: Iso3166Alpha2Code | Iso3166Alpha3Code | Iso3166NumericCode): Iso4217Currency | undefined { +export function findIso4217CurrencyForIso3166Country( + code: Iso3166Alpha2Code | Iso3166Alpha3Code | Iso3166NumericCode, + statusForTheDay?: Date | DateIsoString): Iso4217Currency | undefined { const countryAlpha3Code = typeof code === "string" ? code.length === 3 ? code @@ -15,11 +19,18 @@ export function findIso4217CurrencyForIso3166Country(code: Iso3166Alpha2Code | I return undefined; } - const currencyAlpha3Code = iso3166CountryToIso4217Currency.get(countryAlpha3Code); + const resolver = iso3166CountryToIso4217Currency.get(countryAlpha3Code); - if (!currencyAlpha3Code) { + if (!resolver) { return undefined; } - return iso4217Currencies.find(x => x.alpha3Code === currencyAlpha3Code); + if (typeof resolver === "string") { + return iso4217Currencies.find(x => x.alpha3Code === resolver); + } + + const pointInTime = statusForTheDay ? convertToDate(statusForTheDay) : new Date(); + const resolvedCurrencyAlpha3CodeByDate = resolver(pointInTime); + + return iso4217Currencies.find(x => x.alpha3Code === resolvedCurrencyAlpha3CodeByDate); } \ No newline at end of file diff --git a/src/internal/iso3166CountryToIso4217Currency.ts b/src/internal/iso3166CountryToIso4217Currency.ts index b89976c..f0a8180 100644 --- a/src/internal/iso3166CountryToIso4217Currency.ts +++ b/src/internal/iso3166CountryToIso4217Currency.ts @@ -2,7 +2,10 @@ import { Iso3166Alpha3Code } from "../types/iso3166"; import { Iso4217Alpha3Code } from "../types/iso4217"; /** @internal */ -export const iso3166CountryToIso4217Currency = new Map([ +export type Iso4217Alpha3CodeResolver = Iso4217Alpha3Code |undefined | ((d: Date) => Iso4217Alpha3Code); + +/** @internal */ +export const iso3166CountryToIso4217Currency = new Map([ ["AFG", "AFN"], ["ALA", "EUR"], ["ALB", "ALL"], @@ -58,9 +61,9 @@ export const iso3166CountryToIso4217Currency = new Map d >= new Date("2023-01-01") ? "EUR" : "HRK"], ["CUB", "CUP"], - ["CUW", "ANG"], + ["CUW", (d: Date) => d >= new Date("2025-03-31") ? "XCG" : "ANG"], ["CYP", "EUR"], ["CZE", "CZK"], ["DNK", "DKK"], @@ -204,7 +207,7 @@ export const iso3166CountryToIso4217Currency = new Map d >= new Date("2025-03-31") ? "XCG" : "ANG"], ["SVK", "EUR"], ["SVN", "EUR"], ["SLB", "SBD"], diff --git a/src/internal/iso4217Currencies.ts b/src/internal/iso4217Currencies.ts index 962df7f..ddc5f44 100644 --- a/src/internal/iso4217Currencies.ts +++ b/src/internal/iso4217Currencies.ts @@ -30,7 +30,8 @@ export const iso4217Currencies: Iso4217Currency[] = [ alpha3Code: "ANG", currencyName: "Netherlands Antillean Guilder", numericCode: 532, - minorUnit: 2 + minorUnit: 2, + historicalFrom: "2025-07-01" }, { alpha3Code: "AOA", @@ -1019,6 +1020,13 @@ export const iso4217Currencies: Iso4217Currency[] = [ numericCode: 951, minorUnit: 2 }, + { + alpha3Code: "XCG", + currencyName: "Caribbean Guilder", + numericCode: 532, + minorUnit: 2, + introducedIn: "2025-03-31" + }, { alpha3Code: "XDR", currencyName: "SDR (Special Drawing Right)", diff --git a/yarn.lock b/yarn.lock index 7dd6d71..1856ef1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -609,9 +609,9 @@ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/babel__core@^7.1.14": version "7.20.4" @@ -697,9 +697,9 @@ undici-types "~5.26.4" "@types/node@^20.10.3": - version "20.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030" - integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg== + version "20.10.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.4.tgz#b246fd84d55d5b1b71bf51f964bd514409347198" + integrity sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg== dependencies: undici-types "~5.26.4" @@ -744,20 +744,15 @@ acorn-walk@^8.0.2: integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" + integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== -acorn@^8.1.0, acorn@^8.8.1: +acorn@^8.1.0, acorn@^8.4.1, acorn@^8.8.1: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== -acorn@^8.4.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2584,9 +2579,9 @@ ts-jest@^29.1.1: yargs-parser "^21.0.1" ts-node@^10.8.0: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -2613,9 +2608,9 @@ type-fest@^0.21.3: integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== typescript@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== undici-types@~5.26.4: version "5.26.5"