-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathCurrencyUtils.ts
166 lines (149 loc) · 5.76 KB
/
CurrencyUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
import type {OnyxValues} from '@src/ONYXKEYS';
import ONYXKEYS from '@src/ONYXKEYS';
import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener';
import * as NumberFormatUtils from './NumberFormatUtils';
let currencyList: OnyxValues[typeof ONYXKEYS.CURRENCY_LIST] = {};
Onyx.connect({
key: ONYXKEYS.CURRENCY_LIST,
callback: (val) => {
if (!val || Object.keys(val).length === 0) {
return;
}
currencyList = val;
},
});
/**
* Returns the number of digits after the decimal separator for a specific currency.
* For currencies that have decimal places > 2, floor to 2 instead:
* https://github.com/Expensify/App/issues/15878#issuecomment-1496291464
*
* @param currency - IOU currency
*/
function getCurrencyDecimals(currency: string = CONST.CURRENCY.USD): number {
const decimals = currencyList?.[currency]?.decimals;
return decimals ?? 2;
}
/**
* Returns the currency's minor unit quantity
* e.g. Cent in USD
*
* @param currency - IOU currency
*/
function getCurrencyUnit(currency: string = CONST.CURRENCY.USD): number {
return 10 ** getCurrencyDecimals(currency);
}
/**
* Get localized currency symbol for currency(ISO 4217) Code
*/
function getLocalizedCurrencySymbol(currencyCode: string): string | undefined {
const parts = NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), 0, {
style: 'currency',
currency: currencyCode,
});
return parts.find((part) => part.type === 'currency')?.value;
}
/**
* Get the currency symbol for a currency(ISO 4217) Code
*/
function getCurrencySymbol(currencyCode: string): string | undefined {
return currencyList?.[currencyCode]?.symbol;
}
/**
* Whether the currency symbol is left-to-right.
*/
function isCurrencySymbolLTR(currencyCode: string): boolean {
const parts = NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), 0, {
style: 'currency',
currency: currencyCode,
});
// Currency is LTR when the first part is of currency type.
return parts[0].type === 'currency';
}
/**
* Takes an amount as a floating point number and converts it to an integer equivalent to the amount in "cents".
* This is because the backend always stores amounts in "cents". The backend works in integer cents to avoid precision errors
* when doing math operations.
*
* @note we do not currently support any currencies with more than two decimal places. Decimal past the second place will be rounded. Sorry Tunisia :(
*/
function convertToBackendAmount(amountAsFloat: number): number {
return Math.round(amountAsFloat * 100);
}
/**
* Takes an amount in "cents" as an integer and converts it to a floating point amount used in the frontend.
*
* @note we do not support any currencies with more than two decimal places.
*/
function convertToFrontendAmount(amountAsInt: number): number {
return Math.trunc(amountAsInt) / 100.0;
}
/**
* Given an amount in the "cents", convert it to a string for display in the UI.
* The backend always handle things in "cents" (subunit equal to 1/100)
*
* @param amountInCents – should be an integer. Anything after a decimal place will be dropped.
* @param currency - IOU currency
*/
function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string {
const convertedAmount = convertToFrontendAmount(amountInCents);
return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
style: 'currency',
currency,
// We are forcing the number of decimals because we override the default number of decimals in the backend for RSD
// See: https://github.com/Expensify/PHP-Libs/pull/834
minimumFractionDigits: currency === 'RSD' ? getCurrencyDecimals(currency) : undefined,
});
}
/**
* Given an amount, convert it to a string for display in the UI.
*
* @param amount – should be a float.
* @param currency - IOU currency
*/
function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD): string {
const convertedAmount = amount / 100.0;
return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
style: 'currency',
currency,
minimumFractionDigits: getCurrencyDecimals(currency) + 1,
});
}
/**
* Acts the same as `convertAmountToDisplayString` but the result string does not contain currency
*/
function convertToDisplayStringWithoutCurrency(amountInCents: number, currency: string = CONST.CURRENCY.USD) {
const convertedAmount = convertToFrontendAmount(amountInCents);
return NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
style: 'currency',
currency,
// We are forcing the number of decimals because we override the default number of decimals in the backend for RSD
// See: https://github.com/Expensify/PHP-Libs/pull/834
minimumFractionDigits: currency === 'RSD' ? getCurrencyDecimals(currency) : undefined,
})
.filter((x) => x.type !== 'currency')
.filter((x) => x.type !== 'literal' || x.value.trim().length !== 0)
.map((x) => x.value)
.join('');
}
/**
* Checks if passed currency code is a valid currency based on currency list
*/
function isValidCurrencyCode(currencyCode: string): boolean {
const currency = currencyList?.[currencyCode];
return Boolean(currency);
}
export {
getCurrencyDecimals,
getCurrencyUnit,
getLocalizedCurrencySymbol,
getCurrencySymbol,
isCurrencySymbolLTR,
convertToBackendAmount,
convertToFrontendAmount,
convertToDisplayString,
convertAmountToDisplayString,
convertToDisplayStringWithoutCurrency,
isValidCurrencyCode,
};