@ducatjs/money 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +73 -0
- package/README.md +479 -62
- package/data/cash-rounding.json +123 -2
- package/data/cldr-cash.json +101 -0
- package/data/historic.json +78 -0
- package/data/iso4217-list-three.xml +1185 -0
- package/dist/adyen-fx.d.ts.map +1 -1
- package/dist/adyen-fx.js +22 -4
- package/dist/adyen-fx.js.map +1 -1
- package/dist/boc.d.ts +31 -0
- package/dist/boc.d.ts.map +1 -0
- package/dist/boc.js +114 -0
- package/dist/boc.js.map +1 -0
- package/dist/boe.d.ts +33 -0
- package/dist/boe.d.ts.map +1 -0
- package/dist/boe.js +114 -0
- package/dist/boe.js.map +1 -0
- package/dist/cash.d.ts +30 -1
- package/dist/cash.d.ts.map +1 -1
- package/dist/cash.js +50 -2
- package/dist/cash.js.map +1 -1
- package/dist/convert.d.ts +10 -9
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +13 -3
- package/dist/convert.js.map +1 -1
- package/dist/drizzle/mysql.d.ts +105 -0
- package/dist/drizzle/mysql.d.ts.map +1 -0
- package/dist/drizzle/mysql.js +60 -0
- package/dist/drizzle/mysql.js.map +1 -0
- package/dist/drizzle/pg.d.ts +103 -0
- package/dist/drizzle/pg.d.ts.map +1 -0
- package/dist/drizzle/pg.js +60 -0
- package/dist/drizzle/pg.js.map +1 -0
- package/dist/drizzle/sqlite.d.ts +102 -0
- package/dist/drizzle/sqlite.d.ts.map +1 -0
- package/dist/drizzle/sqlite.js +59 -0
- package/dist/drizzle/sqlite.js.map +1 -0
- package/dist/ecb.d.ts +37 -2
- package/dist/ecb.d.ts.map +1 -1
- package/dist/ecb.js +148 -10
- package/dist/ecb.js.map +1 -1
- package/dist/fed.d.ts +35 -0
- package/dist/fed.d.ts.map +1 -0
- package/dist/fed.js +134 -0
- package/dist/fed.js.map +1 -0
- package/dist/fetch.d.ts +45 -0
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +167 -24
- package/dist/fetch.js.map +1 -1
- package/dist/fn.d.ts +8 -1
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +8 -1
- package/dist/fn.js.map +1 -1
- package/dist/generated/cash.d.ts +2 -0
- package/dist/generated/cash.d.ts.map +1 -1
- package/dist/generated/cash.js +101 -0
- package/dist/generated/cash.js.map +1 -1
- package/dist/generated/historic.d.ts +337 -0
- package/dist/generated/historic.d.ts.map +1 -0
- package/dist/generated/historic.js +66 -0
- package/dist/generated/historic.js.map +1 -0
- package/dist/historic.d.ts +40 -0
- package/dist/historic.d.ts.map +1 -0
- package/dist/historic.js +43 -0
- package/dist/historic.js.map +1 -0
- package/dist/history.d.ts +96 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +213 -0
- package/dist/history.js.map +1 -0
- package/dist/hmrc.d.ts +28 -0
- package/dist/hmrc.d.ts.map +1 -0
- package/dist/hmrc.js +108 -0
- package/dist/hmrc.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kysely.d.ts +120 -0
- package/dist/kysely.d.ts.map +1 -0
- package/dist/kysely.js +309 -0
- package/dist/kysely.js.map +1 -0
- package/dist/money.d.ts +93 -9
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +135 -9
- package/dist/money.js.map +1 -1
- package/dist/parse.d.ts +45 -1
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +290 -3
- package/dist/parse.js.map +1 -1
- package/dist/pg.d.ts +100 -0
- package/dist/pg.d.ts.map +1 -0
- package/dist/pg.js +343 -0
- package/dist/pg.js.map +1 -0
- package/dist/plugins/adyen.d.ts +1 -1
- package/dist/plugins/adyen.d.ts.map +1 -1
- package/dist/plugins/adyen.js.map +1 -1
- package/dist/plugins/all.d.ts +8 -1
- package/dist/plugins/all.d.ts.map +1 -1
- package/dist/plugins/all.js +8 -1
- package/dist/plugins/all.js.map +1 -1
- package/dist/plugins/allocate.d.ts +1 -1
- package/dist/plugins/allocate.d.ts.map +1 -1
- package/dist/plugins/allocate.js.map +1 -1
- package/dist/plugins/braintree.d.ts +1 -1
- package/dist/plugins/braintree.d.ts.map +1 -1
- package/dist/plugins/braintree.js.map +1 -1
- package/dist/plugins/cash.d.ts +1 -1
- package/dist/plugins/cash.d.ts.map +1 -1
- package/dist/plugins/cash.js.map +1 -1
- package/dist/plugins/checkout.d.ts +1 -1
- package/dist/plugins/checkout.d.ts.map +1 -1
- package/dist/plugins/checkout.js.map +1 -1
- package/dist/plugins/convert.d.ts +5 -5
- package/dist/plugins/convert.d.ts.map +1 -1
- package/dist/plugins/convert.js.map +1 -1
- package/dist/plugins/exact.d.ts +1 -1
- package/dist/plugins/exact.d.ts.map +1 -1
- package/dist/plugins/exact.js.map +1 -1
- package/dist/plugins/fixed.d.ts +1 -1
- package/dist/plugins/fixed.d.ts.map +1 -1
- package/dist/plugins/fixed.js.map +1 -1
- package/dist/plugins/format.d.ts +1 -1
- package/dist/plugins/format.d.ts.map +1 -1
- package/dist/plugins/format.js.map +1 -1
- package/dist/plugins/klarna.d.ts +1 -1
- package/dist/plugins/klarna.d.ts.map +1 -1
- package/dist/plugins/klarna.js.map +1 -1
- package/dist/plugins/paypal.d.ts +1 -1
- package/dist/plugins/paypal.d.ts.map +1 -1
- package/dist/plugins/paypal.js +0 -4
- package/dist/plugins/paypal.js.map +1 -1
- package/dist/plugins/prisma.d.ts +1 -1
- package/dist/plugins/prisma.d.ts.map +1 -1
- package/dist/plugins/prisma.js +0 -4
- package/dist/plugins/prisma.js.map +1 -1
- package/dist/plugins/stripe.d.ts +1 -1
- package/dist/plugins/stripe.d.ts.map +1 -1
- package/dist/plugins/stripe.js +0 -4
- package/dist/plugins/stripe.js.map +1 -1
- package/dist/plugins/tax.d.ts +6 -6
- package/dist/plugins/tax.d.ts.map +1 -1
- package/dist/plugins/tax.js.map +1 -1
- package/dist/policy.d.ts +34 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +90 -0
- package/dist/policy.js.map +1 -0
- package/dist/prisma.d.ts +25 -14
- package/dist/prisma.d.ts.map +1 -1
- package/dist/prisma.js +29 -19
- package/dist/prisma.js.map +1 -1
- package/dist/provider.d.ts +21 -7
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +51 -11
- package/dist/provider.js.map +1 -1
- package/dist/rate-text.d.ts +38 -0
- package/dist/rate-text.d.ts.map +1 -0
- package/dist/rate-text.js +143 -0
- package/dist/rate-text.js.map +1 -0
- package/dist/rate.d.ts +61 -5
- package/dist/rate.d.ts.map +1 -1
- package/dist/rate.js +133 -33
- package/dist/rate.js.map +1 -1
- package/dist/rounding.js +1 -1
- package/dist/rounding.js.map +1 -1
- package/dist/schema.d.ts +43 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +77 -0
- package/dist/schema.js.map +1 -0
- package/dist/sql.d.ts +54 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +131 -0
- package/dist/sql.js.map +1 -0
- package/dist/tax.d.ts +13 -9
- package/dist/tax.d.ts.map +1 -1
- package/dist/tax.js +20 -4
- package/dist/tax.js.map +1 -1
- package/dist/testing/arbitraries.d.ts +43 -0
- package/dist/testing/arbitraries.d.ts.map +1 -0
- package/dist/testing/arbitraries.js +98 -0
- package/dist/testing/arbitraries.js.map +1 -0
- package/dist/testing/index.d.ts +67 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +159 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/jest.d.ts +2 -0
- package/dist/testing/jest.d.ts.map +1 -0
- package/dist/testing/jest.js +15 -0
- package/dist/testing/jest.js.map +1 -0
- package/dist/testing/vitest.d.ts +7 -0
- package/dist/testing/vitest.d.ts.map +1 -0
- package/dist/testing/vitest.js +12 -0
- package/dist/testing/vitest.js.map +1 -0
- package/dist/valibot.d.ts +33 -0
- package/dist/valibot.d.ts.map +1 -0
- package/dist/valibot.js +61 -0
- package/dist/valibot.js.map +1 -0
- package/dist/zod.d.ts +36 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +58 -0
- package/dist/zod.js.map +1 -0
- package/package.json +116 -5
- package/src/adyen-fx.ts +25 -4
- package/src/boc.ts +136 -0
- package/src/boe.ts +131 -0
- package/src/cash.ts +70 -2
- package/src/convert.ts +33 -15
- package/src/drizzle/mysql.ts +78 -0
- package/src/drizzle/pg.ts +78 -0
- package/src/drizzle/sqlite.ts +70 -0
- package/src/ecb.ts +179 -13
- package/src/fed.ts +146 -0
- package/src/fetch.ts +207 -28
- package/src/fn.ts +14 -0
- package/src/generated/cash.ts +103 -0
- package/src/generated/historic.ts +67 -0
- package/src/historic.ts +77 -0
- package/src/history.ts +333 -0
- package/src/hmrc.ts +125 -0
- package/src/index.ts +4 -0
- package/src/kysely.ts +588 -0
- package/src/money.ts +210 -12
- package/src/parse.ts +371 -4
- package/src/pg.ts +410 -0
- package/src/plugins/adyen.ts +3 -2
- package/src/plugins/all.ts +10 -0
- package/src/plugins/allocate.ts +3 -2
- package/src/plugins/braintree.ts +3 -2
- package/src/plugins/cash.ts +3 -2
- package/src/plugins/checkout.ts +3 -2
- package/src/plugins/convert.ts +6 -6
- package/src/plugins/exact.ts +3 -2
- package/src/plugins/fixed.ts +3 -2
- package/src/plugins/format.ts +3 -2
- package/src/plugins/klarna.ts +3 -2
- package/src/plugins/paypal.ts +3 -2
- package/src/plugins/prisma.ts +3 -2
- package/src/plugins/stripe.ts +3 -2
- package/src/plugins/tax.ts +11 -11
- package/src/policy.ts +124 -0
- package/src/prisma.ts +91 -10
- package/src/provider.ts +81 -16
- package/src/rate-text.ts +151 -0
- package/src/rate.ts +205 -36
- package/src/rounding.ts +1 -1
- package/src/schema.ts +119 -0
- package/src/sql.ts +178 -0
- package/src/tax.ts +40 -9
- package/src/testing/arbitraries.ts +134 -0
- package/src/testing/index.ts +247 -0
- package/src/testing/jest.ts +18 -0
- package/src/testing/vitest.ts +18 -0
- package/src/valibot.ts +87 -0
- package/src/zod.ts +78 -0
package/src/generated/cash.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface CashRule {
|
|
|
10
10
|
/** false: only secondary sources were found. */
|
|
11
11
|
readonly verified: boolean;
|
|
12
12
|
readonly source: string;
|
|
13
|
+
/** The tie rule the source prescribes, where it isn't halfExpand. */
|
|
14
|
+
readonly rounding?: import('../rounding.js').RoundingMode;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const CASH_RULES: readonly CashRule[] = [
|
|
@@ -371,5 +373,106 @@ export const CASH_RULES: readonly CashRule[] = [
|
|
|
371
373
|
"scope": "cash (permitted, not required; varies by state)",
|
|
372
374
|
"verified": false,
|
|
373
375
|
"source": "https://en.wikipedia.org/wiki/Cash_rounding"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"currency": "AMD",
|
|
379
|
+
"increment": 1000,
|
|
380
|
+
"countries": [
|
|
381
|
+
"AM"
|
|
382
|
+
],
|
|
383
|
+
"scope": "cash (practice)",
|
|
384
|
+
"verified": true,
|
|
385
|
+
"source": "https://old.cba.am/en/SitePages/nccracoinscirculated.aspx"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"currency": "CRC",
|
|
389
|
+
"increment": 1000,
|
|
390
|
+
"countries": [
|
|
391
|
+
"CR"
|
|
392
|
+
],
|
|
393
|
+
"scope": "cash (practice)",
|
|
394
|
+
"verified": true,
|
|
395
|
+
"source": "https://www.bccr.fi.cr/cr/es/noticias/listado-de-noticias/2026/monedas-de-5-10-y-25-del-diseno-anterior-dejaran-de-funcionar-como-medio-de-pago-el-1-de-julio-del-2.html"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"currency": "GYD",
|
|
399
|
+
"increment": 100,
|
|
400
|
+
"countries": [
|
|
401
|
+
"GY"
|
|
402
|
+
],
|
|
403
|
+
"scope": "cash",
|
|
404
|
+
"verified": false,
|
|
405
|
+
"source": "https://bankofguyana.org.gy/bog/images/communications/press_release/Notice%20to%20stop%20minting%20one%20dollar%20coin.pdf"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"currency": "MNT",
|
|
409
|
+
"increment": 100,
|
|
410
|
+
"countries": [
|
|
411
|
+
"MN"
|
|
412
|
+
],
|
|
413
|
+
"scope": "cash",
|
|
414
|
+
"verified": true,
|
|
415
|
+
"source": "https://www.mongolbank.mn/en/p/1416"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"currency": "MRU",
|
|
419
|
+
"increment": 20,
|
|
420
|
+
"countries": [
|
|
421
|
+
"MR"
|
|
422
|
+
],
|
|
423
|
+
"scope": "cash",
|
|
424
|
+
"verified": false,
|
|
425
|
+
"source": "https://cridem.org/media/documents/bcm-presentation-gr-2017.pdf"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
"currency": "PKR",
|
|
429
|
+
"increment": 100,
|
|
430
|
+
"countries": [
|
|
431
|
+
"PK"
|
|
432
|
+
],
|
|
433
|
+
"scope": "cash",
|
|
434
|
+
"verified": true,
|
|
435
|
+
"source": "https://www.sbp.org.pk/press/2014/Coins-15-Sep-2014.pdf"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"currency": "RSD",
|
|
439
|
+
"increment": 100,
|
|
440
|
+
"countries": [
|
|
441
|
+
"RS"
|
|
442
|
+
],
|
|
443
|
+
"scope": "cash (statutory)",
|
|
444
|
+
"verified": true,
|
|
445
|
+
"source": "https://www.nbs.rs/export/sites/NBS_site/documents/propisi/propisi-trz/opticaj_povlacenje_50_para.pdf",
|
|
446
|
+
"rounding": "halfTrunc"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"currency": "TZS",
|
|
450
|
+
"increment": 5000,
|
|
451
|
+
"countries": [
|
|
452
|
+
"TZ"
|
|
453
|
+
],
|
|
454
|
+
"scope": "cash (practice)",
|
|
455
|
+
"verified": false,
|
|
456
|
+
"source": "https://www.bot.go.tz/Publications/Regular/Quarterly%20statistical%20Bulletin/en/2025090409030924.pdf"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"currency": "TWD",
|
|
460
|
+
"increment": 100,
|
|
461
|
+
"countries": [
|
|
462
|
+
"TW"
|
|
463
|
+
],
|
|
464
|
+
"scope": "cash (practice)",
|
|
465
|
+
"verified": true,
|
|
466
|
+
"source": "https://www.cbc.gov.tw/tw/cp-769-26060-37841-1.html"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"currency": "UZS",
|
|
470
|
+
"increment": 5000,
|
|
471
|
+
"countries": [
|
|
472
|
+
"UZ"
|
|
473
|
+
],
|
|
474
|
+
"scope": "cash (practice)",
|
|
475
|
+
"verified": true,
|
|
476
|
+
"source": "https://cbu.uz/en/banknotes-coins/coins/"
|
|
374
477
|
}
|
|
375
478
|
];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-currencies.mjs from data/iso4217-list-three.xml and data/historic.json. Do not edit by hand.
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/** Publication date of the ISO 4217 List Three snapshot. */
|
|
5
|
+
export const ISO_4217_LIST_THREE_PUBLISHED = "2026-01-01";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Withdrawn ISO 4217 codes (with `numeric` and `withdrawn`, the year and month ISO lists) and
|
|
9
|
+
* non-ISO codes (without them), each with the exponent ISO gave it while it was current.
|
|
10
|
+
*/
|
|
11
|
+
export const HISTORIC_CURRENCY_TABLE = {
|
|
12
|
+
ADP: {"name":"Andorran Peseta","numeric":"020","exponent":0,"withdrawn":"2003-07"},
|
|
13
|
+
AFA: {"name":"Afghani","numeric":"004","exponent":2,"withdrawn":"2003-01"},
|
|
14
|
+
ANG: {"name":"Netherlands Antillean Guilder","numeric":"532","exponent":2,"withdrawn":"2025-03"},
|
|
15
|
+
ATS: {"name":"Schilling","numeric":"040","exponent":2,"withdrawn":"2002-03"},
|
|
16
|
+
AYM: {"name":"Azerbaijan Manat","numeric":"945","exponent":2,"withdrawn":"2005-10"},
|
|
17
|
+
AZM: {"name":"Azerbaijanian Manat","numeric":"031","exponent":2,"withdrawn":"2005-12"},
|
|
18
|
+
BEF: {"name":"Belgian Franc","numeric":"056","exponent":0,"withdrawn":"2002-03"},
|
|
19
|
+
BGL: {"name":"Lev","numeric":"100","exponent":2,"withdrawn":"2003-11"},
|
|
20
|
+
BGN: {"name":"Bulgarian Lev","numeric":"975","exponent":2,"withdrawn":"2026-01"},
|
|
21
|
+
BYB: {"name":"Belarusian Ruble","numeric":"112","exponent":0,"withdrawn":"2001-01"},
|
|
22
|
+
BYR: {"name":"Belarusian Ruble","numeric":"974","exponent":0,"withdrawn":"2017-01"},
|
|
23
|
+
CNH: {"name":"Chinese Yuan Renminbi (offshore)","exponent":2},
|
|
24
|
+
CSD: {"name":"Serbian Dinar","numeric":"891","exponent":2,"withdrawn":"2006-10"},
|
|
25
|
+
CUC: {"name":"Peso Convertible","numeric":"931","exponent":2,"withdrawn":"2021-06"},
|
|
26
|
+
CYP: {"name":"Cyprus Pound","numeric":"196","exponent":2,"withdrawn":"2008-01"},
|
|
27
|
+
DEM: {"name":"Deutsche Mark","numeric":"276","exponent":2,"withdrawn":"2002-03"},
|
|
28
|
+
EEK: {"name":"Kroon","numeric":"233","exponent":2,"withdrawn":"2011-01"},
|
|
29
|
+
ESP: {"name":"Spanish Peseta","numeric":"724","exponent":0,"withdrawn":"2002-03"},
|
|
30
|
+
FIM: {"name":"Markka","numeric":"246","exponent":2,"withdrawn":"2002-03"},
|
|
31
|
+
FRF: {"name":"French Franc","numeric":"250","exponent":2,"withdrawn":"2002-03"},
|
|
32
|
+
GHC: {"name":"Cedi","numeric":"288","exponent":2,"withdrawn":"2008-01"},
|
|
33
|
+
GRD: {"name":"Drachma","numeric":"300","exponent":0,"withdrawn":"2002-03"},
|
|
34
|
+
GWP: {"name":"Guinea-Bissau Peso","numeric":"624","exponent":2,"withdrawn":"1997-05"},
|
|
35
|
+
HRK: {"name":"Kuna","numeric":"191","exponent":2,"withdrawn":"2023-01"},
|
|
36
|
+
IEP: {"name":"Irish Pound","numeric":"372","exponent":2,"withdrawn":"2002-03"},
|
|
37
|
+
ITL: {"name":"Italian Lira","numeric":"380","exponent":0,"withdrawn":"2002-03"},
|
|
38
|
+
LTL: {"name":"Lithuanian Litas","numeric":"440","exponent":2,"withdrawn":"2014-12"},
|
|
39
|
+
LUF: {"name":"Luxembourg Franc","numeric":"442","exponent":0,"withdrawn":"2002-03"},
|
|
40
|
+
LVL: {"name":"Latvian Lats","numeric":"428","exponent":2,"withdrawn":"2014-01"},
|
|
41
|
+
MGF: {"name":"Malagasy Franc","numeric":"450","exponent":0,"withdrawn":"2004-12"},
|
|
42
|
+
MRO: {"name":"Ouguiya","numeric":"478","exponent":2,"withdrawn":"2017-12"},
|
|
43
|
+
MTL: {"name":"Maltese Lira","numeric":"470","exponent":2,"withdrawn":"2008-01"},
|
|
44
|
+
MZM: {"name":"Mozambique Metical","numeric":"508","exponent":2,"withdrawn":"2006-06"},
|
|
45
|
+
NLG: {"name":"Netherlands Guilder","numeric":"528","exponent":2,"withdrawn":"2002-03"},
|
|
46
|
+
PTE: {"name":"Portuguese Escudo","numeric":"620","exponent":0,"withdrawn":"2002-03"},
|
|
47
|
+
ROL: {"name":"Old Leu","numeric":"642","exponent":0,"withdrawn":"2005-06"},
|
|
48
|
+
RUR: {"name":"Russian Ruble","numeric":"810","exponent":2,"withdrawn":"2004-01"},
|
|
49
|
+
SDD: {"name":"Sudanese Dinar","numeric":"736","exponent":2,"withdrawn":"2007-07"},
|
|
50
|
+
SIT: {"name":"Tolar","numeric":"705","exponent":2,"withdrawn":"2007-01"},
|
|
51
|
+
SKK: {"name":"Slovak Koruna","numeric":"703","exponent":2,"withdrawn":"2009-01"},
|
|
52
|
+
SLL: {"name":"Leone","numeric":"694","exponent":2,"withdrawn":"2023-12"},
|
|
53
|
+
SRG: {"name":"Surinam Guilder","numeric":"740","exponent":2,"withdrawn":"2003-12"},
|
|
54
|
+
STD: {"name":"Dobra","numeric":"678","exponent":2,"withdrawn":"2017-12"},
|
|
55
|
+
TMM: {"name":"Turkmenistan Manat","numeric":"795","exponent":2,"withdrawn":"2009-01"},
|
|
56
|
+
TPE: {"name":"Timor Escudo","numeric":"626","exponent":0,"withdrawn":"2002-11"},
|
|
57
|
+
TRL: {"name":"Old Turkish Lira","numeric":"792","exponent":0,"withdrawn":"2005-12"},
|
|
58
|
+
USS: {"name":"US Dollar (Same day)","numeric":"998","exponent":2,"withdrawn":"2014-03"},
|
|
59
|
+
VEB: {"name":"Bolivar","numeric":"862","exponent":2,"withdrawn":"2008-01"},
|
|
60
|
+
VEF: {"name":"Bolívar","numeric":"937","exponent":2,"withdrawn":"2018-08"},
|
|
61
|
+
YUM: {"name":"New Dinar","numeric":"891","exponent":2,"withdrawn":"2003-07"},
|
|
62
|
+
ZMK: {"name":"Zambian Kwacha","numeric":"894","exponent":2,"withdrawn":"2012-12"},
|
|
63
|
+
ZWD: {"name":"Zimbabwe Dollar","numeric":"716","exponent":2,"withdrawn":"2008-08"},
|
|
64
|
+
ZWL: {"name":"Zimbabwe Dollar","numeric":"932","exponent":2,"withdrawn":"2024-09"},
|
|
65
|
+
ZWN: {"name":"Zimbabwe Dollar (new)","numeric":"942","exponent":2,"withdrawn":"2006-09"},
|
|
66
|
+
ZWR: {"name":"Zimbabwe Dollar","numeric":"935","exponent":2,"withdrawn":"2009-06"},
|
|
67
|
+
} as const;
|
package/src/historic.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ducatjs/money/historic`: withdrawn ISO 4217 codes and a few non-ISO ones, with the exponent
|
|
3
|
+
* each had and, for ISO codes, the month ISO withdrew it. Nothing is registered until you ask, so
|
|
4
|
+
* the core registry stays ISO 4217 as currently published:
|
|
5
|
+
*
|
|
6
|
+
* defineHistoricCurrencies(['HRK', 'BGN']);
|
|
7
|
+
* money('100', 'HRK'); // 100.00 HRK
|
|
8
|
+
* historicCurrency('HRK')?.withdrawn; // '2023-01'
|
|
9
|
+
*/
|
|
10
|
+
import { defineCurrency } from './currency.js';
|
|
11
|
+
import { HISTORIC_CURRENCY_TABLE, ISO_4217_LIST_THREE_PUBLISHED } from './generated/historic.js';
|
|
12
|
+
|
|
13
|
+
export { ISO_4217_LIST_THREE_PUBLISHED };
|
|
14
|
+
|
|
15
|
+
/** A code in the historic table. */
|
|
16
|
+
export type HistoricCurrencyCode = keyof typeof HISTORIC_CURRENCY_TABLE;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Every historic code, for the `CurrencyCode` type:
|
|
20
|
+
*
|
|
21
|
+
* declare module '@ducatjs/money' {
|
|
22
|
+
* interface CustomCurrencies extends HistoricCurrencies {}
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
export type HistoricCurrencies = { readonly [Code in HistoricCurrencyCode]: true };
|
|
26
|
+
|
|
27
|
+
/** One entry of the historic table. */
|
|
28
|
+
export interface HistoricCurrency {
|
|
29
|
+
readonly code: HistoricCurrencyCode;
|
|
30
|
+
/** The name ISO 4217 List Three gives it (for non-ISO codes, a descriptive name). */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
/** Its minor-unit exponent while it was current: what stored minor units of it mean. */
|
|
33
|
+
readonly exponent: number;
|
|
34
|
+
/** The ISO 4217 numeric code; absent for codes that were never ISO (CNH). */
|
|
35
|
+
readonly numeric?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The year and month ISO 4217 withdrew it ('2023-01'), from List Three. A code withdrawn
|
|
38
|
+
* country by country has the last date. Absent for codes that were never ISO.
|
|
39
|
+
*/
|
|
40
|
+
readonly withdrawn?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const table: Readonly<Record<string, Omit<HistoricCurrency, 'code'>>> = HISTORIC_CURRENCY_TABLE;
|
|
44
|
+
|
|
45
|
+
/** The table entry for a code, or undefined for a code that isn't in it. */
|
|
46
|
+
export function historicCurrency(code: string): HistoricCurrency | undefined {
|
|
47
|
+
// own properties only, so 'toString' and friends are not currencies
|
|
48
|
+
if (!Object.hasOwn(table, code)) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return { code: code as HistoricCurrencyCode, ...table[code]! };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Every entry of the table, by code. */
|
|
55
|
+
export function historicCurrencies(): HistoricCurrency[] {
|
|
56
|
+
return Object.keys(table).map((code) => historicCurrency(code)!);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Registers historic codes with `defineCurrency`, so Money can hold them: all of them, or only
|
|
61
|
+
* `codes`. Registering one again is harmless; a code already defined with another exponent throws,
|
|
62
|
+
* as `defineCurrency` does.
|
|
63
|
+
*/
|
|
64
|
+
export function defineHistoricCurrencies(codes?: readonly HistoricCurrencyCode[]): void {
|
|
65
|
+
const selected: readonly string[] = codes ?? Object.keys(table);
|
|
66
|
+
|
|
67
|
+
// check every code first, so a typo registers nothing
|
|
68
|
+
for (const code of selected) {
|
|
69
|
+
if (!Object.hasOwn(table, code)) {
|
|
70
|
+
throw new RangeError(`${code} is not in the historic currency table`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const code of selected) {
|
|
75
|
+
defineCurrency(code, table[code]!.exponent);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/history.ts
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// `@ducatjs/money/convert/history`: rates as central banks publish them (the other way round, per
|
|
2
|
+
// 100 units, with stripped digits) and dated tables over a range, with lookups that say why they
|
|
3
|
+
// fell back. Opt-in, so that converting at a rate you already hold doesn't pay for it.
|
|
4
|
+
import type { CurrencyInput } from './money.js';
|
|
5
|
+
import {
|
|
6
|
+
rateTable,
|
|
7
|
+
type RateProvider,
|
|
8
|
+
type RateQuery,
|
|
9
|
+
type RateTable,
|
|
10
|
+
type RateTableSpec,
|
|
11
|
+
} from './provider.js';
|
|
12
|
+
|
|
13
|
+
import { ratio, type Ratio } from './decimal.js';
|
|
14
|
+
import { hasEnded, hasStarted, Rate, toIsoString } from './rate.js';
|
|
15
|
+
import type { RateInfo, RateQuote } from './rate.js';
|
|
16
|
+
import { withDecimals } from './rate-text.js';
|
|
17
|
+
|
|
18
|
+
const DAY_MS = 86_400_000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Why a lookup passed over a day: no table that day (a weekend or holiday), the table not yet
|
|
22
|
+
* published at the time asked for, its period over, or the pair not in it (`missing` when the
|
|
23
|
+
* source listed the currency without a value, `notQuoted` when it isn't listed at all).
|
|
24
|
+
*/
|
|
25
|
+
export type FallbackReason = 'noTable' | 'notYetAvailable' | 'expired' | 'missing' | 'notQuoted';
|
|
26
|
+
|
|
27
|
+
/** A day a lookup passed over, and why. */
|
|
28
|
+
export interface Fallback {
|
|
29
|
+
readonly day: string;
|
|
30
|
+
readonly reason: FallbackReason;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PLAIN_DECIMAL = /^\d+(?:\.\d+)?$/;
|
|
34
|
+
|
|
35
|
+
/** The exact rate a quote stands for, in the Rate's own direction. */
|
|
36
|
+
function quoteRatio(quote: RateQuote): Ratio {
|
|
37
|
+
if (!PLAIN_DECIMAL.test(quote.rate)) {
|
|
38
|
+
throw new RangeError(`A quote is a plain decimal string, got ${quote.rate}`);
|
|
39
|
+
}
|
|
40
|
+
const per = quote.per ?? 1;
|
|
41
|
+
if (!Number.isSafeInteger(per) || per < 1) {
|
|
42
|
+
throw new RangeError(`A quote's per is a positive integer, got ${per}`);
|
|
43
|
+
}
|
|
44
|
+
const published = ratio(quote.rate);
|
|
45
|
+
if (quote.inverted === true) {
|
|
46
|
+
return ratio(published.den * BigInt(per), published.num);
|
|
47
|
+
}
|
|
48
|
+
return ratio(published.num, published.den * BigInt(per));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A Rate from a published quote: `per` units and the direction are applied exactly, and the quote
|
|
53
|
+
* is kept on the Rate.
|
|
54
|
+
*
|
|
55
|
+
* rateFromQuote('JPY', 'NOK', { rate: '6.50', per: 100 }) // JPY→NOK 0.065
|
|
56
|
+
* rateFromQuote('USD', 'EUR', { rate: '1.1650', inverted: true }) // USD→EUR 200/233
|
|
57
|
+
*/
|
|
58
|
+
export function rateFromQuote<From extends CurrencyInput, To extends CurrencyInput>(
|
|
59
|
+
from: From,
|
|
60
|
+
to: To,
|
|
61
|
+
quote: RateQuote,
|
|
62
|
+
info?: Omit<RateInfo, 'quote'>,
|
|
63
|
+
): Rate<From, To> {
|
|
64
|
+
const exact = quoteRatio(quote);
|
|
65
|
+
// Stored in one form: per 1 and inverted false are the same as leaving them out, and a quote
|
|
66
|
+
// without them prints its own digits.
|
|
67
|
+
const kept: { rate: string; per?: number; inverted?: boolean } = { rate: quote.rate };
|
|
68
|
+
if (quote.per !== undefined && quote.per !== 1) {
|
|
69
|
+
kept.per = quote.per;
|
|
70
|
+
}
|
|
71
|
+
if (quote.inverted === true) {
|
|
72
|
+
kept.inverted = true;
|
|
73
|
+
}
|
|
74
|
+
return new Rate(from, to, exact, { ...info, quote: kept });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One published rate in a quoted table. */
|
|
78
|
+
export interface QuotedRate {
|
|
79
|
+
/** The rate as published, a decimal string. */
|
|
80
|
+
readonly rate: string;
|
|
81
|
+
/**
|
|
82
|
+
* How many units the published number is for: of `base`, or of the currency when the rate is
|
|
83
|
+
* `inverted` ("NOK per 100 JPY" is `{ rate: '6.50', per: 100, inverted: true }` in a NOK table).
|
|
84
|
+
*/
|
|
85
|
+
readonly per?: number;
|
|
86
|
+
/** Published as base per unit of the currency, the other way round from the table. */
|
|
87
|
+
readonly inverted?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* The decimals the source publishes, for feeds that strip trailing zeros: '1.175' with 4 is
|
|
90
|
+
* kept as '1.1750'.
|
|
91
|
+
*/
|
|
92
|
+
readonly decimals?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A one-base table of published quotes: a decimal string, a QuotedRate, or null (no value). */
|
|
96
|
+
export interface QuotedTableSpec extends Omit<RateTableSpec, 'rates'> {
|
|
97
|
+
readonly rates: Readonly<Record<string, string | QuotedRate | null>>;
|
|
98
|
+
/**
|
|
99
|
+
* Every rate is published as base per unit of the currency (CAD per USD, as the Bank of Canada
|
|
100
|
+
* quotes), unless its entry says otherwise.
|
|
101
|
+
*/
|
|
102
|
+
readonly inverted?: boolean;
|
|
103
|
+
/** The decimals every rate is published with, unless its entry says otherwise. */
|
|
104
|
+
readonly decimals?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A rateTable from rates as a source published them: each is stored exactly in the table's
|
|
109
|
+
* direction, base → currency, and keeps its quote (`rate.quote`).
|
|
110
|
+
*
|
|
111
|
+
* const boc = quotedTable({ base: 'CAD', inverted: true, asOf: '2026-11-06', rates: { USD: '1.3654' } });
|
|
112
|
+
* boc.rate('CAD', 'USD')?.inverse().toString(); // '1.3654'
|
|
113
|
+
*/
|
|
114
|
+
export function quotedTable(spec: QuotedTableSpec): RateTable {
|
|
115
|
+
const rates: Record<string, Rate | null> = {};
|
|
116
|
+
for (const [currency, entry] of Object.entries(spec.rates)) {
|
|
117
|
+
if (entry === null) {
|
|
118
|
+
rates[currency] = null;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
let quoted: QuotedRate;
|
|
122
|
+
if (typeof entry === 'string') {
|
|
123
|
+
quoted = { rate: entry };
|
|
124
|
+
} else {
|
|
125
|
+
quoted = entry;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let text = quoted.rate.trim();
|
|
129
|
+
const decimals = quoted.decimals ?? spec.decimals;
|
|
130
|
+
if (decimals !== undefined) {
|
|
131
|
+
text = withDecimals(text, decimals);
|
|
132
|
+
}
|
|
133
|
+
const quote: { rate: string; per?: number; inverted?: boolean } = { rate: text };
|
|
134
|
+
if (quoted.per !== undefined) {
|
|
135
|
+
quote.per = quoted.per;
|
|
136
|
+
}
|
|
137
|
+
if (quoted.inverted ?? spec.inverted ?? false) {
|
|
138
|
+
quote.inverted = true;
|
|
139
|
+
}
|
|
140
|
+
rates[currency] = rateFromQuote(spec.base, currency, quote, spec);
|
|
141
|
+
}
|
|
142
|
+
return rateTable({ ...spec, rates });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The UTC date ('2026-11-06') of a date, an instant (any offset) or a Date. */
|
|
146
|
+
function dayOf(time: string | Date): string {
|
|
147
|
+
return toIsoString(time).slice(0, 10);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Options for rateHistory(). */
|
|
151
|
+
export interface RateHistoryOptions {
|
|
152
|
+
/**
|
|
153
|
+
* How many days before the day asked for to look back for a daily table (weekends, holidays,
|
|
154
|
+
* currencies missing that day). Default 4, which covers Good Friday to Easter Monday. A table
|
|
155
|
+
* valid for a period (`validUntil`) answers for its whole period instead.
|
|
156
|
+
*/
|
|
157
|
+
readonly lookback?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A rate lookup with its reasons: the rate, or undefined, and the days passed over. */
|
|
161
|
+
export interface RateLookup<From extends string = string, To extends string = string> {
|
|
162
|
+
readonly rate: Rate<From, To> | undefined;
|
|
163
|
+
/** Days passed over before the answer (or before giving up), newest first. */
|
|
164
|
+
readonly skipped: readonly Fallback[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Dated tables held in memory, answering any day synchronously. */
|
|
168
|
+
export interface RateHistory extends RateProvider {
|
|
169
|
+
/** The dated tables, newest first. */
|
|
170
|
+
readonly tables: readonly RateTable[];
|
|
171
|
+
/** The same lookup as `rate`, reporting the days it passed over and why. */
|
|
172
|
+
lookup<From extends string, To extends string>(
|
|
173
|
+
from: From,
|
|
174
|
+
to: To,
|
|
175
|
+
query?: RateQuery,
|
|
176
|
+
): RateLookup<From, To>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Why a table can't answer the pair at the time asked for, or undefined when it can. The table's
|
|
181
|
+
* own dates are checked first; a table built from a list of Rates has none, so its rate's are.
|
|
182
|
+
*/
|
|
183
|
+
function passOver(
|
|
184
|
+
table: RateTable,
|
|
185
|
+
from: string,
|
|
186
|
+
to: string,
|
|
187
|
+
query: RateQuery | undefined,
|
|
188
|
+
periodEnded: boolean,
|
|
189
|
+
): FallbackReason | undefined {
|
|
190
|
+
const asOf = query?.asOf;
|
|
191
|
+
if (periodEnded) {
|
|
192
|
+
return 'expired';
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
asOf !== undefined &&
|
|
196
|
+
table.availableAt !== undefined &&
|
|
197
|
+
!hasStarted(table.availableAt, asOf)
|
|
198
|
+
) {
|
|
199
|
+
return 'notYetAvailable';
|
|
200
|
+
}
|
|
201
|
+
if (table.rate(from, to, query) !== undefined) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (table.missing?.includes(from) === true || table.missing?.includes(to) === true) {
|
|
206
|
+
return 'missing';
|
|
207
|
+
}
|
|
208
|
+
const held = table.rates.find((rate) => rate.from === from && rate.to === to);
|
|
209
|
+
if (held !== undefined && asOf !== undefined) {
|
|
210
|
+
if (held.isExpired(asOf)) {
|
|
211
|
+
return 'expired';
|
|
212
|
+
}
|
|
213
|
+
if (!held.isAvailable(asOf)) {
|
|
214
|
+
return 'notYetAvailable';
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return 'notQuoted';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The ISO date `count` days before `day`. */
|
|
221
|
+
function daysBefore(day: string, count: number): string {
|
|
222
|
+
return new Date(Date.parse(`${day}T00:00:00Z`) - count * DAY_MS).toISOString().slice(0, 10);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A provider over many dated tables, such as a date range loaded in one call: for each lookup it
|
|
227
|
+
* takes the latest table at or before `asOf` that has the pair, published by then and still
|
|
228
|
+
* valid, looking back up to `lookback` days. Each currency pair falls back on its own, so a
|
|
229
|
+
* currency missing on a holiday comes from the day before while the others stay current; the
|
|
230
|
+
* Rate's `asOf` says which day it is from. Without an `asOf`, the newest table with the pair
|
|
231
|
+
* answers. `lookup` reports why a lookup fell back.
|
|
232
|
+
*
|
|
233
|
+
* const history = rateHistory(parseEcbXml(xml90Days));
|
|
234
|
+
* history.rate('EUR', 'USD', { asOf: '2026-11-08' }); // Sunday: Friday's rate
|
|
235
|
+
* history.lookup('EUR', 'USD', { asOf: '2026-11-08' }).skipped;
|
|
236
|
+
* // [{ day: '2026-11-08', reason: 'noTable' }, { day: '2026-11-07', reason: 'noTable' }]
|
|
237
|
+
*/
|
|
238
|
+
export function rateHistory(
|
|
239
|
+
tables: readonly RateTable[],
|
|
240
|
+
options?: RateHistoryOptions,
|
|
241
|
+
): RateHistory {
|
|
242
|
+
const lookback = options?.lookback ?? 4;
|
|
243
|
+
if (!Number.isSafeInteger(lookback) || lookback < 0) {
|
|
244
|
+
throw new RangeError(`lookback must be a non-negative integer, got ${lookback}`);
|
|
245
|
+
}
|
|
246
|
+
const dated: { day: string; table: RateTable }[] = [];
|
|
247
|
+
for (const table of tables) {
|
|
248
|
+
if (table.asOf === undefined) {
|
|
249
|
+
throw new RangeError('rateHistory needs tables with an asOf');
|
|
250
|
+
}
|
|
251
|
+
dated.push({ day: dayOf(table.asOf), table });
|
|
252
|
+
}
|
|
253
|
+
// Newest first; a stable sort keeps the caller's order within a day.
|
|
254
|
+
dated.sort((entry, other) => (other.day < entry.day ? -1 : Number(other.day > entry.day)));
|
|
255
|
+
|
|
256
|
+
const hasPeriods = dated.some((entry) => entry.table.validUntil !== undefined);
|
|
257
|
+
|
|
258
|
+
function lookup<From extends string, To extends string>(
|
|
259
|
+
from: From,
|
|
260
|
+
to: To,
|
|
261
|
+
query?: RateQuery,
|
|
262
|
+
): RateLookup<From, To> {
|
|
263
|
+
const asOf = query?.asOf;
|
|
264
|
+
const skipped: Fallback[] = [];
|
|
265
|
+
let firstDay: string | undefined;
|
|
266
|
+
if (asOf !== undefined) {
|
|
267
|
+
firstDay = dayOf(asOf);
|
|
268
|
+
} else {
|
|
269
|
+
firstDay = dated[0]?.day;
|
|
270
|
+
}
|
|
271
|
+
if (firstDay === undefined) {
|
|
272
|
+
return { rate: undefined, skipped };
|
|
273
|
+
}
|
|
274
|
+
const lastDay = daysBefore(firstDay, lookback);
|
|
275
|
+
// The newest day of the lookback window not yet accounted for by a daily table.
|
|
276
|
+
let expectedDay = firstDay;
|
|
277
|
+
|
|
278
|
+
for (const { day, table } of dated) {
|
|
279
|
+
if (day > firstDay) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const periodEnded =
|
|
283
|
+
asOf !== undefined && table.validUntil !== undefined && hasEnded(table.validUntil, asOf);
|
|
284
|
+
const coversPeriod = table.validUntil !== undefined && !periodEnded;
|
|
285
|
+
if (day < lastDay && !coversPeriod) {
|
|
286
|
+
// Older daily tables are out of the window; an older period table may still cover it.
|
|
287
|
+
if (!hasPeriods) {
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Days between the last table and this one had no table. A period table that answers
|
|
294
|
+
// covers them, so for one they count only once it turns out not to answer.
|
|
295
|
+
// An older period table comes after every day of the window, so all of them are gaps.
|
|
296
|
+
const inWindow = day >= lastDay;
|
|
297
|
+
const accountedTo = inWindow ? day : daysBefore(lastDay, 1);
|
|
298
|
+
const gaps: Fallback[] = [];
|
|
299
|
+
for (let gap = expectedDay; gap > accountedTo; gap = daysBefore(gap, 1)) {
|
|
300
|
+
gaps.push({ day: gap, reason: 'noTable' });
|
|
301
|
+
}
|
|
302
|
+
if (!coversPeriod) {
|
|
303
|
+
skipped.push(...gaps);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const reason = passOver(table, from, to, query, periodEnded);
|
|
307
|
+
if (reason === undefined) {
|
|
308
|
+
return { rate: table.rate(from, to, query) as Rate<From, To>, skipped };
|
|
309
|
+
}
|
|
310
|
+
if (coversPeriod) {
|
|
311
|
+
skipped.push(...gaps);
|
|
312
|
+
}
|
|
313
|
+
skipped.push({ day, reason });
|
|
314
|
+
if (accountedTo <= expectedDay) {
|
|
315
|
+
expectedDay = inWindow ? daysBefore(day, 1) : accountedTo;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Days left in the window without any table at all.
|
|
320
|
+
for (let gap = expectedDay; gap >= lastDay; gap = daysBefore(gap, 1)) {
|
|
321
|
+
skipped.push({ day: gap, reason: 'noTable' });
|
|
322
|
+
}
|
|
323
|
+
return { rate: undefined, skipped };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
tables: dated.map((entry) => entry.table),
|
|
328
|
+
rate(from, to, query) {
|
|
329
|
+
return lookup(from, to, query).rate;
|
|
330
|
+
},
|
|
331
|
+
lookup,
|
|
332
|
+
};
|
|
333
|
+
}
|