Skip to content

Commit

Permalink
fix: extra validation for dates (#910)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
profnandaa authored Oct 15, 2018
1 parent 509324f commit dad8961
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 74 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Validator | Description
**isISBN(str [, version])** | check if the string is an ISBN (version 10 or 13).
**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.
**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier).
**isISO8601(str)** | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date.
**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`.
**isRFC3339(str)** | check if the string is a valid [RFC 3339](https://tools.ietf.org/html/rfc3339) date.
**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.
**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.
Expand Down
36 changes: 33 additions & 3 deletions lib/isISO8601.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,41 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de

/* eslint-disable max-len */
// from http://goo.gl/0ejHHW
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)?)?)?)?$/;
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)?)?)?)?$/;
/* eslint-enable max-len */
var isValidDate = function isValidDate(str) {
// str must have passed the ISO8601 check
// this check is meant to catch invalid dates
// like 2009-02-31
// first check for ordinal dates
var ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
if (ordinalMatch) {
var oYear = Number(ordinalMatch[1]);
var oDay = Number(ordinalMatch[2]);
// if is leap year
if (oYear % 4 === 0 && oYear % 100 !== 0) {
return oDay <= 366;
}
return oDay <= 365;
}
var match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
var year = match[1];
var month = match[2];
var day = match[3];
// create a date object and compare
var d = new Date(year + '-' + (month || 1) + '-' + (day || 1));
if (isNaN(d.getFullYear())) return false;
if (month && day) {
return d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === day;
}
return true;
};

function isISO8601(str) {
function isISO8601(str, options) {
(0, _assertString2.default)(str);
return iso8601.test(str);
var check = iso8601.test(str);
if (!options) return check;
if (check && options.strict) return isValidDate(str);
return check;
}
module.exports = exports['default'];
37 changes: 34 additions & 3 deletions src/lib/isISO8601.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,41 @@ import assertString from './util/assertString';

/* eslint-disable max-len */
// from http://goo.gl/0ejHHW
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)?)?)?)?$/;
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)?)?)?)?$/;
/* eslint-enable max-len */
const isValidDate = (str) => {
// str must have passed the ISO8601 check
// this check is meant to catch invalid dates
// like 2009-02-31
// first check for ordinal dates
const ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
if (ordinalMatch) {
const oYear = Number(ordinalMatch[1]);
const oDay = Number(ordinalMatch[2]);
// if is leap year
if (oYear % 4 === 0
&& oYear % 100 !== 0) return oDay <= 366;
return oDay <= 365;
}
const match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
const year = match[1];
const month = match[2];
const day = match[3];
// create a date object and compare
const d = new Date(`${year}-${month || 1}-${day || 1}`);
if (isNaN(d.getFullYear())) return false;
if (month && day) {
return d.getFullYear() === year
&& (d.getMonth() + 1) === month
&& d.getDate() === day;
}
return true;
};

export default function isISO8601(str) {
export default function isISO8601(str, options) {
assertString(str);
return iso8601.test(str);
const check = iso8601.test(str);
if (!options) return check;
if (check && options.strict) return isValidDate(str);
return check;
}
161 changes: 98 additions & 63 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -5500,76 +5500,111 @@ describe('Validators', () => {
});
});

const validISO8601 = [
'2009-12T12:34',
'2009',
'2009-05-19',
'2009-05-19',
'20090519',
'2009123',
'2009-05',
'2009-123',
'2009-222',
'2009-001',
'2009-W01-1',
'2009-W51-1',
'2009-W511',
'2009-W33',
'2009W511',
'2009-05-19',
'2009-05-19 00:00',
'2009-05-19 14',
'2009-05-19 14:31',
'2009-05-19 14:39:22',
'2009-05-19T14:39Z',
'2009-W21-2',
'2009-W21-2T01:22',
'2009-139',
'2009-05-19 14:39:22-06:00',
'2009-05-19 14:39:22+0600',
'2009-05-19 14:39:22-01',
'20090621T0545Z',
'2007-04-06T00:00',
'2007-04-05T24:00',
'2010-02-18T16:23:48.5',
'2010-02-18T16:23:48,444',
'2010-02-18T16:23:48,3-06:00',
'2010-02-18T16:23.4',
'2010-02-18T16:23,25',
'2010-02-18T16:23.33+0600',
'2010-02-18T16.23334444',
'2010-02-18T16,2283',
'2009-05-19 143922.500',
'2009-05-19 1439,55',
];

const invalidISO8601 = [
'200905',
'2009367',
'2009-',
'2007-04-05T24:50',
'2009-000',
'2009-M511',
'2009M511',
'2009-05-19T14a39r',
'2009-05-19T14:3924',
'2009-0519',
'2009-05-1914:39',
'2009-05-19 14:',
'2009-05-19r14:39',
'2009-05-19 14a39a22',
'200912-01',
'2009-05-19 14:39:22+06a00',
'2009-05-19 146922.500',
'2010-02-18T16.5:23.35:48',
'2010-02-18T16:23.35:48',
'2010-02-18T16:23.35:48.45',
'2009-05-19 14.5.44',
'2010-02-18T16:23.33.600',
'2010-02-18T16,25:23:48,444',
'2010-13-1',
];

it('should validate ISO 8601 dates', () => {
// from http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
test({
validator: 'isISO8601',
valid: validISO8601,
invalid: invalidISO8601,
});
});

it('should validate ISO 8601 dates, with strict = true (regression)', () => {
test({
validator: 'isISO8601',
args: [
{ strict: true },
],
valid: validISO8601,
invalid: invalidISO8601,
});
});

it('should validate ISO 8601 dates, with strict = true', () => {
test({
validator: 'isISO8601',
args: [
{ strict: true },
],
valid: [
'2009-12T12:34',
'2009',
'2009-05-19',
'2009-05-19',
'20090519',
'2009123',
'2009-05',
'2000-02-29',
'2009-123',
'2009-222',
'2009-001',
'2009-W01-1',
'2009-W51-1',
'2009-W511',
'2009-W33',
'2009W511',
'2009-05-19',
'2009-05-19 00:00',
'2009-05-19 14',
'2009-05-19 14:31',
'2009-05-19 14:39:22',
'2009-05-19T14:39Z',
'2009-W21-2',
'2009-W21-2T01:22',
'2009-139',
'2009-05-19 14:39:22-06:00',
'2009-05-19 14:39:22+0600',
'2009-05-19 14:39:22-01',
'20090621T0545Z',
'2007-04-06T00:00',
'2007-04-05T24:00',
'2010-02-18T16:23:48.5',
'2010-02-18T16:23:48,444',
'2010-02-18T16:23:48,3-06:00',
'2010-02-18T16:23.4',
'2010-02-18T16:23,25',
'2010-02-18T16:23.33+0600',
'2010-02-18T16.23334444',
'2010-02-18T16,2283',
'2009-05-19 143922.500',
'2009-05-19 1439,55',
],
invalid: [
'200905',
'2009367',
'2009-',
'2007-04-05T24:50',
'2009-000',
'2009-M511',
'2009M511',
'2009-05-19T14a39r',
'2009-05-19T14:3924',
'2009-0519',
'2009-05-1914:39',
'2009-05-19 14:',
'2009-05-19r14:39',
'2009-05-19 14a39a22',
'200912-01',
'2009-05-19 14:39:22+06a00',
'2009-05-19 146922.500',
'2010-02-18T16.5:23.35:48',
'2010-02-18T16:23.35:48',
'2010-02-18T16:23.35:48.45',
'2009-05-19 14.5.44',
'2010-02-18T16:23.33.600',
'2010-02-18T16,25:23:48,444',
],
invalid: [
'2010-02-30',
'2009-02-29',
'2009-366',
],
});
});
Expand Down
36 changes: 33 additions & 3 deletions validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1305,12 +1305,42 @@ function isCurrency(str, options) {

/* eslint-disable max-len */
// from http://goo.gl/0ejHHW
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)?)?)?)?$/;
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)?)?)?)?$/;
/* eslint-enable max-len */
var isValidDate = function isValidDate(str) {
// str must have passed the ISO8601 check
// this check is meant to catch invalid dates
// like 2009-02-31
// first check for ordinal dates
var ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/);
if (ordinalMatch) {
var oYear = Number(ordinalMatch[1]);
var oDay = Number(ordinalMatch[2]);
// if is leap year
if (oYear % 4 === 0 && oYear % 100 !== 0) {
return oDay <= 366;
}
return oDay <= 365;
}
var match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
var year = match[1];
var month = match[2];
var day = match[3];
// create a date object and compare
var d = new Date(year + '-' + (month || 1) + '-' + (day || 1));
if (isNaN(d.getFullYear())) return false;
if (month && day) {
return d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === day;
}
return true;
};

function isISO8601(str) {
function isISO8601(str, options) {
assertString(str);
return iso8601.test(str);
var check = iso8601.test(str);
if (!options) return check;
if (check && options.strict) return isValidDate(str);
return check;
}

/* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */
Expand Down
2 changes: 1 addition & 1 deletion validator.min.js

Large diffs are not rendered by default.

0 comments on commit dad8961

Please sign in to comment.