From b0e928dae1c6453448015170ac66454c34691225 Mon Sep 17 00:00:00 2001 From: Sarhan Date: Sat, 8 Aug 2020 12:21:41 +0100 Subject: [PATCH 1/2] fix(IsDate): prevent using mixed delimiter --- README.md | 2 +- src/lib/isDate.js | 14 ++++++++++---- test/validators.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b62a4e470..7c2d96fd7 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Validator | Description **isCreditCard(str)** | check if the string is a credit card. **isCurrency(str [, options])** | check if the string is a valid currency amount.

`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}`.
**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]. **isDataURI(str)** | check if the string is a [data uri format](https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs). -**isDate(input [, format])** | Check if the input is a valid date. e.g. [`2002-07-15`, new Date()].

`format` is a string and defaults to `YYYY/MM/DD` +**isDate(input [, format, strictMode])** | Check if the input is a valid date. e.g. [`2002-07-15`, new Date()].

`format` is a string and defaults to `YYYY/MM/DD`.

`strictMode` is a boolean and defaults to `false`. If `strictMode` is set to true, the validator will reject inputs with a delimiter different from `format`. **isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.

`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`

`locale` determine 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', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'ku-IQ', nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`.
**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'. **isDivisibleBy(str, number)** | check if the string is a number that's divisible by another. **isEAN(str)** | check if the string is an EAN (European Article Number). diff --git a/src/lib/isDate.js b/src/lib/isDate.js index ff3df4ecf..8d518d47f 100644 --- a/src/lib/isDate.js +++ b/src/lib/isDate.js @@ -13,11 +13,17 @@ function zip(date, format) { return zippedArr; } -export default function isDate(input, format = 'YYYY/MM/DD') { +export default function isDate(input, format = 'YYYY/MM/DD', strictMode = false) { if (typeof input === 'string' && isValidFormat(format)) { - const splitter = /[-/]/, - dateAndFormat = zip(input.split(splitter), format.toLowerCase().split(splitter)), - dateObj = {}; + const formatDelimiter = ['.', '-', '/'].find(delimiter => format.indexOf(delimiter) !== -1); + const dateDelimiter = strictMode + ? formatDelimiter + : ['.', '-', '/'].find(delimiter => input.indexOf(delimiter) !== -1); + const dateAndFormat = zip( + input.split(dateDelimiter), + format.toLowerCase().split(formatDelimiter) + ); + const dateObj = {}; for (const [dateWord, formatWord] of dateAndFormat) { if (dateWord.length !== formatWord.length) { diff --git a/test/validators.js b/test/validators.js index ab6128516..2cc94a9e5 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8751,6 +8751,7 @@ describe('Validators', () => { '2020-02-30', // invalid date '2019-02-29', // non-leap year '2020-04-31', // invalid date + '2020/03-15', // mixed delimiter ], }); test({ @@ -8765,6 +8766,7 @@ describe('Validators', () => { '15-7-2002', '15/7/02', '15-7-02', + '15-07/2002', ], }); test({ @@ -8777,6 +8779,7 @@ describe('Validators', () => { invalid: [ '15/7/2002', '15-7-2002', + '15/07-02', ], }); test({ @@ -8790,6 +8793,37 @@ describe('Validators', () => { '5/07/02', '15/7/02', '15-7-02', + '5/7-02', + ], + }); + test({ + validator: 'isDate', + args: ['DD/MM/YYYY', true], + valid: [ + '15/07/2002', + ], + invalid: [ + '15-07-2002', + '15/7/2002', + '15-7-2002', + '15/7/02', + '15-7-02', + '15-07/2002', + ], + }); + test({ + validator: 'isDate', + args: ['YYYY/MM/DD', true], + valid: [ + new Date(), + new Date([2014, 2, 15]), + new Date('2014-03-15'), + '2020/02/29', + ], + invalid: [ + '2014-02-15', + '2020-02-29', + '15-07/2002', ], }); }); From 1f9833e63729775625badd7d5c448c0d2285e670 Mon Sep 17 00:00:00 2001 From: Sarhan Date: Sat, 8 Aug 2020 13:18:33 +0100 Subject: [PATCH 2/2] refactor(isDate): normalize date options and enforce strictMode --- README.md | 2 +- src/lib/isDate.js | 32 ++++++++++++++++++++------ test/validators.js | 57 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7c2d96fd7..57820ab2c 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Validator | Description **isCreditCard(str)** | check if the string is a credit card. **isCurrency(str [, options])** | check if the string is a valid currency amount.

`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}`.
**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]. **isDataURI(str)** | check if the string is a [data uri format](https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs). -**isDate(input [, format, strictMode])** | Check if the input is a valid date. e.g. [`2002-07-15`, new Date()].

`format` is a string and defaults to `YYYY/MM/DD`.

`strictMode` is a boolean and defaults to `false`. If `strictMode` is set to true, the validator will reject inputs with a delimiter different from `format`. +**isDate(input [, options])** | Check if the input is a valid date. e.g. [`2002-07-15`, new Date()].

`options` is an object which can contain the keys `format`, `strictMode` and/or `delimiters`

`format` is a string and defaults to `YYYY/MM/DD`.

`strictMode` is a boolean and defaults to `false`. If `strictMode` is set to true, the validator will reject inputs different from `format`.

`delimiters` is an array of allowed date delimiters and defaults to `['/', '-']`. **isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.

`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`

`locale` determine 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', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'ku-IQ', nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`.
**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'. **isDivisibleBy(str, number)** | check if the string is a number that's divisible by another. **isEAN(str)** | check if the string is an EAN (European Article Number). diff --git a/src/lib/isDate.js b/src/lib/isDate.js index 8d518d47f..110c2193a 100644 --- a/src/lib/isDate.js +++ b/src/lib/isDate.js @@ -1,3 +1,11 @@ +import merge from './util/merge'; + +const default_date_options = { + format: 'YYYY/MM/DD', + delimiters: ['/', '-'], + strictMode: false, +}; + function isValidFormat(format) { return /(^(y{4}|y{2})[\/-](m{1,2})[\/-](d{1,2})$)|(^(m{1,2})[\/-](d{1,2})[\/-]((y{4}|y{2})$))|(^(d{1,2})[\/-](m{1,2})[\/-]((y{4}|y{2})$))/gi.test(format); } @@ -13,15 +21,21 @@ function zip(date, format) { return zippedArr; } -export default function isDate(input, format = 'YYYY/MM/DD', strictMode = false) { - if (typeof input === 'string' && isValidFormat(format)) { - const formatDelimiter = ['.', '-', '/'].find(delimiter => format.indexOf(delimiter) !== -1); - const dateDelimiter = strictMode +export default function isDate(input, options) { + if (typeof options === 'string') { // Allow backward compatbility for old format isDate(input [, format]) + options = merge({ format: options }, default_date_options); + } else { + options = merge(options, default_date_options); + } + if (typeof input === 'string' && isValidFormat(options.format)) { + const formatDelimiter = options.delimiters + .find(delimiter => options.format.indexOf(delimiter) !== -1); + const dateDelimiter = options.strictMode ? formatDelimiter - : ['.', '-', '/'].find(delimiter => input.indexOf(delimiter) !== -1); + : options.delimiters.find(delimiter => input.indexOf(delimiter) !== -1); const dateAndFormat = zip( input.split(dateDelimiter), - format.toLowerCase().split(formatDelimiter) + options.format.toLowerCase().split(formatDelimiter) ); const dateObj = {}; @@ -36,5 +50,9 @@ export default function isDate(input, format = 'YYYY/MM/DD', strictMode = false) return new Date(`${dateObj.m}/${dateObj.d}/${dateObj.y}`).getDate() === +dateObj.d; } - return Object.prototype.toString.call(input) === '[object Date]' && isFinite(input); + if (!options.strictMode) { + return Object.prototype.toString.call(input) === '[object Date]' && isFinite(input); + } + + return false; } diff --git a/test/validators.js b/test/validators.js index 2cc94a9e5..4d7a9cb95 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8756,7 +8756,7 @@ describe('Validators', () => { }); test({ validator: 'isDate', - args: ['DD/MM/YYYY'], + args: ['DD/MM/YYYY'], // old format for backward compatibility valid: [ '15-07-2002', '15/07/2002', @@ -8771,7 +8771,22 @@ describe('Validators', () => { }); test({ validator: 'isDate', - args: ['DD/MM/YY'], + args: [{ format: 'DD/MM/YYYY' }], + valid: [ + '15-07-2002', + '15/07/2002', + ], + invalid: [ + '15/7/2002', + '15-7-2002', + '15/7/02', + '15-7-02', + '15-07/2002', + ], + }); + test({ + validator: 'isDate', + args: [{ format: 'DD/MM/YY' }], valid: [ '15-07-02', '15/07/02', @@ -8784,7 +8799,7 @@ describe('Validators', () => { }); test({ validator: 'isDate', - args: ['D/M/YY'], + args: [{ format: 'D/M/YY' }], valid: [ '5-7-02', '5/7/02', @@ -8798,7 +8813,7 @@ describe('Validators', () => { }); test({ validator: 'isDate', - args: ['DD/MM/YYYY', true], + args: [{ format: 'DD/MM/YYYY', strictMode: true }], valid: [ '15/07/2002', ], @@ -8813,17 +8828,45 @@ describe('Validators', () => { }); test({ validator: 'isDate', - args: ['YYYY/MM/DD', true], + args: [{ strictMode: true }], + valid: [ + '2020/01/15', + '2014/02/15', + '2014/03/15', + '2020/02/29', + ], + invalid: [ + '2014-02-15', + '2020-02-29', + '15-07/2002', + new Date(), + new Date([2014, 2, 15]), + new Date('2014-03-15'), + ], + }); + test({ + validator: 'isDate', + args: [{ delimiters: ['/', ' '] }], valid: [ new Date(), new Date([2014, 2, 15]), new Date('2014-03-15'), '2020/02/29', + '2020 02 29', ], invalid: [ - '2014-02-15', '2020-02-29', - '15-07/2002', + '', + '15072002', + null, + undefined, + { year: 2002, month: 7, day: 15 }, + 42, + { toString() { return '[object Date]'; } }, + '2020/02/30', + '2019/02/29', + '2020/04/31', + '2020/03-15', ], }); });