From 28e6a1951f39664f721620470f8003876b219686 Mon Sep 17 00:00:00 2001 From: RAM PRASAD ATLURI <116977270+ATLURI0001@users.noreply.github.com> Date: Wed, 11 Nov 2026 09:26:55 +0530 Subject: [PATCH] fix(firms): reject blank/out-of-range CSV coords and UTC calendar rollover --- src/data/firmsCsv.js | 32 +++++++++++++++++++++++++++----- src/data/firmsCsv.test.mjs | 11 +++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/data/firmsCsv.js b/src/data/firmsCsv.js index 138b1a4..91b0919 100644 --- a/src/data/firmsCsv.js +++ b/src/data/firmsCsv.js @@ -97,9 +97,21 @@ export function parseFirmsCsv(text) { if (!line) continue; const parts = line.split(','); if (parts.length < header.length) continue; // malformed row — skip - const lat = Number(parts[iLat]); - const lon = Number(parts[iLon]); - if (!Number.isFinite(lat) || !Number.isFinite(lon)) continue; + const latText = parts[iLat]?.trim() ?? ''; + const lonText = parts[iLon]?.trim() ?? ''; + if (!latText || !lonText) continue; + const lat = Number(latText); + const lon = Number(lonText); + if ( + !Number.isFinite(lat) || + !Number.isFinite(lon) || + lat < -90 || + lat > 90 || + lon < -180 || + lon > 180 + ) { + continue; + } records.push({ lat, @@ -145,9 +157,19 @@ export function acquisitionMsUtc(acqDate, acqTime) { day > 31 || hours > 23 || minutes > 59 - ) + ) { return NaN; - return Date.UTC(year, month - 1, day, hours, minutes); + } + const ms = Date.UTC(year, month - 1, day, hours, minutes); + const check = new Date(ms); + if ( + check.getUTCFullYear() !== year || + check.getUTCMonth() !== month - 1 || + check.getUTCDate() !== day + ) { + return NaN; + } + return ms; } /** diff --git a/src/data/firmsCsv.test.mjs b/src/data/firmsCsv.test.mjs index 798946e..7d6da5a 100644 --- a/src/data/firmsCsv.test.mjs +++ b/src/data/firmsCsv.test.mjs @@ -69,6 +69,14 @@ test('acquisitionMsUtc: "0" = midnight UTC; garbage → NaN', () => { ); assert.ok(Number.isNaN(acquisitionMsUtc('not-a-date', '1006'))); assert.ok(Number.isNaN(acquisitionMsUtc('2026-07-16', 'xx'))); + // Calendar rollover dates (e.g. Feb 30, Apr 31, non-leap Feb 29) -> NaN + assert.ok(Number.isNaN(acquisitionMsUtc('2026-02-30', '1006'))); + assert.ok(Number.isNaN(acquisitionMsUtc('2026-04-31', '1006'))); + assert.ok(Number.isNaN(acquisitionMsUtc('2026-02-29', '1006'))); + assert.equal( + acquisitionMsUtc('2024-02-29', '1006'), + Date.UTC(2024, 1, 29, 10, 6), + ); }); test('header-only input → []', () => { @@ -90,6 +98,9 @@ test('malformed rows are skipped, valid rows kept', () => { 'not,enough,fields', 'garbage-line-with-no-commas', 'NaN,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', + ',-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', + '95.0,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', + '38.99488,-181.5,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', '38.99488,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', ].join('\n'); const records = parseFirmsCsv(text);