@stll/money 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +27 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -171,18 +171,34 @@ const toMajorUnits = ({ amountCents, currency }) => amountCents / 10 ** currency
171
171
  * RangeError would take the whole board down with it.
172
172
  */
173
173
  const formatMoneyCents = ({ amountCents, currency, locale, fractionDigits }) => {
174
- const major = toMajorUnits({
175
- amountCents,
176
- currency
174
+ const minorDigits = currencyMinorUnitDigits(currency);
175
+ const digits = fractionDigits ?? minorDigits;
176
+ if (!Number.isInteger(digits) || digits < 0 || digits > 100) return panic("Money fraction digits must be an integer from 0 to 100");
177
+ const magnitude = BigInt(Math.abs(cents(amountCents)));
178
+ const negative = amountCents < 0 || Object.is(amountCents, -0);
179
+ const displayUnits = digits >= minorDigits ? magnitude * 10n ** BigInt(digits - minorDigits) : (magnitude + 10n ** BigInt(minorDigits - digits) / 2n) / 10n ** BigInt(minorDigits - digits);
180
+ const scale = 10n ** BigInt(digits);
181
+ const whole = displayUnits / scale;
182
+ const fraction = (displayUnits % scale).toString().padStart(digits, "0");
183
+ const formatted = Result.try(() => {
184
+ const formatter = new Intl.NumberFormat(locale, {
185
+ style: "currency",
186
+ currency,
187
+ minimumFractionDigits: digits,
188
+ maximumFractionDigits: digits
189
+ });
190
+ const digitFormatter = new Intl.NumberFormat(locale, { useGrouping: false });
191
+ const localizedFraction = fraction.split("").map((digit) => digitFormatter.format(Number(digit))).join("");
192
+ const negativeSubunit = negative && whole === 0n;
193
+ let template = whole;
194
+ if (negative) template = negativeSubunit ? -1n : -whole;
195
+ return formatter.formatToParts(template).map((part) => {
196
+ if (part.type === "fraction") return localizedFraction;
197
+ if (negativeSubunit && part.type === "integer") return digitFormatter.format(0);
198
+ return part.value;
199
+ }).join("");
177
200
  });
178
- const digits = fractionDigits ?? currencyMinorUnitDigits(currency);
179
- const formatted = Result.try(() => new Intl.NumberFormat(locale, {
180
- style: "currency",
181
- currency,
182
- minimumFractionDigits: digits,
183
- maximumFractionDigits: digits
184
- }).format(major));
185
- return formatted.isErr() ? `${major.toFixed(digits)} ${currency}` : formatted.value;
201
+ return formatted.isErr() ? `${negative ? "-" : ""}${whole}${digits === 0 ? "" : `.${fraction}`} ${currency}` : formatted.value;
186
202
  };
187
203
  //#endregion
188
204
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/money",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Branded minor-unit monetary amounts and currency-safe billing arithmetic.",
5
5
  "keywords": [
6
6
  "billing",