diff --git a/src/exports.test.ts b/src/exports.test.ts index 0f563e1f..34e202df 100644 --- a/src/exports.test.ts +++ b/src/exports.test.ts @@ -6,6 +6,7 @@ const expectedNamedExports = [ 'formatAmount', 'formatAmountFromString', 'formatPriceUnit', + 'omitTrailingDecimalZeros', 'parseDecimalValue', 'toDinero', 'toDineroFromInteger', diff --git a/src/index.ts b/src/index.ts index d76ff598..9d2ef9ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export { formatAmount, formatAmountFromString, formatPriceUnit, + omitTrailingDecimalZeros, parseDecimalValue, toIntegerAmount, addSeparatorToDineroString, diff --git a/src/money/formatters.test.ts b/src/money/formatters.test.ts index 10c257ae..86038380 100644 --- a/src/money/formatters.test.ts +++ b/src/money/formatters.test.ts @@ -5,6 +5,7 @@ import { formatAmount, formatAmountFromString, formatPriceUnit, + omitTrailingDecimalZeros, parseDecimalValue, toIntegerAmount, unitDisplayLabels, @@ -294,6 +295,26 @@ describe('formatPriceUnit', () => { ); }); +describe('omitTrailingDecimalZeros', () => { + it.each` + price | expected + ${'10.00 €'} | ${'10 €'} + ${'10,00 €'} | ${'10 €'} + ${'1.000,00 €'} | ${'1.000 €'} + ${'10.50 €'} | ${'10.50 €'} + ${'10,50 €'} | ${'10,50 €'} + ${'€10.00'} | ${'€10'} + ${'€10.00/Stück'} | ${'€10/Stück'} + ${'€10,00/Stück'} | ${'€10/Stück'} + ${'10.00 € / Monat'} | ${'10 € / Monat'} + ${'10,00 € / Monat'} | ${'10 € / Monat'} + ${'0.00 €'} | ${'0 €'} + ${'10.20 €'} | ${'10.20 €'} + `('should transform "$price" into "$expected"', ({ price, expected }) => { + expect(omitTrailingDecimalZeros(price)).toEqual(expected); + }); +}); + describe('parseDecimalValue', () => { it.each` value | expected diff --git a/src/money/formatters.ts b/src/money/formatters.ts index 408d1c3a..771404ab 100644 --- a/src/money/formatters.ts +++ b/src/money/formatters.ts @@ -217,14 +217,9 @@ export const formatAmountFromString = ({ subunitFromAmount, ); - return formatWithSubunit( - dineroObjectFromAmount - .multiply(100) - .convertPrecision(precision ?? amountPrecision) - .setLocale(locale || DEFAULT_LOCALE) - .toFormat(format || amountFormat), - subunit, - ); + const dSubunit = dineroObjectFromAmount.multiply(100).convertPrecision(precision ?? amountPrecision); + + return formatWithSubunit(dSubunit.setLocale(locale || DEFAULT_LOCALE).toFormat(format || amountFormat), subunit); } return dineroObjectFromAmount @@ -352,6 +347,26 @@ function shouldDisplayAmountAsCents(amount: number, currency?: Currency) { return dAbsoluteAmount.hasSubUnits() && dAbsoluteAmount.lessThan(dAmountOfOneUnit); } +/** + * Removes trailing decimal zeros (.00 or ,00) from a formatted price string. + * Handles prices with currency symbols, billing period suffixes, and tiered pricing unit suffixes (e.g. €10.00/Stück). + * + * @param price - The formatted price string + * @returns The price string without trailing decimal zeros + */ +export const omitTrailingDecimalZeros = (price: string): string => { + const trailingZerosWithDot = /(\.00)(\s.*)?$/; + const trailingZerosWithComma = /(,00)(\s.*)?$/; + const trailingZerosWithDotBeforeSlash = /(\.00)(\/[\w\W]*)$/; + const trailingZerosWithCommaBeforeSlash = /(,00)(\/[\w\W]*)$/; + + return price + .replace(trailingZerosWithDot, '$2') + .replace(trailingZerosWithComma, '$2') + .replace(trailingZerosWithDotBeforeSlash, '$2') + .replace(trailingZerosWithCommaBeforeSlash, '$2'); +}; + /** * Converts a decimal string value into a valid decimal amount value, without any thousand separators, using dot as the decimal separator. *