From 78b7a811777d567a583e1f8d5a0206bd8a1dddab Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Sat, 23 May 2026 23:51:07 +0530 Subject: [PATCH 1/6] fix: harden date parsing to reject unrealistic years and invalid strings #107 --- frontend/src/utils/date.ts | 12 ++++++++++-- frontend/testing/unit/utils/date.test.ts | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 5b0fc02be..3f5aab03d 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -20,8 +20,16 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null : [`${isoCompatible}Z`, isoCompatible, raw] for (const candidate of candidates) { - const d = new Date(candidate) - if (!Number.isNaN(d.getTime())) return d + const d = new Date(candidate) + + // 1. Check if the date is technically valid + const isValid = !Number.isNaN(d.getTime()) + + // 2. Sanity check: Ensure the year is between 1900 and 2100 + // This is what will catch the "99999" error from your screenshot + const isRealistic = isValid && d.getFullYear() > 1900 && d.getFullYear() < 2100 + + if (isRealistic) return d } } catch (error) { console.error('Date parsing failed:', error, raw) diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 7d878d329..470c6dd45 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -112,4 +112,23 @@ import { expect(result.time).not.toBe("UNKNOWN TIME"); }); }); - }); \ No newline at end of file + }); + + describe("Issue #107: Invalid Date Handling", () => { + test("returns N/A for completely random strings", () => { + // This should fail initially because the current function + // might return 'Invalid Date' or crash instead of 'N/A' + expect(formatLocaleDate("not-a-date")).toBe("N/A"); + }); + + test("returns N/A for impossible calendar dates", () => { + // This catches dates that JavaScript usually 'overflows' + // like turning month 13 into next year + expect(formatLocaleDate("2026-13-45")).toBe("N/A"); + }); + + test("returns N/A for numeric strings that aren't timestamps", () => { + // Prevents random 5-digit strings from being parsed as years + expect(formatLocaleDate("99999")).toBe("N/A"); + }); +}); \ No newline at end of file From 3184bc10c524282aa25b7d6ee28ef0be227af756 Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Sun, 24 May 2026 00:13:32 +0530 Subject: [PATCH 2/6] fix: refine comments and harden date validation logic #107 --- frontend/src/utils/date.ts | 1 - frontend/testing/unit/utils/date.test.ts | 33 ++++++++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 3f5aab03d..10606f540 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -26,7 +26,6 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null const isValid = !Number.isNaN(d.getTime()) // 2. Sanity check: Ensure the year is between 1900 and 2100 - // This is what will catch the "99999" error from your screenshot const isRealistic = isValid && d.getFullYear() > 1900 && d.getFullYear() < 2100 if (isRealistic) return d diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 470c6dd45..38e11a7b2 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -114,21 +114,20 @@ import { }); }); - describe("Issue #107: Invalid Date Handling", () => { - test("returns N/A for completely random strings", () => { - // This should fail initially because the current function - // might return 'Invalid Date' or crash instead of 'N/A' - expect(formatLocaleDate("not-a-date")).toBe("N/A"); - }); + describe("Issue #107: Invalid Date Handling", () => { + test("returns N/A for completely random strings", () => { + // This should fail initially because the current function + // might return 'Invalid Date' or crash instead of 'N/A' + expect(formatLocaleDate("not-a-date")).toBe("N/A"); + }); - test("returns N/A for impossible calendar dates", () => { - // This catches dates that JavaScript usually 'overflows' - // like turning month 13 into next year - expect(formatLocaleDate("2026-13-45")).toBe("N/A"); - }); - - test("returns N/A for numeric strings that aren't timestamps", () => { - // Prevents random 5-digit strings from being parsed as years - expect(formatLocaleDate("99999")).toBe("N/A"); - }); -}); \ No newline at end of file + test("returns N/A for impossible calendar dates", () => { + // This catches dates that JavaScript usually 'overflows' + expect(formatLocaleDate("2026-13-45")).toBe("N/A"); + }); + + test("returns N/A for numeric strings that aren't timestamps", () => { + // Prevents random 5-digit strings from being parsed as years + expect(formatLocaleDate("99999")).toBe("N/A"); + }); + }); \ No newline at end of file From ebc5022c5c67022b8f396a2eb5b5b4bf502ec3ed Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Thu, 28 May 2026 22:28:17 +0530 Subject: [PATCH 3/6] chore: fix formatting hygiene, comment styles, and lint issues for #107 --- frontend/src/utils/date.ts | 24 +++++++++++------------- frontend/testing/unit/utils/date.test.ts | 12 +----------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 10606f540..9e5dc4cdb 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -8,10 +8,10 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null if (raw.toLowerCase() === 'now') return new Date() try { - // Handle formats like "YYYY-MM-DD HH:MM:SS" (SQLite) vs standard ISO-8601 + // Handle formats like "YYYY-MM-DD HH:MM:SS" (SQLite) vs standard ISO-8601 const isoCompatible = raw.includes('T') ? raw : raw.replace(' ', 'T') - - // Check if the string already has timezone info (e.g. "Z" or "+HH:MM") + + // Check if the string already has timezone info (e.g. "Z" or "+HH:MM") const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/.test(isoCompatible) // We try multiple candidate strings if timezone is missing @@ -20,15 +20,13 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null : [`${isoCompatible}Z`, isoCompatible, raw] for (const candidate of candidates) { - const d = new Date(candidate) - - // 1. Check if the date is technically valid - const isValid = !Number.isNaN(d.getTime()) - - // 2. Sanity check: Ensure the year is between 1900 and 2100 - const isRealistic = isValid && d.getFullYear() > 1900 && d.getFullYear() < 2100 - - if (isRealistic) return d + const d = new Date(candidate) + const isValid = !Number.isNaN(d.getTime()) + + // Filter out invalid dates and unrealistic years (e.g., year 99999) + if (isValid && d.getFullYear() > 1900 && d.getFullYear() < 2100) { + return d + } } } catch (error) { console.error('Date parsing failed:', error, raw) @@ -50,7 +48,7 @@ function getPreferredTimeZone(): string | undefined { } } } catch (e) { - // Fallback to system default + // Fallback to system default } return undefined; } diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 38e11a7b2..5bc229540 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -115,19 +115,9 @@ import { }); describe("Issue #107: Invalid Date Handling", () => { - test("returns N/A for completely random strings", () => { - // This should fail initially because the current function - // might return 'Invalid Date' or crash instead of 'N/A' + test("returns N/A for unrealistic or non-standard dates", () => { expect(formatLocaleDate("not-a-date")).toBe("N/A"); - }); - - test("returns N/A for impossible calendar dates", () => { - // This catches dates that JavaScript usually 'overflows' expect(formatLocaleDate("2026-13-45")).toBe("N/A"); - }); - - test("returns N/A for numeric strings that aren't timestamps", () => { - // Prevents random 5-digit strings from being parsed as years expect(formatLocaleDate("99999")).toBe("N/A"); }); }); \ No newline at end of file From 5f9a59f34b40c26dbcd168054b4f98ea95ad7f6b Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Thu, 28 May 2026 22:38:53 +0530 Subject: [PATCH 4/6] style: remove invisible trailing spaces on empty lines --- frontend/src/utils/date.ts | 26 +++++++++---------- frontend/testing/unit/utils/date.test.ts | 32 ++++++++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 9e5dc4cdb..932441f04 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -13,7 +13,7 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null // Check if the string already has timezone info (e.g. "Z" or "+HH:MM") const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/.test(isoCompatible) - + // We try multiple candidate strings if timezone is missing const candidates = hasTimezone ? [isoCompatible, raw] @@ -22,7 +22,7 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null for (const candidate of candidates) { const d = new Date(candidate) const isValid = !Number.isNaN(d.getTime()) - + // Filter out invalid dates and unrealistic years (e.g., year 99999) if (isValid && d.getFullYear() > 1900 && d.getFullYear() < 2100) { return d @@ -31,7 +31,7 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null } catch (error) { console.error('Date parsing failed:', error, raw) } - + return null } @@ -72,7 +72,7 @@ export function getCurrentTimeZone(): string { export function getTimeZoneAbbreviation(): string { try { const tz = getPreferredTimeZone(); - const formatter = new Intl.DateTimeFormat([], { + const formatter = new Intl.DateTimeFormat([], { timeZoneName: 'short', ...(tz ? { timeZone: tz } : {}) }); @@ -90,15 +90,15 @@ export function getTimeZoneAbbreviation(): string { export function formatBriefingDate(dateStr: string | null): string { const d = parseDateSafe(dateStr) if (!d) return '' - + const tz = getPreferredTimeZone(); const options: Intl.DateTimeFormatOptions = tz ? { timeZone: tz } : {}; - + const day = d.toLocaleDateString([], { ...options, day: '2-digit' }) const month = d.toLocaleDateString([], { ...options, month: 'short' }).toUpperCase() const year = d.toLocaleDateString([], { ...options, year: '2-digit' }) const time = d.toLocaleTimeString([], { ...options, hour: '2-digit', minute: '2-digit', hour12: false }) - + return `${day} ${month}, ${year}, ${time}` } @@ -108,7 +108,7 @@ export function formatBriefingDate(dateStr: string | null): string { export function formatTaskInit(dateStr: string): { date: string, time: string, tz: string } { const parsed = parseDateSafe(dateStr) if (!parsed) return { date: 'UNKNOWN DATE', time: 'UNKNOWN TIME', tz: '' } - + const tz = getPreferredTimeZone(); const date = parsed.toLocaleDateString([], { month: 'short', @@ -116,7 +116,7 @@ export function formatTaskInit(dateStr: string): { date: string, time: string, t year: 'numeric', ...(tz ? { timeZone: tz } : {}) }).toUpperCase() - + const time = parsed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', @@ -124,9 +124,9 @@ export function formatTaskInit(dateStr: string): { date: string, time: string, t hour12: false, ...(tz ? { timeZone: tz } : {}) }) - + const tzAbbr = getTimeZoneAbbreviation(); - + return { date, time, tz: tzAbbr } } @@ -136,7 +136,7 @@ export function formatTaskInit(dateStr: string): { date: string, time: string, t export function formatDateLong(dateStr: string | null): string { const d = parseDateSafe(dateStr) if (!d) return 'N/A' - + const tz = getPreferredTimeZone(); const formatted = d.toLocaleString([], { day: '2-digit', @@ -148,7 +148,7 @@ export function formatDateLong(dateStr: string | null): string { hour12: false, ...(tz ? { timeZone: tz } : {}) }).toUpperCase() - + const tzAbbr = getTimeZoneAbbreviation(); return tzAbbr ? `${formatted} ${tzAbbr}` : formatted; } diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 5bc229540..78075ae7e 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -8,55 +8,55 @@ import { getCurrentTimeZone, formatTaskInit, } from "../../../src/utils/date"; - + describe("date utilities", () => { beforeEach(() => { localStorage.clear(); }); - + describe("parseDateSafe", () => { test("parses ISO timestamp", () => { const result = parseDateSafe("2026-05-12T10:30:00Z"); expect(result).not.toBeNull(); expect(result instanceof Date).toBe(true); }); - + test("parses SQLite timestamp", () => { const result = parseDateSafe("2026-05-12 10:30:00"); expect(result).not.toBeNull(); }); - + test("returns null for invalid input", () => { expect(parseDateSafe("invalid-date")).toBeNull(); }); - + test("returns null for empty string", () => { expect(parseDateSafe("")).toBeNull(); }); - + test("returns null for null input", () => { expect(parseDateSafe(null)).toBeNull(); }); }); - + describe("formatDateLong", () => { test("formats valid date", () => { const result = formatDateLong("2026-05-12T10:30:00Z"); expect(result).not.toBe("N/A"); expect(result).toContain("2026"); }); - + test("returns N/A for invalid input", () => { expect(formatDateLong("bad-date")).toBe("N/A"); }); }); - + describe("formatLocaleDate", () => { test("formats valid date", () => { const result = formatLocaleDate("2026-05-12T10:30:00Z"); expect(result).not.toBe("N/A"); }); - + test("returns N/A for invalid input", () => { expect(formatLocaleDate("bad-date")).toBe("N/A"); }); @@ -69,13 +69,13 @@ import { expect(formatLocaleDate(undefined)).toBe("N/A"); }); }); - + describe("formatLocaleTime", () => { test("formats valid time", () => { const result = formatLocaleTime("2026-05-12T10:30:00Z"); expect(result).not.toBe("N/A"); }); - + test("returns N/A for invalid input", () => { expect(formatLocaleTime("bad-date")).toBe("N/A"); }); @@ -88,24 +88,24 @@ import { expect(formatLocaleTime(undefined)).toBe("N/A"); }); }); - + describe("timezone preference safety", () => { test("does not crash without localStorage config", () => { expect(() => formatLocaleDate("2026-05-12T10:30:00Z")).not.toThrow(); }); - + test("uses fallback timezone safely", () => { expect(getCurrentTimeZone()).toBeTruthy(); }); }); - + describe("formatTaskInit", () => { test("returns UNKNOWN values for invalid date", () => { const result = formatTaskInit("bad-date"); expect(result.date).toBe("UNKNOWN DATE"); expect(result.time).toBe("UNKNOWN TIME"); }); - + test("formats valid task date", () => { const result = formatTaskInit("2026-05-12T10:30:00Z"); expect(result.date).not.toBe("UNKNOWN DATE"); From d6ea70d41d7a30d524068219895bb26ebf14a00f Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Fri, 29 May 2026 12:44:38 +0530 Subject: [PATCH 5/6] style: manual indentation fix and nested issue 107 tests --- frontend/src/utils/date.ts | 51 +++--- frontend/testing/unit/utils/date.test.ts | 202 +++++++++++------------ 2 files changed, 123 insertions(+), 130 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 932441f04..c1ae60f80 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -40,25 +40,25 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null */ function getPreferredTimeZone(): string | undefined { try { - const saved = localStorage.getItem('secuscan-config'); - if (saved) { - const config = JSON.parse(saved); - if (config.timezone && config.timezone !== 'auto') { - return config.timezone; - } - } + const saved = localStorage.getItem('secuscan-config'); + if (saved) { + const config = JSON.parse(saved) + if (config.timezone && config.timezone !== 'auto') { + return config.timezone + } + } } catch (e) { // Fallback to system default } - return undefined; -} + return undefined + } /** * Returns the current timezone being used (either preferred or system default). */ export function getCurrentTimeZone(): string { - const preferred = getPreferredTimeZone(); - if (preferred) return preferred; + const preferred = getPreferredTimeZone() + if (preferred) return preferred try { return Intl.DateTimeFormat().resolvedOptions().timeZone } catch (e) { @@ -75,12 +75,12 @@ export function getTimeZoneAbbreviation(): string { const formatter = new Intl.DateTimeFormat([], { timeZoneName: 'short', ...(tz ? { timeZone: tz } : {}) - }); - const parts = formatter.formatToParts(new Date()); - const tzPart = parts.find(part => part.type === 'timeZoneName'); - return tzPart ? tzPart.value : ''; + }) + const parts = formatter.formatToParts(new Date()) + const tzPart = parts.find(part => part.type === 'timeZoneName') + return tzPart ? tzPart.value : '' } catch (e) { - return ''; + return '' } } @@ -91,8 +91,8 @@ export function formatBriefingDate(dateStr: string | null): string { const d = parseDateSafe(dateStr) if (!d) return '' - const tz = getPreferredTimeZone(); - const options: Intl.DateTimeFormatOptions = tz ? { timeZone: tz } : {}; + const tz = getPreferredTimeZone() + const options: Intl.DateTimeFormatOptions = tz ? { timeZone: tz } : {} const day = d.toLocaleDateString([], { ...options, day: '2-digit' }) const month = d.toLocaleDateString([], { ...options, month: 'short' }).toUpperCase() @@ -149,8 +149,8 @@ export function formatDateLong(dateStr: string | null): string { ...(tz ? { timeZone: tz } : {}) }).toUpperCase() - const tzAbbr = getTimeZoneAbbreviation(); - return tzAbbr ? `${formatted} ${tzAbbr}` : formatted; + const tzAbbr = getTimeZoneAbbreviation() + return tzAbbr ? `${formatted} ${tzAbbr}` : formatted } /** @@ -158,12 +158,12 @@ export function formatDateLong(dateStr: string | null): string { */ export function formatLocaleDate(dateStr: string | Date | null | undefined, options: Intl.DateTimeFormatOptions = {}): string { const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr; - if (!d) return 'N/A'; - const tz = getPreferredTimeZone(); + if (!d) return 'N/A' + const tz = getPreferredTimeZone() return d.toLocaleDateString([], { ...(tz ? { timeZone: tz } : {}), ...options - }); + }) } /** @@ -172,13 +172,14 @@ export function formatLocaleDate(dateStr: string | Date | null | undefined, opti export function formatLocaleTime(dateStr: string | Date | null | undefined, options: Intl.DateTimeFormatOptions = {}): string { const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr; if (!d) return 'N/A'; - const tz = getPreferredTimeZone(); + const tz = getPreferredTimeZone() return d.toLocaleTimeString([], { ...(tz ? { timeZone: tz } : {}), hour12: false, ...options - }); + }) } + export type DateRange = 'all' | '24h' | '7d' | '30d' export function isWithinDateRange(dateStr: string, range: DateRange): boolean { diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 78075ae7e..6644f5dea 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, beforeEach } from "vitest"; +import { describe, test, expect, beforeEach } from "vitest" import { parseDateSafe, @@ -7,117 +7,109 @@ import { formatLocaleTime, getCurrentTimeZone, formatTaskInit, -} from "../../../src/utils/date"; - - describe("date utilities", () => { - beforeEach(() => { - localStorage.clear(); - }); - - describe("parseDateSafe", () => { - test("parses ISO timestamp", () => { - const result = parseDateSafe("2026-05-12T10:30:00Z"); - expect(result).not.toBeNull(); - expect(result instanceof Date).toBe(true); - }); - - test("parses SQLite timestamp", () => { - const result = parseDateSafe("2026-05-12 10:30:00"); - expect(result).not.toBeNull(); - }); - - test("returns null for invalid input", () => { - expect(parseDateSafe("invalid-date")).toBeNull(); - }); - - test("returns null for empty string", () => { - expect(parseDateSafe("")).toBeNull(); - }); - - test("returns null for null input", () => { - expect(parseDateSafe(null)).toBeNull(); - }); - }); - - describe("formatDateLong", () => { - test("formats valid date", () => { - const result = formatDateLong("2026-05-12T10:30:00Z"); - expect(result).not.toBe("N/A"); - expect(result).toContain("2026"); - }); - - test("returns N/A for invalid input", () => { - expect(formatDateLong("bad-date")).toBe("N/A"); - }); - }); - - describe("formatLocaleDate", () => { - test("formats valid date", () => { - const result = formatLocaleDate("2026-05-12T10:30:00Z"); - expect(result).not.toBe("N/A"); - }); - - test("returns N/A for invalid input", () => { - expect(formatLocaleDate("bad-date")).toBe("N/A"); - }); - - test("returns N/A for null input", () => { - expect(formatLocaleDate(null)).toBe("N/A"); - }); - - test("returns N/A for undefined input", () => { - expect(formatLocaleDate(undefined)).toBe("N/A"); - }); - }); - - describe("formatLocaleTime", () => { - test("formats valid time", () => { - const result = formatLocaleTime("2026-05-12T10:30:00Z"); - expect(result).not.toBe("N/A"); - }); - - test("returns N/A for invalid input", () => { - expect(formatLocaleTime("bad-date")).toBe("N/A"); - }); - - test("returns N/A for null input", () => { - expect(formatLocaleTime(null)).toBe("N/A"); - }); - - test("returns N/A for undefined input", () => { - expect(formatLocaleTime(undefined)).toBe("N/A"); - }); - }); +} from "../../../src/utils/date" + +describe("date utilities", () => { + beforeEach(() => { + localStorage.clear() + }) + + describe("parseDateSafe", () => { + test("parses ISO timestamp", () => { + const result = parseDateSafe("2026-05-12T10:30:00Z") + expect(result).not.toBeNull() + expect(result instanceof Date).toBe(true) + }) + + test("parses SQLite timestamp", () => { + const result = parseDateSafe("2026-05-12 10:30:00") + expect(result).not.toBeNull() + }) + + test("returns null for invalid input", () => { + expect(parseDateSafe("invalid-date")).toBeNull() + }) + + test("returns null for empty string", () => { + expect(parseDateSafe("")).toBeNull() + }) + + test("returns null for null input", () => { + expect(parseDateSafe(null)).toBeNull() + }) + }) + + describe("formatDateLong", () => { + test("formats valid date", () => { + const result = formatDateLong("2026-05-12T10:30:00Z") + expect(result).not.toBe("N/A") + expect(result).toContain("2026") + }) + + test("returns N/A for invalid input", () => { + expect(formatDateLong("bad-date")).toBe("N/A") + }) + }) + + describe("formatLocaleDate", () => { + test("formats valid date", () => { + const result = formatLocaleDate("2026-05-12T10:30:00Z") + expect(result).not.toBe("N/A") + }) + + test("returns N/A for invalid input", () => { + expect(formatLocaleDate("bad-date")).toBe("N/A") + }) + + test("returns N/A for null input", () => { + expect(formatLocaleDate(null)).toBe("N/A") + }) + + test("returns N/A for undefined input", () => { + expect(formatLocaleDate(undefined)).toBe("N/A") + }) + + describe("Issue #107 : Invalid Date Handling", () => { + test("returns N/A for unrealistic or non-standard dates", () => { + expect(formatLocaleDate("not-a-date")).toBe("N/A") + expect(formatLocaleDate("2026-13-45")).toBe("N/A") + expect(formatLocaleDate("99999")).toBe("N/A") + }) + }) + }) + + describe("formatLocaleTime", () => { + test("formats valid time", () => { + const result = formatLocaleTime("2026-05-12T10:30:00Z") + expect(result).not.toBe("N/A") + }) + + test("returns N/A for invalid input", () => { + expect(formatLocaleTime("bad-date")).toBe("N/A") + }) + }) describe("timezone preference safety", () => { test("does not crash without localStorage config", () => { - expect(() => formatLocaleDate("2026-05-12T10:30:00Z")).not.toThrow(); - }); + expect(() => formatLocaleDate("2026-05-12T10:30:00Z")).not.toThrow() + }) test("uses fallback timezone safely", () => { - expect(getCurrentTimeZone()).toBeTruthy(); - }); - }); + expect(getCurrentTimeZone()).toBeTruthy() + }) + }) describe("formatTaskInit", () => { test("returns UNKNOWN values for invalid date", () => { - const result = formatTaskInit("bad-date"); - expect(result.date).toBe("UNKNOWN DATE"); - expect(result.time).toBe("UNKNOWN TIME"); - }); + const result = formatTaskInit("bad-date") + expect(result.date).toBe("UNKNOWN DATE") + expect(result.time).toBe("UNKNOWN TIME") + }) test("formats valid task date", () => { - const result = formatTaskInit("2026-05-12T10:30:00Z"); - expect(result.date).not.toBe("UNKNOWN DATE"); - expect(result.time).not.toBe("UNKNOWN TIME"); - }); - }); - }); - - describe("Issue #107: Invalid Date Handling", () => { - test("returns N/A for unrealistic or non-standard dates", () => { - expect(formatLocaleDate("not-a-date")).toBe("N/A"); - expect(formatLocaleDate("2026-13-45")).toBe("N/A"); - expect(formatLocaleDate("99999")).toBe("N/A"); - }); - }); \ No newline at end of file + const result = formatTaskInit("2026-05-12T10:30:00Z") + expect(result.date).not.toBe("UNKNOWN DATE") + expect(result.time).not.toBe("UNKNOWN TIME") + }) + }) + }) \ No newline at end of file From 3e9db9c1f50c8ec5ebe54832e223e38150a8b320 Mon Sep 17 00:00:00 2001 From: Saumya Tripathi Date: Thu, 4 Jun 2026 22:21:05 +0530 Subject: [PATCH 6/6] chore: clean up code hygiene and formatting per review --- frontend/src/utils/date.ts | 38 +++++++++++----------- frontend/testing/unit/utils/date.test.ts | 40 ++++++++++++------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index c1ae60f80..f421368a9 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -8,10 +8,10 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null if (raw.toLowerCase() === 'now') return new Date() try { - // Handle formats like "YYYY-MM-DD HH:MM:SS" (SQLite) vs standard ISO-8601 + // Handle formats like "YYYY-MM-DD HH:MM:SS" (SQLite) vs standard ISO-8601 const isoCompatible = raw.includes('T') ? raw : raw.replace(' ', 'T') - // Check if the string already has timezone info (e.g. "Z" or "+HH:MM") + // Check if the string already has timezone info (e.g. "Z" or "+HH:MM") const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/.test(isoCompatible) // We try multiple candidate strings if timezone is missing @@ -39,19 +39,19 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null * Gets the preferred timezone from local storage or returns undefined to use system default. */ function getPreferredTimeZone(): string | undefined { - try { - const saved = localStorage.getItem('secuscan-config'); - if (saved) { - const config = JSON.parse(saved) - if (config.timezone && config.timezone !== 'auto') { - return config.timezone - } + try { + const saved = localStorage.getItem('secuscan-config') + if (saved) { + const config = JSON.parse(saved) + if (config.timezone && config.timezone !== 'auto') { + return config.timezone } - } catch (e) { - // Fallback to system default } - return undefined + } catch (e) { + // Fallback to system default } + return undefined +} /** * Returns the current timezone being used (either preferred or system default). @@ -71,7 +71,7 @@ export function getCurrentTimeZone(): string { */ export function getTimeZoneAbbreviation(): string { try { - const tz = getPreferredTimeZone(); + const tz = getPreferredTimeZone() const formatter = new Intl.DateTimeFormat([], { timeZoneName: 'short', ...(tz ? { timeZone: tz } : {}) @@ -109,7 +109,7 @@ export function formatTaskInit(dateStr: string): { date: string, time: string, t const parsed = parseDateSafe(dateStr) if (!parsed) return { date: 'UNKNOWN DATE', time: 'UNKNOWN TIME', tz: '' } - const tz = getPreferredTimeZone(); + const tz = getPreferredTimeZone() const date = parsed.toLocaleDateString([], { month: 'short', day: 'numeric', @@ -125,7 +125,7 @@ export function formatTaskInit(dateStr: string): { date: string, time: string, t ...(tz ? { timeZone: tz } : {}) }) - const tzAbbr = getTimeZoneAbbreviation(); + const tzAbbr = getTimeZoneAbbreviation() return { date, time, tz: tzAbbr } } @@ -137,7 +137,7 @@ export function formatDateLong(dateStr: string | null): string { const d = parseDateSafe(dateStr) if (!d) return 'N/A' - const tz = getPreferredTimeZone(); + const tz = getPreferredTimeZone() const formatted = d.toLocaleString([], { day: '2-digit', month: 'short', @@ -157,7 +157,7 @@ export function formatDateLong(dateStr: string | null): string { * Shorthand for general toLocaleDateString without hardcoding. */ export function formatLocaleDate(dateStr: string | Date | null | undefined, options: Intl.DateTimeFormatOptions = {}): string { - const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr; + const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr if (!d) return 'N/A' const tz = getPreferredTimeZone() return d.toLocaleDateString([], { @@ -170,8 +170,8 @@ export function formatLocaleDate(dateStr: string | Date | null | undefined, opti * Shorthand for general toLocaleTimeString without hardcoding. */ export function formatLocaleTime(dateStr: string | Date | null | undefined, options: Intl.DateTimeFormatOptions = {}): string { - const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr; - if (!d) return 'N/A'; + const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr + if (!d) return 'N/A' const tz = getPreferredTimeZone() return d.toLocaleTimeString([], { ...(tz ? { timeZone: tz } : {}), diff --git a/frontend/testing/unit/utils/date.test.ts b/frontend/testing/unit/utils/date.test.ts index 6644f5dea..75119f261 100644 --- a/frontend/testing/unit/utils/date.test.ts +++ b/frontend/testing/unit/utils/date.test.ts @@ -38,7 +38,7 @@ describe("date utilities", () => { expect(parseDateSafe(null)).toBeNull() }) }) - + describe("formatDateLong", () => { test("formats valid date", () => { const result = formatDateLong("2026-05-12T10:30:00Z") @@ -89,27 +89,27 @@ describe("date utilities", () => { }) }) - describe("timezone preference safety", () => { - test("does not crash without localStorage config", () => { - expect(() => formatLocaleDate("2026-05-12T10:30:00Z")).not.toThrow() - }) + describe("timezone preference safety", () => { + test("does not crash without localStorage config", () => { + expect(() => formatLocaleDate("2026-05-12T10:30:00Z")).not.toThrow() + }) - test("uses fallback timezone safely", () => { - expect(getCurrentTimeZone()).toBeTruthy() - }) + test("uses fallback timezone safely", () => { + expect(getCurrentTimeZone()).toBeTruthy() }) + }) - describe("formatTaskInit", () => { - test("returns UNKNOWN values for invalid date", () => { - const result = formatTaskInit("bad-date") - expect(result.date).toBe("UNKNOWN DATE") - expect(result.time).toBe("UNKNOWN TIME") - }) + describe("formatTaskInit", () => { + test("returns UNKNOWN values for invalid date", () => { + const result = formatTaskInit("bad-date") + expect(result.date).toBe("UNKNOWN DATE") + expect(result.time).toBe("UNKNOWN TIME") + }) - test("formats valid task date", () => { - const result = formatTaskInit("2026-05-12T10:30:00Z") - expect(result.date).not.toBe("UNKNOWN DATE") - expect(result.time).not.toBe("UNKNOWN TIME") - }) + test("formats valid task date", () => { + const result = formatTaskInit("2026-05-12T10:30:00Z") + expect(result.date).not.toBe("UNKNOWN DATE") + expect(result.time).not.toBe("UNKNOWN TIME") }) - }) \ No newline at end of file + }) +})