File size: 1,715 Bytes
45a105b adb91eb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // Date/time presented in Asia/Riyadh by default (backend-configured tz).
export function formatDateTime(epochSeconds: number, locale = 'en', tz = 'Asia/Riyadh'): string {
try {
return new Intl.DateTimeFormat(locale === 'ar' ? 'ar-SA' : 'en-GB', {
dateStyle: 'medium', timeStyle: 'short', timeZone: tz,
}).format(new Date(epochSeconds * 1000))
} catch {
return new Date(epochSeconds * 1000).toISOString()
}
}
// Any real transaction is after this floor; anything before it (epoch 0, a placeholder
// like `2`, or `1970-01-01T00:00:00Z`) is treated as "no real timestamp".
const MIN_VALID_MS = Date.UTC(2000, 0, 1)
/**
* Safe timestamp formatter. Accepts an epoch-seconds number OR an ISO string. Returns `null`
* for missing/empty/invalid/epoch-equivalent values so the caller can show a localized
* "Date unavailable" fallback — it NEVER fabricates the current date and never renders 1970.
*/
export function formatDateTimeSafe(
value: number | string | null | undefined,
locale = 'en',
tz = 'Asia/Riyadh',
): string | null {
if (value === null || value === undefined) return null
let ms: number
if (typeof value === 'number') {
if (!Number.isFinite(value)) return null
ms = value * 1000 // backend convention: epoch seconds
} else {
const s = value.trim()
if (s === '') return null
ms = new Date(s).getTime() // ISO string (already ms-based)
}
if (!Number.isFinite(ms) || ms < MIN_VALID_MS) return null
try {
return new Intl.DateTimeFormat(locale === 'ar' ? 'ar-SA' : 'en-GB', {
dateStyle: 'medium', timeStyle: 'short', timeZone: tz,
}).format(new Date(ms))
} catch {
return null
}
}
|