Skip to content

Commit dad8961

Browse files
authoredOct 15, 2018
fix: extra validation for dates (#910)
* fix: extra validation for dates This commit adds an additional test for dates over and above passing the pattern match. It checks to assertain that the date is a valid date. For examples, dates like 2009-02-29 are caught as invalid. Additionally, it also fixes a minor bug with the `weekDate` that was wrongfully leaving out W53 as an invalid date. fixes #772 * fix: add extra validation for ordinal dates - adds provision for ordinal dates - checks that ordinal dates are also valid for leap years - includes regression tests for previous cases with `strict = false`
1 parent 509324f commit dad8961

File tree

6 files changed

+200
-74
lines changed

6 files changed

+200
-74
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Validator | Description
9191
**isISBN(str [, version])** | check if the string is an ISBN (version 10 or 13).
9292
**isISSN(str [, options])** | check if the string is an [ISSN](https://en.wikipedia.org/wiki/International_Standard_Serial_Number).<br/><br/>`options` is an object which defaults to `{ case_sensitive: false, require_hyphen: false }`. If `case_sensitive` is true, ISSNs with a lowercase `'x'` as the check digit are rejected.
9393
**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier).
94-
**isISO8601(str)** | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date.
94+
**isISO8601(str)** | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date; for additional checks for valid dates, e.g. invalidates dates like `2009-02-29`, pass `options` object as a second parameter with `options.strict = true`.
9595
**isRFC3339(str)** | check if the string is a valid [RFC 3339](https://tools.ietf.org/html/rfc3339) date.
9696
**isISO31661Alpha2(str)** | check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
9797
**isISO31661Alpha3(str)** | check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.

‎lib/isISO8601.js

+33-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,41 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
1313

1414
/* eslint-disable max-len */
1515
// from http://goo.gl/0ejHHW
16-
var iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
16+
var iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
1717
/* eslint-enable max-len */
18+
var isValidDate = function isValidDate(str) {
19+
// str must have passed the ISO8601 check
20+
// this check is meant to catch invalid dates
21+
// like 2009-02-31
22+
// first check for ordinal dates
23+
var ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
24+
if (ordinalMatch) {
25+
var oYear = Number(ordinalMatch[1]);
26+
var oDay = Number(ordinalMatch[2]);
27+
// if is leap year
28+
if (oYear % 4 === 0 && oYear % 100 !== 0) {
29+
return oDay <= 366;
30+
}
31+
return oDay <= 365;
32+
}
33+
var match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
34+
var year = match[1];
35+
var month = match[2];
36+
var day = match[3];
37+
// create a date object and compare
38+
var d = new Date(year + '-' + (month || 1) + '-' + (day || 1));
39+
if (isNaN(d.getFullYear())) return false;
40+
if (month && day) {
41+
return d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === day;
42+
}
43+
return true;
44+
};
1845

19-
function isISO8601(str) {
46+
function isISO8601(str, options) {
2047
(0, _assertString2.default)(str);
21-
return iso8601.test(str);
48+
var check = iso8601.test(str);
49+
if (!options) return check;
50+
if (check && options.strict) return isValidDate(str);
51+
return check;
2252
}
2353
module.exports = exports['default'];

‎src/lib/isISO8601.js

+34-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,41 @@ import assertString from './util/assertString';
22

33
/* eslint-disable max-len */
44
// from http://goo.gl/0ejHHW
5-
const iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
5+
const iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
66
/* eslint-enable max-len */
7+
const isValidDate = (str) => {
8+
// str must have passed the ISO8601 check
9+
// this check is meant to catch invalid dates
10+
// like 2009-02-31
11+
// first check for ordinal dates
12+
const ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
13+
if (ordinalMatch) {
14+
const oYear = Number(ordinalMatch[1]);
15+
const oDay = Number(ordinalMatch[2]);
16+
// if is leap year
17+
if (oYear % 4 === 0
18+
&& oYear % 100 !== 0) return oDay <= 366;
19+
return oDay <= 365;
20+
}
21+
const match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
22+
const year = match[1];
23+
const month = match[2];
24+
const day = match[3];
25+
// create a date object and compare
26+
const d = new Date(`${year}-${month || 1}-${day || 1}`);
27+
if (isNaN(d.getFullYear())) return false;
28+
if (month && day) {
29+
return d.getFullYear() === year
30+
&& (d.getMonth() + 1) === month
31+
&& d.getDate() === day;
32+
}
33+
return true;
34+
};
735

8-
export default function isISO8601(str) {
36+
export default function isISO8601(str, options) {
937
assertString(str);
10-
return iso8601.test(str);
38+
const check = iso8601.test(str);
39+
if (!options) return check;
40+
if (check && options.strict) return isValidDate(str);
41+
return check;
1142
}

‎test/validators.js

+98-63
Original file line numberDiff line numberDiff line change
@@ -5500,76 +5500,111 @@ describe('Validators', () => {
55005500
});
55015501
});
55025502

5503+
const validISO8601 = [
5504+
'2009-12T12:34',
5505+
'2009',
5506+
'2009-05-19',
5507+
'2009-05-19',
5508+
'20090519',
5509+
'2009123',
5510+
'2009-05',
5511+
'2009-123',
5512+
'2009-222',
5513+
'2009-001',
5514+
'2009-W01-1',
5515+
'2009-W51-1',
5516+
'2009-W511',
5517+
'2009-W33',
5518+
'2009W511',
5519+
'2009-05-19',
5520+
'2009-05-19 00:00',
5521+
'2009-05-19 14',
5522+
'2009-05-19 14:31',
5523+
'2009-05-19 14:39:22',
5524+
'2009-05-19T14:39Z',
5525+
'2009-W21-2',
5526+
'2009-W21-2T01:22',
5527+
'2009-139',
5528+
'2009-05-19 14:39:22-06:00',
5529+
'2009-05-19 14:39:22+0600',
5530+
'2009-05-19 14:39:22-01',
5531+
'20090621T0545Z',
5532+
'2007-04-06T00:00',
5533+
'2007-04-05T24:00',
5534+
'2010-02-18T16:23:48.5',
5535+
'2010-02-18T16:23:48,444',
5536+
'2010-02-18T16:23:48,3-06:00',
5537+
'2010-02-18T16:23.4',
5538+
'2010-02-18T16:23,25',
5539+
'2010-02-18T16:23.33+0600',
5540+
'2010-02-18T16.23334444',
5541+
'2010-02-18T16,2283',
5542+
'2009-05-19 143922.500',
5543+
'2009-05-19 1439,55',
5544+
];
5545+
5546+
const invalidISO8601 = [
5547+
'200905',
5548+
'2009367',
5549+
'2009-',
5550+
'2007-04-05T24:50',
5551+
'2009-000',
5552+
'2009-M511',
5553+
'2009M511',
5554+
'2009-05-19T14a39r',
5555+
'2009-05-19T14:3924',
5556+
'2009-0519',
5557+
'2009-05-1914:39',
5558+
'2009-05-19 14:',
5559+
'2009-05-19r14:39',
5560+
'2009-05-19 14a39a22',
5561+
'200912-01',
5562+
'2009-05-19 14:39:22+06a00',
5563+
'2009-05-19 146922.500',
5564+
'2010-02-18T16.5:23.35:48',
5565+
'2010-02-18T16:23.35:48',
5566+
'2010-02-18T16:23.35:48.45',
5567+
'2009-05-19 14.5.44',
5568+
'2010-02-18T16:23.33.600',
5569+
'2010-02-18T16,25:23:48,444',
5570+
'2010-13-1',
5571+
];
5572+
55035573
it('should validate ISO 8601 dates', () => {
55045574
// from http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
55055575
test({
55065576
validator: 'isISO8601',
5577+
valid: validISO8601,
5578+
invalid: invalidISO8601,
5579+
});
5580+
});
5581+
5582+
it('should validate ISO 8601 dates, with strict = true (regression)', () => {
5583+
test({
5584+
validator: 'isISO8601',
5585+
args: [
5586+
{ strict: true },
5587+
],
5588+
valid: validISO8601,
5589+
invalid: invalidISO8601,
5590+
});
5591+
});
5592+
5593+
it('should validate ISO 8601 dates, with strict = true', () => {
5594+
test({
5595+
validator: 'isISO8601',
5596+
args: [
5597+
{ strict: true },
5598+
],
55075599
valid: [
5508-
'2009-12T12:34',
5509-
'2009',
5510-
'2009-05-19',
5511-
'2009-05-19',
5512-
'20090519',
5513-
'2009123',
5514-
'2009-05',
5600+
'2000-02-29',
55155601
'2009-123',
55165602
'2009-222',
5517-
'2009-001',
5518-
'2009-W01-1',
5519-
'2009-W51-1',
5520-
'2009-W511',
5521-
'2009-W33',
5522-
'2009W511',
5523-
'2009-05-19',
5524-
'2009-05-19 00:00',
5525-
'2009-05-19 14',
5526-
'2009-05-19 14:31',
5527-
'2009-05-19 14:39:22',
5528-
'2009-05-19T14:39Z',
5529-
'2009-W21-2',
5530-
'2009-W21-2T01:22',
5531-
'2009-139',
5532-
'2009-05-19 14:39:22-06:00',
5533-
'2009-05-19 14:39:22+0600',
5534-
'2009-05-19 14:39:22-01',
5535-
'20090621T0545Z',
5536-
'2007-04-06T00:00',
5537-
'2007-04-05T24:00',
5538-
'2010-02-18T16:23:48.5',
5539-
'2010-02-18T16:23:48,444',
5540-
'2010-02-18T16:23:48,3-06:00',
5541-
'2010-02-18T16:23.4',
5542-
'2010-02-18T16:23,25',
5543-
'2010-02-18T16:23.33+0600',
5544-
'2010-02-18T16.23334444',
5545-
'2010-02-18T16,2283',
5546-
'2009-05-19 143922.500',
5547-
'2009-05-19 1439,55',
5548-
],
5549-
invalid: [
5550-
'200905',
5551-
'2009367',
5552-
'2009-',
5553-
'2007-04-05T24:50',
5554-
'2009-000',
5555-
'2009-M511',
5556-
'2009M511',
5557-
'2009-05-19T14a39r',
5558-
'2009-05-19T14:3924',
5559-
'2009-0519',
5560-
'2009-05-1914:39',
5561-
'2009-05-19 14:',
5562-
'2009-05-19r14:39',
5563-
'2009-05-19 14a39a22',
5564-
'200912-01',
5565-
'2009-05-19 14:39:22+06a00',
5566-
'2009-05-19 146922.500',
5567-
'2010-02-18T16.5:23.35:48',
5568-
'2010-02-18T16:23.35:48',
5569-
'2010-02-18T16:23.35:48.45',
5570-
'2009-05-19 14.5.44',
5571-
'2010-02-18T16:23.33.600',
5572-
'2010-02-18T16,25:23:48,444',
5603+
],
5604+
invalid: [
5605+
'2010-02-30',
5606+
'2009-02-29',
5607+
'2009-366',
55735608
],
55745609
});
55755610
});

‎validator.js

+33-3
Original file line numberDiff line numberDiff line change
@@ -1305,12 +1305,42 @@ function isCurrency(str, options) {
13051305

13061306
/* eslint-disable max-len */
13071307
// from http://goo.gl/0ejHHW
1308-
var iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
1308+
var iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
13091309
/* eslint-enable max-len */
1310+
var isValidDate = function isValidDate(str) {
1311+
// str must have passed the ISO8601 check
1312+
// this check is meant to catch invalid dates
1313+
// like 2009-02-31
1314+
// first check for ordinal dates
1315+
var ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
1316+
if (ordinalMatch) {
1317+
var oYear = Number(ordinalMatch[1]);
1318+
var oDay = Number(ordinalMatch[2]);
1319+
// if is leap year
1320+
if (oYear % 4 === 0 && oYear % 100 !== 0) {
1321+
return oDay <= 366;
1322+
}
1323+
return oDay <= 365;
1324+
}
1325+
var match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
1326+
var year = match[1];
1327+
var month = match[2];
1328+
var day = match[3];
1329+
// create a date object and compare
1330+
var d = new Date(year + '-' + (month || 1) + '-' + (day || 1));
1331+
if (isNaN(d.getFullYear())) return false;
1332+
if (month && day) {
1333+
return d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === day;
1334+
}
1335+
return true;
1336+
};
13101337

1311-
function isISO8601(str) {
1338+
function isISO8601(str, options) {
13121339
assertString(str);
1313-
return iso8601.test(str);
1340+
var check = iso8601.test(str);
1341+
if (!options) return check;
1342+
if (check && options.strict) return isValidDate(str);
1343+
return check;
13141344
}
13151345

13161346
/* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */

‎validator.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.