From af128bd4cd23c36bcdbd3dc30de6048d416ed645 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 11 Oct 2026 09:51:14 -0400 Subject: [PATCH] fix(regional): keep impossible compact article dates unknown --- CHANGELOG.md | 1 + docs/CURRENT-STATE.md | 5 +++ src/data/regional-calendar-evidence.test.mjs | 43 ++++++++++++++++++++ src/data/regionalModel.js | 17 ++++++-- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 src/data/regional-calendar-evidence.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index a770fbd35..6212e745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Tool string length validation counts Unicode characters correctly, including characters outside the basic multilingual plane. - Director timeline seeks cross the dateline along the short longitude arc. +- Regional articles retain unknown publication times when compact source dates do not identify a real calendar instant. - Treat explicitly current, complete empty vessel coverage as a successful update, clearing obsolete contacts while retaining stale or incomplete snapshots. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 1f794a5b5..03d98aa1c 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5326,3 +5326,8 @@ datacenters, dams and the Nepal locator retain that shared credit while displaye Military fragments stitch by source identity. Different mapped parcels merge only when their footprints touch or contain one another, never across a seam tolerance gap. + + +### fix(regional): keep impossible compact article dates unknown + +Round-trip compact timestamps through a valid Date and retain the article with an unknown timestamp when its source date is impossible. Valid compact and ordinary ISO output formats are preserved. diff --git a/src/data/regional-calendar-evidence.test.mjs b/src/data/regional-calendar-evidence.test.mjs new file mode 100644 index 000000000..a442a9feb --- /dev/null +++ b/src/data/regional-calendar-evidence.test.mjs @@ -0,0 +1,43 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { normalizeRegionalArticles } from './regionalModel.js'; +test('compact source dates must denote an actual calendar instant', () => { + for (const date of [ + '20260230T120000Z', + '20260431T120000Z', + '20261301T120000Z', + '20260716T240000Z', + '20260716T126000Z', + '20260716T120060Z', + ]) { + const [article] = normalizeRegionalArticles({ + articles: [ + { + title: 'Fixture', + url: 'https://fixture.example/news', + seendate: date, + }, + ], + }); + assert.equal(article.publishedAt, null, date); + assert.equal(article.title, 'Fixture'); + } + for (const [input, expected] of [ + ['20240229T004500Z', '2024-02-29T00:45:00Z'], + ['20260716T000000Z', '2026-07-16T00:00:00Z'], + ['2026-07-16T12:00:00Z', '2026-07-16T12:00:00.000Z'], + ]) { + assert.equal( + normalizeRegionalArticles({ + articles: [ + { + title: 'Fixture', + url: 'https://fixture.example/news', + seendate: input, + }, + ], + })[0].publishedAt, + expected, + ); + } +}); diff --git a/src/data/regionalModel.js b/src/data/regionalModel.js index 3a6d79f14..fca473994 100644 --- a/src/data/regionalModel.js +++ b/src/data/regionalModel.js @@ -67,11 +67,20 @@ export function normalizeRegionalArticles(payload, limit = MAX_ARTICLES) { seen.add(signature); const rawDate = cleanText(row?.seendate, 32); const compactDate = /^(\d{8})T(\d{6})Z$/.exec(rawDate); - const publishedAt = compactDate + const candidateDate = compactDate ? `${compactDate[1].slice(0, 4)}-${compactDate[1].slice(4, 6)}-${compactDate[1].slice(6, 8)}T${compactDate[2].slice(0, 2)}:${compactDate[2].slice(2, 4)}:${compactDate[2].slice(4, 6)}Z` - : Number.isNaN(Date.parse(rawDate)) - ? null - : new Date(rawDate).toISOString(); + : rawDate; + const parsedMs = Date.parse(candidateDate); + const validDate = + Number.isFinite(parsedMs) && + (!compactDate || + new Date(parsedMs).toISOString().slice(0, 19) === + candidateDate.slice(0, 19)); + const publishedAt = validDate + ? compactDate + ? candidateDate + : new Date(parsedMs).toISOString() + : null; articles.push({ title, url,