From 76e9d91db9398247800e0ac474943f6964db9757 Mon Sep 17 00:00:00 2001 From: yfwmaniish Date: Tue, 25 Aug 2026 21:26:08 +0000 Subject: [PATCH 1/2] fix(isISO8601): validate week dates in strict mode isValidDate() had no branch for week dates (YYYY-Www[-D]), so isISO8601(str, { strict: true }) never actually validated them -- it fell through to the ordinal/calendar-date branch, which always returned true for any string matching the loose /(\d{4})-?(\d{0,2})-?(\d*)/ capture (week strings capture only the year, since "Www" isn't digits). The main iso8601 regex already constrains the week field to 01-53 syntactically, but only weeks 01-52 exist in every ISO week-numbering year -- week 53 exists only in "long" years (53 ISO weeks). Added hasISOWeek53() using the standard algorithm (move 31 Dec to the Thursday of its own ISO week, compare that Thursday's year-relative week number) and a weekMatch branch in isValidDate that applies it. Fixes #2859. --- src/lib/isISO8601.js | 21 +++++++++++++++++++++ test/validators.test.js | 12 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/lib/isISO8601.js b/src/lib/isISO8601.js index 6eea9ae27..d0e92d047 100644 --- a/src/lib/isISO8601.js +++ b/src/lib/isISO8601.js @@ -6,6 +6,20 @@ const iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9] // 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[1-9]|[1-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[0-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 */ +// Returns true iff ISO week-numbering year `year` has a 53rd week, i.e. 31 +// December of that year falls within ISO week 53 rather than week 1 of the +// following year. Only week 53 is year-dependent -- weeks 01-52 always exist +// -- and the main iso8601 regex already constrains the week field to 01-53 +// syntactically, so this is the only additional check strict mode needs. +const hasISOWeek53 = (year) => { + const dec31 = new Date(Date.UTC(year, 11, 31)); + const dayNum = dec31.getUTCDay() || 7; // Sunday (0) -> 7, so Mon=1..Sun=7 + dec31.setUTCDate(dec31.getUTCDate() + (4 - dayNum)); // Thursday of the same ISO week + const yearStart = new Date(Date.UTC(dec31.getUTCFullYear(), 0, 1)); + const week = Math.ceil((((dec31 - yearStart) / 86400000) + 1) / 7); + return week === 53; +}; + const isValidDate = (str) => { // str must have passed the ISO8601 check // this check is meant to catch invalid dates @@ -19,6 +33,13 @@ const isValidDate = (str) => { if ((oYear % 4 === 0 && oYear % 100 !== 0) || oYear % 400 === 0) return oDay <= 366; return oDay <= 365; } + // then check for week dates (YYYY-Www or YYYY-Www-D, basic or extended) + const weekMatch = str.match(/^([\+-]?\d{4})-?W(\d{2})/); + if (weekMatch) { + const wYear = Number(weekMatch[1]); + const week = Number(weekMatch[2]); + return week <= 52 || hasISOWeek53(wYear); + } const match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number); const year = match[1]; const month = match[2]; diff --git a/test/validators.test.js b/test/validators.test.js index 98d2a12ff..cb41b88a3 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -12443,12 +12443,24 @@ describe('Validators', () => { '2009-222', '2020-366', '2400-366', + // week 53 exists in these ISO week-numbering years (long years) + '2015-W53-1', + '2015-W53', + '2020-W53-7', + '2026-W53', ], invalid: [ '2010-02-30', '2009-02-29', '2009-366', '2019-02-31', + // week 53 does not exist in these ISO week-numbering years (short + // years) -- regression test for #2859: previously strict mode never + // validated week dates at all, so these were wrongly accepted. + '2019-W53-1', + '2019-W53', + '2021-W53-7', + '2022-W53', ], }); }); From b8035a9784ced7a77500839cd3f15a531022d588 Mon Sep 17 00:00:00 2001 From: yfwmaniish Date: Wed, 26 Aug 2026 06:16:08 +0000 Subject: [PATCH 2/2] test(isISO8601): cover the remaining week-date branches codecov flagged the strict week-date patch at 91.66% -- one of the three new branches was never taken. Two of the added lines short-circuit on data the existing cases never supply: - `week <= 52 || hasISOWeek53(year)` only ever evaluated the right-hand side, because every new case used week 53. Added '2020-W10' and '2020-W10-3'. - `dec31.getUTCDay() || 7` only remaps Sunday, and 31 December falls on a Sunday in none of the years tested so far. Added '2017-W53' -- 31 Dec 2017 is a Sunday and 2017 is a short year, so it exercises the remap and stays correctly invalid. isISO8601.js is now at 100% statement, branch, function and line coverage. Co-Authored-By: Claude Opus 5 --- test/validators.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/validators.test.js b/test/validators.test.js index cb41b88a3..a6f54ec5e 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -12448,6 +12448,9 @@ describe('Validators', () => { '2015-W53', '2020-W53-7', '2026-W53', + // weeks 01-52 exist in every ISO week-numbering year, long or short + '2020-W10', + '2020-W10-3', ], invalid: [ '2010-02-30', @@ -12461,6 +12464,9 @@ describe('Validators', () => { '2019-W53', '2021-W53-7', '2022-W53', + // 31 December 2017 is a Sunday, so the ISO remap of getUTCDay() 0 -> 7 + // runs here; 2017 is still a short year, so week 53 does not exist. + '2017-W53', ], }); });