From 69404afbb6fb1b5e5b9b973d6824add72f53ffa4 Mon Sep 17 00:00:00 2001 From: prasanth Date: Thu, 11 Jun 2026 20:20:01 +0530 Subject: [PATCH] feat(isISO8601): add fractionalSecondsOnly option to require seconds before a fraction (#2003) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- src/lib/isISO8601.js | 9 ++++++++- test/validators.test.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ef42fd79..6815ed76a 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 [, options])** | check if the string is a valid [ISO 8601][ISO 8601] date.
`options` is an object which defaults to `{ strict: false, strictSeparator: false, fractionalSecondsOnly: 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. If `fractionalSecondsOnly` is true, a decimal fraction is only allowed directly after a full seconds field (e.g. `2018-09-11T10:16:00.000Z`), so fractions attached to minutes or hours like `2018-09-11T10:16.000Z` will be invalid. **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..002d46477 100644 --- a/src/lib/isISO8601.js +++ b/src/lib/isISO8601.js @@ -5,6 +5,9 @@ import assertString from './util/assertString'; 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)?)?)?)?$/; +// a decimal fraction is only valid directly after a full seconds field (HH:MM:SS or HHMMSS); +// separators must match (\2): both ':' or both absent, per ISO basic/extended forms +const fractionalSecondsBeforeSeconds = /[T\s]([01]\d|2[0-3])(:?)[0-5]\d\2[0-5]\d[.,]\d/; /* eslint-enable max-len */ const isValidDate = (str) => { // str must have passed the ISO8601 check @@ -39,6 +42,10 @@ const isValidDate = (str) => { export default function isISO8601(str, options = {}) { assertString(str); const check = options.strictSeparator ? iso8601StrictSeparator.test(str) : iso8601.test(str); - if (check && options.strict) return isValidDate(str); + if (!check) return false; + if (options.fractionalSecondsOnly && /[.,]\d/.test(str) && !fractionalSecondsBeforeSeconds.test(str)) { + return false; + } + if (options.strict) return isValidDate(str); return check; } diff --git a/test/validators.test.js b/test/validators.test.js index a4c3d7193..f3c4557ff 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -12521,6 +12521,35 @@ describe('Validators', () => { }); }); + it('should validate ISO 8601 dates, with fractionalSecondsOnly = true', () => { + test({ + validator: 'isISO8601', + args: [ + { fractionalSecondsOnly: true }, + ], + valid: [ + '2009', + '2009-05-19', + '2009-05-19T14:39Z', + '2009-05-19 14:39:22', + '2010-02-18T16:23:48.5', + '2010-02-18T16:23:48,444', + '2010-02-18T16:23:48,3-06:00', + '2009-05-19 143922.500', + ], + invalid: [ + // a fraction attached to minutes or hours (no seconds present) must be rejected + '2018-09-11T10:16.000Z', + '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 1439,55', + ], + }); + }); + it('should validate ISO 15924 script codes', () => { test({ validator: 'isISO15924',