Guides · 8 min read
Unix timestamps in JavaScript: gotchas and patterns
JavaScript's native Date object is famously quirky. Here is a practical guide to working with Unix timestamps in JS — what to use, what to avoid, and the libraries worth reaching for.
Published 22 April 2026
JavaScript’s Date is a 30-year-old API that was designed in 10 days and has had to remain backwards-compatible ever since. It works, but it has rough edges that catch newcomers and veterans alike — month numbers that start at zero, locale-sensitive parsing, and timezones that work almost but not quite the way you’d expect.
This guide is the practical playbook: how to get a Unix timestamp out of JavaScript, how to put one back in, and the patterns that keep you out of trouble.
Getting the current Unix timestamp
JavaScript’s clock APIs return milliseconds since the epoch. To get seconds, divide and floor:
const seconds = Math.floor(Date.now() / 1000);
Three things to notice:
Date.now()is faster and cleaner thannew Date().getTime(). Same result, no allocation.Math.floormatters. JavaScript divides as a float, and a value like1745301600.847would truncate inconsistently if you usedparseIntor bitwise tricks.- Don’t use
| 0or>>> 0. These coerce to a 32-bit integer. They look like a clever trick to get integer seconds, but they overflow on 19 January 2038 at 03:14:08 UTC. We have a whole guide on the Year 2038 problem — short version: just useMath.floor.
If you want milliseconds instead:
const millis = Date.now();
Milliseconds are JavaScript’s native unit. If you have a choice and you are storing the value in a JavaScript-fronted system, prefer milliseconds — you avoid the unit conversion at every boundary.
Converting a timestamp to a Date
The Date constructor accepts milliseconds:
const d = new Date(1745301600 * 1000); // from seconds
const d2 = new Date(1745301600000); // from milliseconds
If you have a 10-digit number, it is almost certainly seconds, and you have to multiply. If you have a 13-digit number, it is milliseconds, and you don’t. There’s a whole guide on telling the units apart.
Once you have a Date object, you can format it. The two reliable methods are:
d.toISOString(); // "2026-04-22T08:00:00.000Z" — always UTC, always ISO 8601
d.toLocaleString(); // "4/22/2026, 10:00:00 AM" — locale-dependent
toISOString() is your friend for storage, logging, and machine-to-machine communication. toLocaleString() is your friend for displaying to a user, but be aware that the format depends on the user’s locale and you usually want to pass options:
d.toLocaleString('en-GB', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'Europe/Oslo',
});
// "22 Apr 2026, 10:00"
Parsing strings into Unix time
This is where Date gets dangerous. new Date(string) accepts a wide range of inputs, but the behaviour depends on the format:
new Date('2026-04-22'); // Midnight UTC — interpreted as ISO date
new Date('2026-04-22T10:00'); // Local time! No timezone = local
new Date('2026-04-22T10:00Z'); // UTC — explicit timezone
new Date('22 April 2026'); // Locale-dependent — works in Chrome, may not in others
new Date('04/22/2026'); // Ambiguous — is this April 22 or 4 February?
Rule of thumb: the only string format you should pass to new Date() is full ISO 8601 with a timezone designator (Z or ±HH:MM). Anything else is asking for trouble.
For everything else, use a library. date-fns and Day.js both parse robustly:
import { parse } from 'date-fns';
const d = parse('22/04/2026', 'dd/MM/yyyy', new Date());
The “month is zero-indexed” trap
When you build a Date from parts, months go 0–11:
new Date(2026, 3, 22); // 22 April 2026 — note the 3, not 4
Days, hours, minutes, seconds are all 1-indexed (well, 0-indexed for time-of-day, but 1-indexed for day-of-month). Only months are 0-indexed. Nobody knows why. It is the most reliable bug-source in the API, and it is the reason we recommend ISO strings or timestamps for construction wherever possible.
Compare:
// Bad — easy to fat-finger.
new Date(2026, 4, 22); // 22 May 2026!
// Good — unambiguous.
new Date('2026-04-22T00:00:00Z');
new Date(1745279940000);
Timezones
Date always represents an instant in time — internally, milliseconds since the epoch. There is no notion of timezone attached to a Date. The timezone only enters when you ask for a calendar field:
const d = new Date('2026-04-22T08:00:00Z');
d.getHours(); // 10 — assumes user is in CEST
d.getUTCHours(); // 8 — UTC
d.toLocaleString('en-US', { timeZone: 'America/New_York' });
// "4/22/2026, 4:00:00 AM"
For one-off timezone conversions, toLocaleString with a timeZone option is fine. For anything serious — repeated formatting, doing math in a specific zone, calculating “start of day” in Tokyo — reach for Temporal (the new ECMAScript proposal, available natively in modern Node and behind a polyfill in browsers) or a library like date-fns-tz or Luxon.
Working with durations
Don’t subtract two Date objects when you actually want a duration. It works for the common case, but it is fragile around DST transitions and won’t handle anything calendar-aware. Use Date.now() differences for short durations:
const start = Date.now();
await doWork();
const elapsedMs = Date.now() - start;
For human-readable durations like “3 days, 4 hours ago”:
import { formatDistanceToNow } from 'date-fns';
formatDistanceToNow(d, { addSuffix: true });
// "3 days ago"
Or use the native Intl.RelativeTimeFormat:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-3, 'day'); // "3 days ago"
Library or no library
For small projects, plain Date plus Intl.DateTimeFormat and Intl.RelativeTimeFormat is enough. The native APIs have come a long way and you don’t need a 70KB library to format a date.
Reach for a library when:
- You are parsing many user-entered strings in custom formats (
date-fns,Day.js) - You need timezone math that goes beyond
toLocaleString(Luxon,date-fns-tz) - You want a stable, immutable API (
Day.js,Luxon, futureTemporal) - You are parsing or formatting durations (
date-fns,Day.jsplugins)
Moment.js is famously fine, but it is in maintenance mode and doesn’t tree-shake — pick date-fns or Day.js for new code.
A reference snippet to keep in your head
If you remember nothing else from this guide, remember this:
// Now, in seconds:
const now = Math.floor(Date.now() / 1000);
// Convert seconds to a Date:
const d = new Date(now * 1000);
// Convert a Date to a UTC ISO string:
const iso = d.toISOString();
// Display to a user in their locale:
const human = d.toLocaleString();
Everything else is decoration. If you need to test a value right now, paste it into the Unixdates converter and watch the live output update.
Need to convert a timestamp right now? Try the Unixdates converter — auto-detects seconds, milliseconds and microseconds.