When you actually need this
Debug logs come back with created_at: 1700000000 and you need to know what time that was. A SaaS API returns ts: 1700000000000 — milliseconds, not seconds — and your parser falls over. You need to drop a record into a database with a strict ISO 8601 column and you have a Date object from somewhere mysterious. Spreadsheets, Postgres, MongoDB, S3 metadata, Stripe webhook payloads — they all phrase time differently, and the bridge between them is pure boilerplate every time. This tool is the boilerplate.
It exists because copy-pasting new Date(...) into a browser console is annoying when you do it twenty times a day, and because the mental conversion between seconds and milliseconds takes more cycles than it should.
Gotchas we keep hitting
The single biggest source of errors is seconds vs milliseconds. Java’s System.currentTimeMillis() and Postgres EXTRACT(EPOCH FROM x) * 1000 give milliseconds. The Unix date +%s command and most C-family code give seconds. Mix them and you’ll get either January 1970 (you fed seconds where ms was expected) or year 55000 (you fed ms where seconds was expected). Auto-detection here flips at 1e10 — anything bigger is treated as milliseconds.
ISO 8601 strings come in many shapes. The parser here is strict: it requires the T separator between date and time, and an explicit timezone (Z for UTC or +HH:MM / -HH:MM offset). Strings like 2023-11-14 22:13:20 (space, no offset) are rejected because they’re ambiguous. Date.parse in different browsers will silently disagree on inputs like this — being strict here saves you from finding out at 2am.
Daylight savings time is the silent killer. If you’re computing relative durations across DST boundaries with arithmetic on epoch seconds, you’ll get answers that are 60 minutes off. Always work in UTC for math, then convert to local for display.
Date.toISOString() always emits Z, never +00:00. They’re the same instant but some downstream parsers reject one or the other. The local-timezone formatter here emits ±HH:MM; switch to UTC if you need Z.
Big numbers (above 2^53) lose precision in JavaScript. Microsecond and nanosecond timestamps will silently truncate. If you’re working with high-precision time, convert to a string at the source and parse downstream.
With AI in the loop
When you’re asking an LLM to write date logic — “compute the next business day after a given timestamp” or “format this for the user’s timezone” — paste the output of this tool as ground truth. Anthropic and OpenAI models will confidently hallucinate timezone offsets and DST handling that look plausible but break in production. Pinning a fixed reference time in your prompt (“treat now as 2026-05-10T00:00:00Z”) helps the model produce reproducible output that you can verify against this tool.
If the model is generating SQL with date arithmetic, double-check it here before running. INTERVAL '1 day' and + 86400 are subtly different across DST. We’ve watched a model add 86400 seconds to a timestamp with time zone column and produce off-by-one-hour results twice a year.