diff --git a/frontend/src/utils/__tests__/resolutionDate.property.test.js b/frontend/src/utils/__tests__/resolutionDate.property.test.js index aa38c65..bb859a2 100644 --- a/frontend/src/utils/__tests__/resolutionDate.property.test.js +++ b/frontend/src/utils/__tests__/resolutionDate.property.test.js @@ -43,6 +43,14 @@ function isValidCalendarYmd(s) { return true; } +// True iff `s` is an ISO datetime string whose date prefix is a valid calendar date. +// e.g. "2026-07-01T00:00:00.000Z" → true (the helper now extracts the date prefix). +function isIsoDateTimeWithValidDate(s) { + if (typeof s !== 'string') return false; + if (!/^\d{4}-\d{2}-\d{2}T/.test(s)) return false; + return isValidCalendarYmd(s.slice(0, 10)); +} + // --- Shared arbitraries ----------------------------------------------------- // Four-digit zero-padded year string (0000–9999) — always matches \d{4}. @@ -130,7 +138,7 @@ const wrongShapeArb = fc.oneof( const invalidStringArb = fc .oneof(wrongShapeArb, badMonthArb, badDayArb, impossibleDayArb, twoDigitArb) - .filter(s => typeof s === 'string' && s.trim() !== '' && !isValidCalendarYmd(s.trim())); + .filter(s => typeof s === 'string' && s.trim() !== '' && !isValidCalendarYmd(s.trim()) && !isIsoDateTimeWithValidDate(s.trim())); // Any input category (used for totality / independence properties). const anyInputArb = fc.oneof( diff --git a/frontend/src/utils/__tests__/resolutionDate.test.js b/frontend/src/utils/__tests__/resolutionDate.test.js index 0cb346d..aa40d23 100644 --- a/frontend/src/utils/__tests__/resolutionDate.test.js +++ b/frontend/src/utils/__tests__/resolutionDate.test.js @@ -32,6 +32,13 @@ describe('formatResolutionDate', () => { value: '2024-02-29', }); }); + + it("classifies an ISO datetime '2026-07-03T00:00:00.000Z' as set with the date prefix", () => { + expect(formatResolutionDate('2026-07-03T00:00:00.000Z')).toEqual({ + state: 'set', + value: '2026-07-03', + }); + }); }); describe('invalid — present but not a valid calendar date (Requirement 1.6)', () => { diff --git a/frontend/src/utils/resolutionDate.js b/frontend/src/utils/resolutionDate.js index 64178c8..34845ba 100644 --- a/frontend/src/utils/resolutionDate.js +++ b/frontend/src/utils/resolutionDate.js @@ -63,14 +63,20 @@ export function formatResolutionDate(raw) { } // Must match the strict YYYY-MM-DD shape. - if (!YMD_SHAPE.test(trimmed)) { + // Also accept ISO datetime strings (e.g. "2026-07-03T00:00:00.000Z") by + // extracting the date prefix — the pg driver returns DATE columns this way. + let candidate = trimmed; + if (!YMD_SHAPE.test(candidate) && /^\d{4}-\d{2}-\d{2}T/.test(candidate)) { + candidate = candidate.slice(0, 10); + } + if (!YMD_SHAPE.test(candidate)) { return { state: 'invalid' }; } // Shape is correct; verify it is a real calendar date. - const year = Number(trimmed.slice(0, 4)); - const month = Number(trimmed.slice(5, 7)); - const day = Number(trimmed.slice(8, 10)); + const year = Number(candidate.slice(0, 4)); + const month = Number(candidate.slice(5, 7)); + const day = Number(candidate.slice(8, 10)); if (month < 1 || month > 12) { return { state: 'invalid' }; @@ -80,7 +86,7 @@ export function formatResolutionDate(raw) { return { state: 'invalid' }; } - // Valid calendar date; the trimmed value is already the canonical + // Valid calendar date; the candidate value is the canonical // zero-padded YYYY-MM-DD form. - return { state: 'set', value: trimmed }; + return { state: 'set', value: candidate }; }