Converting timestamps in JavaScript
JavaScript represents every instant in time with a single built-in type, the
Date object, and stores it internally as a count of milliseconds since
1970-01-01T00:00:00Z — a Unix timestamp, just in milliseconds instead of the seconds most
other languages and Unix tools use. Date.now() hands you that count
directly as a plain number, and new Date(ms) turns a millisecond count back
into a full Date object with year, month, day, hour, minute and second all
derived from it. In between, .getTime() pulls the millisecond count back out
of an existing Date, and toISOString() renders that instant as
a readable UTC string. These four calls cover the vast majority of timestamp work you
will ever do in JavaScript: an API returns createdAt as an epoch number, a
form needs to show it as a date, and a click handler needs to convert whatever the user
typed back into a number for the next request. The conversions themselves are one-liners,
but two details trip up even experienced JavaScript developers: the units mismatch
between JavaScript's milliseconds and the seconds convention used almost everywhere
else, and the fact that a Date has no timezone of its own — only the method
you call to read it decides whether you see UTC or the browser's local time. Get either
wrong and a timestamp that looks correct in the console will render as 1970, the far
future, or a few hours off once real data reaches it. This page works through the calls
you actually need, with runnable code for each direction and for the two gotchas that
most often turn "just convert this number" into a debugging session before it does.
Get the current Unix timestamp
Date.now() returns the current epoch value directly, in milliseconds. There
is no timezone to get wrong here — it is just a count of milliseconds since 1970 UTC.
const epochMs = Date.now() // 1786795200123 (number, milliseconds)
const epochSeconds = Math.floor(Date.now() / 1000) // 1786795200 (whole seconds)
Convert a Date to a Unix timestamp
Call .getTime() on a Date object, or use the unary plus
operator — both return milliseconds. Divide by 1000 if the value needs to match a Unix
API or a database column that stores seconds.
const date = new Date('2026-08-15T04:00:00Z')
const epochMs = date.getTime() // 1786795200000
const epochMsShort = +date // 1786795200000, same value
const epochSeconds = Math.floor(date.getTime() / 1000) // 1786795200
Convert a Unix timestamp to a Date
Pass milliseconds to the Date constructor. Because JavaScript always works
in milliseconds, a timestamp given in seconds — the more common convention outside
JavaScript — has to be multiplied by 1000 first.
const epochSeconds = 1786795200
// Seconds must be multiplied by 1000 first
const date = new Date(epochSeconds * 1000)
console.log(date.toISOString()) // 2026-08-15T04:00:00.000Z
// A millisecond value goes in directly
const epochMs = 1786795200000
const sameDate = new Date(epochMs)
The modern replacement is Temporal, a Stage 3 TC39 proposal that has begun
landing in browsers and has a maintained polyfill for everything else.
Temporal.Instant is the type Date should have been — an exact
instant with no timezone and no mutable setters — and it is the first API in the language
that takes epoch seconds without asking you to remember the factor of 1000:
// Seconds and milliseconds each get their own constructor — no × 1000
const instant = Temporal.Instant.fromEpochSeconds(1786795200)
const sameInstant = Temporal.Instant.fromEpochMilliseconds(1786795200000)
instant.toString() // '2026-08-15T04:00:00Z'
instant.epochSeconds // 1786795200 — back out again, still seconds
instant.toZonedDateTimeISO('Asia/Kolkata').toString()
// '2026-08-15T09:30:00+05:30[Asia/Kolkata]'
The separate fromEpochSeconds and fromEpochMilliseconds
constructors are the point: the seconds-versus-milliseconds bug this page keeps returning
to exists because new Date(n) has one constructor and has to guess. If you
are writing new code and can afford the polyfill,
Temporal.Instant.fromEpochSeconds() deletes a whole class of bug rather than
documenting it.
Handling timezones in JavaScript
A Date object always stores one instant internally as UTC milliseconds — it
has no timezone attached to it. Whether you see UTC or local time depends entirely on
which method reads it: toISOString() always renders UTC,
toLocaleString() and getters like getHours() render the
browser's local timezone, and passing a timeZone option renders any named
zone you choose. To see what one instant looks like across several named zones at once,
with the daylight-saving offset for that specific date already resolved, use
the UTC-to-local timezone converter.
const date = new Date(1786795200000)
date.toISOString() // '2026-08-15T04:00:00.000Z' — always UTC
date.toLocaleString() // browser's local timezone
date.getUTCHours() // reads the UTC hour
date.getHours() // reads the LOCAL hour — different machine, different answer
date.toLocaleString('en-US', { timeZone: 'America/New_York' })
// renders the same instant in a specific named zone, regardless of the browser's own timezone
Common gotchas
-
Seconds vs milliseconds.
new Date()andDate.now()both use milliseconds, but most APIs and Unix tools use seconds. Forgetting to multiply a seconds value by 1000 lands you in 1970; multiplying a value that was already milliseconds lands you thousands of years in the future. When a number arrives from an API you do not control, count its digits to settle the unit before it reachesnew Date(). When it has already rendered and the screen shows a date in January 1970, work the other way round: the 1970 debugger starts from the symptom and names which of the missing multiplications produced it. -
Local getters vs UTC getters.
getHours(),getDate()andgetDay()read the browser's local timezone; theirgetUTCHours(),getUTCDate()andgetUTCDay()counterparts read UTC. Mixing the two on the sameDateis a common source of off-by-one-day bugs near midnight. -
Months are zero-indexed.
new Date(2026, 7, 15)is August 15, not July — the month argument to theDateconstructor runs 0–11, unlike every other field. -
Date.parse()is locale- and engine-dependent for anything other than the ISO 8601 format. Stick to ISO strings (2026-08-15T04:00:00Z) or explicit epoch numbers when parsing dates from an API — free-form strings like"08/15/2026"parse inconsistently across browsers.
Frequently asked questions
How do I get the current Unix timestamp in JavaScript?
Call Date.now(). It returns the current Unix timestamp in milliseconds
since 1970-01-01T00:00:00Z, as a plain number. For seconds, divide by 1000 and floor the
result: Math.floor(Date.now() / 1000).
How do I convert a JavaScript Date to a Unix timestamp?
Call .getTime() on the Date object, or use the unary plus
operator: +date. Both return milliseconds since the epoch. Divide by 1000 if
you need seconds to match a Unix API or database column.
How do I convert a Unix timestamp to a JavaScript Date?
Pass milliseconds to the Date constructor: new Date(epochMs).
JavaScript's Date constructor always expects milliseconds, so a Unix
timestamp given in seconds must be multiplied by 1000 first:
new Date(epochSeconds * 1000).
Why is my JavaScript date off by 1000x or showing the wrong year?
Almost always a seconds/milliseconds mismatch. JavaScript's Date constructor
and Date.now() both use milliseconds, but most other languages and Unix
tools use seconds. Passing a raw seconds value into new Date() without
multiplying by 1000 produces a date in 1970; passing a milliseconds value where seconds
were expected produces a date far in the future.
How do I handle timezones in JavaScript?
A Date object always stores a single instant in UTC internally — it has no
timezone of its own. toISOString() always renders that instant in UTC.
toLocaleString() and getters like getHours() render or read it
in the browser's local timezone. For a specific named timezone, pass a
timeZone option to toLocaleString() or
Intl.DateTimeFormat, e.g. { timeZone: 'America/New_York' }.
Where to go next
toISOString() is only half the story if the string is coming the other way:
turning an ISO 8601 or RFC 3339 string back into epoch
seconds is where a missing Z quietly shifts the result by your
timezone's offset. And if the other end of the wire is a backend rather than a browser,
the same conversion in Python works in seconds, which is why
a value crossing between the two so often arrives 1000× off.