diff --git a/README.md b/README.md
index 8ef42fd79..99a2ddd31 100644
--- a/README.md
+++ b/README.md
@@ -129,7 +129,7 @@ Validator | Description
**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier).
**isISO6346(str)** | check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification.
**isISO6391(str)** | check if the string is a valid [ISO 639-1][ISO 639-1] language code.
-**isISO8601(str [, options])** | check if the string is a valid [ISO 8601][ISO 8601] date.
`options` is an object which defaults to `{ strict: false, strictSeparator: false }`. If `strict` is true, date strings with invalid dates like `2009-02-29` will be invalid. If `strictSeparator` is true, date strings with date and time separated by anything other than a T will be invalid.
+**isISO8601(str)** | check if the string is a valid [ISO 8601][ISO 8601] date.
**isISO15924(str)** | check if the string is a valid [ISO 15924][ISO 15924] officially assigned script code.
**isISO31661Alpha2(str [, options])** | check if the string is a valid [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2] officially assigned country code.
`options` is an object which can contain the key `userAssignedCodes`: an array of custom codes that are not officially assigned (e.g. `['XK']`).
**isISO31661Alpha3(str [, options])** | check if the string is a valid [ISO 3166-1 alpha-3][ISO 3166-1 alpha-3] officially assigned country code.
`options` is an object which can contain the key `userAssignedCodes`: an array of custom codes that are not officially assigned (e.g. `['XXK']`).
diff --git a/src/lib/isISO8601.js b/src/lib/isISO8601.js
index 1f797347d..5593d5b4a 100644
--- a/src/lib/isISO8601.js
+++ b/src/lib/isISO8601.js
@@ -2,43 +2,80 @@ 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-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)?)?)?)?$/;
-// same as above, except with a strict 'T' separator between date and time
-const iso8601StrictSeparator = /^([\+-]?\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]((([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}\.*|$)/);
+function isLeapYear(year) {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+function has53Week(year) {
+ const jan1 = new Date(Date.UTC(year, 0, 1)).getUTCDay();
+ return (jan1 === 4) || (isLeapYear(year) && jan1 === 3);
+}
+
+const isValidIso8601 = (str) => {
+ const iso8601 = /^(?:\d{4}(?:-\d{2}(?:-\d{2})?|-\d{3}|-W\d{2}(?:-\d)?)?)(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+\-]\d{2}:\d{2}|[+\-]\d{4}|[+\-]\d{2})?)?$/;
+ // this above regex check iso8601 format along with correct milisecond and second variation and T if time exist
+ // it rejects basic format(20251231) , only accepts date with extended format(2025-12-31).
+ // future Bug: it doesnot validate astronomical year, change the regex and fix its working if it's needed
+ const checkIso8601Format = iso8601.test(str);
+ if (!checkIso8601Format) {
+ return false;
+ }
+
+ // Not checking time for ordinary dates and dates with weeks as its uncommon, but can be added in future as it is valid format.
+ // need to check for ordinary dates
+ const ordinalMatch = str.match(/^(\d{4})-?(\d{3})([T]{1}\.*|$)/);
if (ordinalMatch) {
const oYear = Number(ordinalMatch[1]);
const oDay = Number(ordinalMatch[2]);
+ if (oDay < 1) return false;
// if is leap year
- if ((oYear % 4 === 0 && oYear % 100 !== 0) || oYear % 400 === 0) return oDay <= 366;
+ if (isLeapYear(oYear)) 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];
- const monthString = month ? `0${month}`.slice(-2) : month;
- const dayString = day ? `0${day}`.slice(-2) : day;
-
- // create a date object and compare
- const d = new Date(`${year}-${monthString || '01'}-${dayString || '01'}`);
+
+ // need to check for dates with week, dates with week and day
+ // only week match - issue if dates are with time weekmatch cannot reject it it will check only week part and return it, which i need to solve, same case for ordinal dates.
+ const WeekMatch = str.match(/^(\d{4})-W(\d{2})(?:-(\d))?$/);
+ if (WeekMatch) {
+ const [, yearStr, weekStr, dayStr] = WeekMatch;
+ const year = Number(yearStr);
+ const week = parseInt(weekStr, 10);
+ const day = dayStr ? parseInt(dayStr, 10) : null;
+ // check if week is in correct range
+ if (week < 1 || week > (has53Week(year) ? 53 : 52)) return false;
+ // check if week is last week of year it means 53 or 52, does it ends in between the last day
+ // check if day exist if it does it is in correct range
+ if (day != null) {
+ if (day < 1 || day > 7) return false;
+ }
+ return true;
+ }
+
+ // check for correct values in iso8601 format
+ if (isNaN(Date.parse(str))) {
+ return false;
+ }
+
+ // Final date check for correct range of date if it has month and day
+ // for edge cases like feb 30 is parsed in date.parse so it should be checked manually so date.parse check for out of 31 days and rest is passed through it
+ const dateMatch = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number);
+ const year = dateMatch[1];
+ const month = dateMatch[2];
+ const day = dateMatch[3];
+ // check for valid month and day
if (month && day) {
- return d.getUTCFullYear() === year
- && (d.getUTCMonth() + 1) === month
- && d.getUTCDate() === day;
+ const d = new Date(Date.UTC(year, month - 1, day));
+ return (
+ d.getUTCFullYear() === year &&
+ d.getUTCMonth() + 1 === month &&
+ d.getUTCDate() === day
+ );
}
+
return true;
};
-export default function isISO8601(str, options = {}) {
+export default function isISO8601(str) {
assertString(str);
- const check = options.strictSeparator ? iso8601StrictSeparator.test(str) : iso8601.test(str);
- if (check && options.strict) return isValidDate(str);
- return check;
+ return isValidIso8601(str);
}
diff --git a/test/validators.test.js b/test/validators.test.js
index a4c3d7193..8f729d5ce 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -12298,82 +12298,89 @@ 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',
- '2009-10-10',
- '2020-366',
- '2000-366',
+ '2020-W53-4',
+ '2024-02-29',
+ '2023-12-31',
+ '2023-365',
+ '2024-366',
+ '2023-03-15T12:30:45.123+05:30',
+ '2023-03-15T12:30:45Z',
+ '2023-03-15T12:30:45+0000',
+ '2023-03-15T12:30:45+00:00',
+ '2023-W01-1',
+ '2023-W52-7',
+ '2020-W01',
+ '2024-001',
+ '2023-03-15',
+ '2023-03',
+ '2023',
+ '2024-02-29',
+ '2023-02-28',
+ '2023-04-30',
+ '2023-01-31',
+ '2023-03',
+ '2023',
+ '2020-001',
+ '2023-W01',
+ '2023-03-15T12:30Z',
+ '2023-03-15T12:30:45.1Z',
+ '2023-03-15T12:30:45.12Z',
+ '2023-03-15T12:30:45.123456Z',
+ '2020-W53-4',
+ '2009-W53-4',
];
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',
+ '2019-W53-2',
+ '2020-W52-8',
+ '2012-W53-4',
+ '2019-366',
+ '2023-13-01',
+ '2023-02-30',
+ '2023-W00-2',
+ 'T12:00:00Z',
+ '2023-01-01T10:00:00+25:00',
+ '2023-01-01T10:00:00Zabc',
+ '2023-52-1',
+ '2020-W10-0',
+ '2020-W53-8',
+ '2020-W54-1',
+ '2020-W01-00',
+ '2024-367',
+ 'abcd-ef-gh',
+ '',
+ '123',
+ '2023-03-15T25:00:00Z',
+ '2023-03-15T12:60:00Z',
+ '2023-03-15T12:00:60Z',
'nonsense2021-01-01T00:00:00Z',
'2021-01-01T00:00:00Znonsense',
+ '2023-02-29',
+ '2023-02-30',
+ '2023-04-31',
+ '2023-01-32',
+ '2023-13',
+ '20A3',
+ '+001980-13-25',
+ '0000-01-01',
+ '2023-0032',
+ '2023-W01-8',
+ '2023-W01-0',
+ '2023-W01-01',
+ '2023-03-15T',
+ '2023-03-15T12:',
+ '2023-03-15T12:30:Z',
+ '2023-03-15T12:30:45.',
+ '2023-03-15T12:30:45.12+2400',
+ '2023-03-15T12:30:45+2400',
+ '2023-03-15T12:30:45+2360',
+ '2023-W1',
+ '2023-W01-',
+ '2023-03-15T12:30:45.123+01',
+ '2000-000',
];
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,
@@ -12381,146 +12388,6 @@ describe('Validators', () => {
});
});
- 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: [
- '2000-02-29',
- '2009-123',
- '2009-222',
- '2020-366',
- '2400-366',
- ],
- invalid: [
- '2010-02-30',
- '2009-02-29',
- '2009-366',
- '2019-02-31',
- ],
- });
- });
-
- it('should validate ISO 8601 dates, with strictSeparator = true', () => {
- test({
- validator: 'isISO8601',
- args: [
- { strictSeparator: true },
- ],
- valid: [
- '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-19T14:39Z',
- '2009-W21-2',
- '2009-W21-2T01:22',
- '2009-139',
- '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-10-10',
- '2020-366',
- '2000-366',
- ],
- 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',
- '2010-13-1',
- '2009-05-19 00:00',
- // Previously valid cases
- '2009-05-19 14',
- '2009-05-19 14:31',
- '2009-05-19 14:39:22',
- '2009-05-19 14:39:22-06:00',
- '2009-05-19 14:39:22+0600',
- '2009-05-19 14:39:22-01',
- ],
- });
- });
-
- it('should validate ISO 8601 dates, with strict = true and strictSeparator = true (regression)', () => {
- test({
- validator: 'isISO8601',
- args: [
- { strict: true, strictSeparator: true },
- ],
- valid: [
- '2000-02-29',
- '2009-123',
- '2009-222',
- '2020-366',
- '2400-366',
- ],
- invalid: [
- '2010-02-30',
- '2009-02-29',
- '2009-366',
- '2019-02-31',
- '2009-05-19 14',
- '2009-05-19 14:31',
- '2009-05-19 14:39:22',
- '2009-05-19 14:39:22-06:00',
- '2009-05-19 14:39:22+0600',
- '2009-05-19 14:39:22-01',
- ],
- });
- });
-
it('should validate ISO 15924 script codes', () => {
test({
validator: 'isISO15924',