Skip to content

Commit 71f95ec

Browse files
abhu85claude
andcommitted
feat(isCurrency): add require_thousands_separator option
Add new option `require_thousands_separator` to enforce thousands separators in currency values. When enabled, amounts of 1000 or greater must include thousands separators (e.g., "1,234" is valid but "1234" is invalid). This is useful for validating financial data where comma separators must always be present. Fixes #912 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 88e0d3d commit 71f95ec

3 files changed

Lines changed: 82 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Validator | Description
100100
**isBtcAddress(str)** | check if the string is a valid BTC address.
101101
**isByteLength(str [, options])** | check if the string's length (in UTF-8 bytes) falls in a range.<br/><br/>`options` is an object which defaults to `{ min: 0, max: undefined }`.
102102
**isCreditCard(str [, options])** | check if the string is a credit card number.<br/><br/> `options` is an optional object that can be supplied with the following key(s): `provider` is an optional key whose value should be a string, and defines the company issuing the credit card. Valid values include `['amex', 'dinersclub', 'discover', 'jcb', 'mastercard', 'unionpay', 'visa']` or blank will check for any provider.
103-
**isCurrency(str [, options])** | check if the string is a valid currency amount.<br/><br/>`options` is an object which defaults to `{ symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false }`.<br/>**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowed not a range, for example a range 1 to 3 will be given as [1, 2, 3].
103+
**isCurrency(str [, options])** | check if the string is a valid currency amount.<br/><br/>`options` is an object which defaults to `{ symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false, require_thousands_separator: false }`.<br/>**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowed not a range, for example a range 1 to 3 will be given as [1, 2, 3].
104104
**isDataURI(str)** | check if the string is a [data uri format][Data URI Format].
105105
**isDate(str [, options])** | check if the string is a valid date. e.g. [`2002-07-15`, new Date()].<br/><br/> `options` is an object which can contain the keys `format`, `strictMode` and/or `delimiters`.<br/><br/>`format` is a string and defaults to `YYYY/MM/DD`.<br/><br/>`strictMode` is a boolean and defaults to `false`. If `strictMode` is set to true, the validator will reject strings different from `format`.<br/><br/> `delimiters` is an array of allowed date delimiters and defaults to `['/', '-']`.
106106
**isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.<br/><br/>`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`.<br/><br/>`locale` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa', 'fa-AF', 'fa-IR', 'fr-FR', 'fr-CA', 'hu-HU', 'id-ID', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pl-Pl', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN']`.<br/>**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'.

src/lib/isCurrency.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ function currencyRegex(options) {
1010
negative = '-?',
1111
whole_dollar_amount_without_sep = '[1-9]\\d*',
1212
whole_dollar_amount_with_sep = `[1-9]\\d{0,2}(\\${options.thousands_separator}\\d{3})*`,
13-
valid_whole_dollar_amounts = [
14-
'0', whole_dollar_amount_without_sep, whole_dollar_amount_with_sep],
13+
valid_whole_dollar_amounts = options.require_thousands_separator
14+
? ['0', '[1-9]\\d{0,2}', whole_dollar_amount_with_sep]
15+
: ['0', whole_dollar_amount_without_sep, whole_dollar_amount_with_sep],
1516
whole_dollar_amount = `(${valid_whole_dollar_amounts.join('|')})?`,
1617
decimal_amount = `(\\${options.decimal_separator}(${decimal_digits}))${options.require_decimal ? '' : '?'}`;
1718
let pattern = whole_dollar_amount + (options.allow_decimal || options.require_decimal ? decimal_amount : '');
@@ -70,6 +71,7 @@ const default_currency_options = {
7071
require_decimal: false,
7172
digits_after_decimal: [2],
7273
allow_space_after_digits: false,
74+
require_thousands_separator: false,
7375
};
7476

7577
export default function isCurrency(str, options) {

test/validators.test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12069,6 +12069,83 @@ describe('Validators', () => {
1206912069
'$R 1.400,00',
1207012070
],
1207112071
});
12072+
12073+
// require_thousands_separator option (issue #912)
12074+
test({
12075+
validator: 'isCurrency',
12076+
args: [
12077+
{
12078+
require_thousands_separator: true,
12079+
},
12080+
],
12081+
valid: [
12082+
'$10,123.45',
12083+
'-$10,123.45',
12084+
'$1,234.56',
12085+
'1,234.56',
12086+
'$1,234,567.89',
12087+
'1,234,567.89',
12088+
'$100.00',
12089+
'100.00',
12090+
'$10.00',
12091+
'10.00',
12092+
'$1.00',
12093+
'1.00',
12094+
'$0.50',
12095+
'0.50',
12096+
'.50',
12097+
'$.50',
12098+
'0',
12099+
'$0',
12100+
'999',
12101+
'$999',
12102+
'999.99',
12103+
'$999.99',
12104+
],
12105+
invalid: [
12106+
'1234.56',
12107+
'$1234.56',
12108+
'1234',
12109+
'$1234',
12110+
'10000',
12111+
'$10000',
12112+
'1234567.89',
12113+
'$1234567.89',
12114+
'10000.00',
12115+
'$10000.00',
12116+
],
12117+
});
12118+
12119+
// require_thousands_separator with different separator
12120+
test({
12121+
validator: 'isCurrency',
12122+
args: [
12123+
{
12124+
require_thousands_separator: true,
12125+
thousands_separator: '.',
12126+
decimal_separator: ',',
12127+
symbol: '€',
12128+
symbol_after_digits: true,
12129+
},
12130+
],
12131+
valid: [
12132+
'10.123,45€',
12133+
'1.234,56€',
12134+
'1.234.567,89€',
12135+
'100,00€',
12136+
'10,00€',
12137+
'1,00€',
12138+
'0,50€',
12139+
'999€',
12140+
'999,99€',
12141+
],
12142+
invalid: [
12143+
'1234,56€',
12144+
'1234€',
12145+
'10000€',
12146+
'1234567,89€',
12147+
],
12148+
});
1207212149
});
1207312150

1207412151
it('should validate Ethereum addresses', () => {

0 commit comments

Comments
 (0)