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',