Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 54 additions & 48 deletions frontend/src/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,55 @@ export function parseDateSafe(rawValue: string | null | undefined): Date | null
try {
// 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")
const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/.test(isoCompatible)

// We try multiple candidate strings if timezone is missing
const candidates = hasTimezone
? [isoCompatible, raw]
: [`${isoCompatible}Z`, isoCompatible, raw]

for (const candidate of candidates) {
const d = new Date(candidate)
if (!Number.isNaN(d.getTime())) return d
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)
}

return 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;
}
}
} catch (e) {
// Fallback to system default
try {
const saved = localStorage.getItem('secuscan-config')
if (saved) {
const config = JSON.parse(saved)
if (config.timezone && config.timezone !== 'auto') {
return config.timezone
}
}
return undefined;
} catch (e) {
// Fallback to system default
}
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) {
Expand All @@ -66,16 +71,16 @@ export function getCurrentTimeZone(): string {
*/
export function getTimeZoneAbbreviation(): string {
try {
const tz = getPreferredTimeZone();
const formatter = new Intl.DateTimeFormat([], {
const tz = getPreferredTimeZone()
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 ''
}
}

Expand All @@ -85,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 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}`
}

Expand All @@ -103,25 +108,25 @@ 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 tz = getPreferredTimeZone()
const date = parsed.toLocaleDateString([], {
month: 'short',
day: 'numeric',
year: 'numeric',
...(tz ? { timeZone: tz } : {})
}).toUpperCase()

const time = parsed.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
...(tz ? { timeZone: tz } : {})
})
const tzAbbr = getTimeZoneAbbreviation();

const tzAbbr = getTimeZoneAbbreviation()

return { date, time, tz: tzAbbr }
}

Expand All @@ -131,8 +136,8 @@ 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 tz = getPreferredTimeZone()
const formatted = d.toLocaleString([], {
day: '2-digit',
month: 'short',
Expand All @@ -143,37 +148,38 @@ export function formatDateLong(dateStr: string | null): string {
hour12: false,
...(tz ? { timeZone: tz } : {})
}).toUpperCase()
const tzAbbr = getTimeZoneAbbreviation();
return tzAbbr ? `${formatted} ${tzAbbr}` : formatted;

const tzAbbr = getTimeZoneAbbreviation()
return tzAbbr ? `${formatted} ${tzAbbr}` : formatted
}

/**
* 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;
if (!d) return 'N/A';
const tz = getPreferredTimeZone();
const d = typeof dateStr === 'string' || dateStr === null || dateStr === undefined ? parseDateSafe(dateStr) : dateStr
if (!d) return 'N/A'
const tz = getPreferredTimeZone()
return d.toLocaleDateString([], {
...(tz ? { timeZone: tz } : {}),
...options
});
})
}

/**
* 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 tz = getPreferredTimeZone();
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 } : {}),
hour12: false,
...options
});
})
}

export type DateRange = 'all' | '24h' | '7d' | '30d'

export function isWithinDateRange(dateStr: string, range: DateRange): boolean {
Expand Down
Loading
Loading