diff --git a/README.md b/README.md index 447b414..8b586bd 100644 --- a/README.md +++ b/README.md @@ -143,11 +143,11 @@ These changes improve throughput and reduce memory pressure when working with la - [formatFileSize](#formatfilesize) - Converts a number of bytes into a human-readable file size string (B, KB, MB, GB, TB). - [formatOrdinal](#formatordinal) - Converts a number into its ordinal string representation (e.g., 1 → "1st", 2 → "2nd"). - [formatList](#formatlist) - Formats an array of strings into a human-readable list with proper commas and "and". +- [formatCreditCard](#formatcreditcard) - Formats a credit card number by grouping digits into readable parts. - [formatToOctal](#formattotoctal) - Converts a decimal number to octal, optional "0o" prefix. - [formatTemperature](#formattemperature) - Converts temperatures between Celsius, Fahrenheit, and Kelvin. - [formatToBinary](#formattobinary) - Converts a decimal integer to a binary string with optional bit grouping. - [formatToHexadecimal](#formattohexadecimal) - Converts temperatures between Celsius, Fahrenheit, and Kelvin. - - [formatToDecimal](#formattodecimal) - Converts base-2/8/16 strings to decimal. ## 📋 API Reference @@ -1486,6 +1486,27 @@ formatList(['apple', 123]); // TypeError | --------- | -------- | -------- | ----------------------------------------- | | arr | string[] | required | The array of strings to format as a list. | +#### formatCreditCard(cardNumber) +Formats a credit card number into readable groups of digits separated by spaces. +Supports 15-digit (AmEx) and 16-digit (Visa/MasterCard) numbers. +Non-digit characters are automatically stripped before formatting. +Throws an error if the input is not a string. + +```javascript +import { formatCreditCard } from 'stringzy'; + +formatCreditCard('1234567812345678'); // "1234 5678 1234 5678" +formatCreditCard('4111111111111111'); // "4111 1111 1111 1111" +formatCreditCard('378282246310005'); // "3782 822463 10005" (AmEx) +formatCreditCard('4111-1111-1111-1111'); // "4111 1111 1111 1111" +formatCreditCard('123'); // "" (invalid length) +formatCreditCard(''); // "" (empty string) +``` + +| Parameter | Type | Default | Description | +| ---------- | ------ | -------- | ------------------------------------------------------------------- | +| cardNumber | string | required | The credit card number to format. Cannot include non-digit characters. | + #### formatToOctal(num, options?) Converts a decimal number to its octal (base-8) string representation. Supports negatives and an optional `0o` prefix. diff --git a/src/formatting/creditCard.ts b/src/formatting/creditCard.ts new file mode 100644 index 0000000..36224ff --- /dev/null +++ b/src/formatting/creditCard.ts @@ -0,0 +1,29 @@ +/** + * Formats a credit card number by grouping digits into readable parts. + * + * @param {string} cardNumber - The credit card number to format. + * @returns {string} The formatted credit card number. + * @throws {TypeError} If the input is not a string. + */ +export function formatCreditCard(cardNumber: string): string { + if (typeof cardNumber !== 'string') { + throw new TypeError('Input must be a string'); + } + + // Remove all non-digit characters + const cleaned = cardNumber.replace(/\D/g, ''); + + // Only accept 15 or 16 digit card numbers + if (cleaned.length !== 15 && cleaned.length !== 16) { + return ''; + } + + // Format based on length: + // 16 digits → 4-4-4-4 (Visa, MasterCard) + if (cleaned.length === 16) { + return cleaned.replace(/(\d{4})(?=\d)/g, '$1 ').trim(); + } + + // 15 digits → 4-6-5 (AmEx) + return cleaned.replace(/(\d{4})(\d{6})(\d{5})/, '$1 $2 $3'); +} diff --git a/src/formatting/index.ts b/src/formatting/index.ts index cfc69de..0fd62bc 100644 --- a/src/formatting/index.ts +++ b/src/formatting/index.ts @@ -8,6 +8,7 @@ export { formatPercentage } from './percentage'; export { formatFileSize } from './fileSize'; export { formatOrdinal } from './ordinal'; export { formatList } from './listToString'; +export { formatCreditCard } from './creditCard'; export { formatToOctal } from './octal'; export { formatTemperature } from './temperature'; export { formatToBinary } from './binary'; @@ -24,6 +25,7 @@ import { formatPercentage } from './percentage'; import { formatFileSize } from './fileSize'; import { formatOrdinal } from './ordinal'; import { formatList } from './listToString'; +import { formatCreditCard } from './creditCard'; import { formatToOctal } from './octal'; import { formatTemperature } from './temperature'; import { formatToBinary } from './binary'; @@ -41,6 +43,7 @@ export const formatting = { formatFileSize, formatOrdinal, formatList, + formatCreditCard, formatTemperature, formatToBinary, formatToHexadecimal, diff --git a/src/tests/formatting/creditCard.test.ts b/src/tests/formatting/creditCard.test.ts new file mode 100644 index 0000000..c2d20bd --- /dev/null +++ b/src/tests/formatting/creditCard.test.ts @@ -0,0 +1,31 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { formatCreditCard } from '../../formatting/creditCard'; + +describe('formatCreditCard', () => { + it('formats 16-digit card numbers correctly', () => { + assert.strictEqual(formatCreditCard('1234567812345678'), '1234 5678 1234 5678'); + assert.strictEqual(formatCreditCard('4111111111111111'), '4111 1111 1111 1111'); + }); + + it('formats 15-digit card numbers (AmEx) correctly', () => { + assert.strictEqual(formatCreditCard('378282246310005'), '3782 822463 10005'); + }); + + it('removes non-digit characters before formatting', () => { + assert.strictEqual(formatCreditCard('4111-1111-1111-1111'), '4111 1111 1111 1111'); + assert.strictEqual(formatCreditCard('3782 8224 6310 005'), '3782 822463 10005'); + }); + + it('returns empty string for invalid lengths', () => { + assert.strictEqual(formatCreditCard('123'), ''); + assert.strictEqual(formatCreditCard('11112222333344445555'), ''); + assert.strictEqual(formatCreditCard(''), ''); + }); + + it('throws an error if input is not a string', () => { + assert.throws(() => formatCreditCard(1234567812345678 as any), /Input must be a string/); + assert.throws(() => formatCreditCard(null as any), /Input must be a string/); + assert.throws(() => formatCreditCard(undefined as any), /Input must be a string/); + }); +});