From 48c5ef442e3e708c0a8347467ee93a91fe4042fa Mon Sep 17 00:00:00 2001 From: Kresna Date: Mon, 27 Jul 2026 15:17:04 +0700 Subject: [PATCH] fix(timestamp): detect and convert microsecond/nanosecond epochs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously parseTimestamp treated any numeric string longer than 10 digits as milliseconds, so a 19-digit nanosecond value (e.g. 1784694237438743460) overflowed Date's range and failed with 'Could not parse that as a date or Unix timestamp.' Now the unit is inferred from digit length — ~10 = seconds, ~13 = ms, ~16 = µs, ~19 = ns — and normalized to milliseconds. The UI surfaces the detected unit so the interpretation is explicit. Seconds/millis cutoffs are unchanged, so existing behavior is preserved. Adds coverage for micro/nanosecond parsing and detectNumericUnit. --- src/islands/dev/Timestamp.tsx | 18 ++++++++++++++-- src/tools/dev/timestamp.lib.test.ts | 28 ++++++++++++++++++++++++ src/tools/dev/timestamp.lib.ts | 33 ++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/islands/dev/Timestamp.tsx b/src/islands/dev/Timestamp.tsx index c2ea975..48a6bcd 100644 --- a/src/islands/dev/Timestamp.tsx +++ b/src/islands/dev/Timestamp.tsx @@ -6,6 +6,7 @@ import { Alert } from '@/components/ui/Alert'; import { describeDate, parseTimestamp, + detectNumericUnit, formatInTimeZone, listTimeZones, getLocalTimeZone, @@ -19,6 +20,7 @@ export default function Timestamp() { const [date, setDate] = useState(null); const [timeZone, setTimeZone] = useState(() => getLocalTimeZone()); const [error, setError] = useState(''); + const [detectedUnit, setDetectedUnit] = useState(''); const [pickerValue, setPickerValue] = useState(''); const [pickerZone, setPickerZone] = useState<'local' | 'utc'>('local'); @@ -27,6 +29,7 @@ export default function Timestamp() { setPickerValue(value); setPickerZone(zone); setError(''); + setDetectedUnit(''); if (!value) return; const parsed = parseDateTimeLocal(value, zone); if (parsed) setDate(parsed); @@ -35,6 +38,7 @@ export default function Timestamp() { const convert = () => { setError(''); setDate(null); + setDetectedUnit(''); const trimmed = input.trim(); if (!trimmed) return; const parsed = parseTimestamp(trimmed); @@ -43,10 +47,13 @@ export default function Timestamp() { return; } setDate(parsed); + // Surface how an all-digits value was interpreted (seconds/ms/µs/ns). + if (/^\d+$/.test(trimmed)) setDetectedUnit(detectNumericUnit(trimmed)); }; const now = () => { setError(''); + setDetectedUnit(''); setDate(new Date()); }; @@ -69,7 +76,7 @@ export default function Timestamp() { label="Unix timestamp or date string" value={input} onChange={e => setInput(e.target.value)} - placeholder="1720000000 · 2026-07-12 · Jul 12 2026 10:00" + placeholder="1720000000 (s · ms · µs · ns) · 2026-07-12 · Jul 12 2026 10:00" rows={2} /> @@ -113,13 +120,20 @@ export default function Timestamp() { - {error && {error}} + {detectedUnit && ( +

+ Detected numeric input as{' '} + Unix {detectedUnit}. +

+ )} + {date && ( <>