From 7f6d75be0899c2bc993edc2cefdd58728be00503 Mon Sep 17 00:00:00 2001 From: vycdev2 Date: Sat, 3 Oct 2026 01:06:37 +0000 Subject: [PATCH] fix: reject invalid ISO estimate dates --- CHANGELOG.md | 1 + src/utils/time.ts | 18 ++++++++++++++++++ test/time.spec.ts | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ea9f9..6a91a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Review fixes +- Rejected invalid ISO calendar dates in time estimates instead of silently rolling them into another month. - Preserved balanced closing parentheses, brackets, and braces in relative file-link filenames while excluding surrounding punctuation. - Preserved unsaved archive buffers, serialized simultaneous transfers, and replaced closed archive files only after a complete write. Failed writes, stale source documents, and rejected editor edits keep source tasks intact. - Preserved archive task order, sibling project structure, attached comment indentation, and standalone notes when removing empty projects. diff --git a/src/utils/time.ts b/src/utils/time.ts index 3e688d7..4ddc21a 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -272,6 +272,24 @@ const Time = { if (/^\s*\d+\s*$/.test(to)) return 0; + // Sugar normalizes impossible ISO dates (e.g. February 30) into another + // month; reject those explicitly rather than silently changing a deadline. + if ( + /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2})?)?$/.test(to) && + !moment( + to, + [ + 'YYYY-MM-DD', + 'YYYY-MM-DD HH:mm', + 'YYYY-MM-DD HH:mm:ss', + 'YYYY-MM-DDTHH:mm', + 'YYYY-MM-DDTHH:mm:ss', + ], + true + ).isValid() + ) + return 0; + // Parse duration strings before calendar expressions so they stay relative to `from`. try { const milliseconds = toTime(to).milliseconds(); diff --git a/test/time.spec.ts b/test/time.spec.ts index 9777a4a..c66f263 100644 --- a/test/time.spec.ts +++ b/test/time.spec.ts @@ -74,6 +74,14 @@ describe('Time utilities', () => { expect(Time.durationSeconds('2020-01-02', from)).to.equal(24 * 3600); }); + it('rejects invalid ISO calendar dates rather than rolling them into another month', () => { + const from = new Date(2020, 0, 1); + + expect(Time.durationSeconds('2020-02-30', from)).to.equal(0); + expect(Time.diffSeconds('2021-02-29 09:00', from)).to.equal(0); + expect(Time.durationSeconds('2020-02-29', from)).to.equal(59 * 24 * 3600); + }); + it('supports flat man-hours formatting', () => { const from = new Date('2020-01-01T00:00:00Z'); const to = new Date(from.getTime() + (25 * 3600 + 15 * 60) * 1000);