Convert a Date to a Unix Timestamp in JavaScript
To convert a Date to a Unix timestamp in seconds, use Math.floor(date.getTime() / 1000); getTime() alone gives milliseconds. Part of Unix Timestamps & Epoch Conversion.
Why this scenario is tricky
There is a historical reason the two units coexist so awkwardly. Unix time was defined in seconds decades before JavaScript existed, so nearly every system-level tool, database function, and authentication standard that predates or ignores the browser counts seconds. JavaScript, arriving later and aiming for millisecond animation timing, chose milliseconds for Date. Neither choice is wrong, but the boundary between them runs straight through most web applications, which read a Date in the browser and hand a timestamp to a seconds-based backend.
Because the two representations differ only by a factor of a thousand and both are just "a number," no type system catches the mistake for you unless you introduce one. That is why the practical advice is less about the arithmetic and more about discipline: decide the unit at every boundary deliberately, and encode it where a human or a schema can see it.
Converting a Date to a Unix timestamp is mostly trivial — the value is already epoch milliseconds internally — but the trivia hides the same units trap that catches everyone. date.getTime() (and the unary +date shorthand) returns milliseconds, which is what most JavaScript APIs want, while Unix timestamps, JWT claims, and many backends want whole seconds. The conversion itself is one division, but doing it in the wrong direction, or forgetting it entirely, produces timestamps that are off by a factor of a thousand and land decades from where they should. The correctness question is therefore never "how do I convert" but "which unit does the consumer expect," and the code should make that unit explicit.
getTime() (or +date) already gives epoch milliseconds — the value most JavaScript APIs want. The only real work is emitting seconds for systems that expect them, and doing the division with Math.floor so a fractional second is truncated rather than rounded up into the next second.
Minimal working solution
Watch the direction of the conversion, too: dividing an already-seconds value by a thousand a second time lands you back near the Unix epoch in January 1970, which is the mirror image of the too-large mistake. The safe habit is to convert exactly once, at the point where the value leaves your system's canonical representation for a protocol that uses a different unit, and never to re-derive a unit from a value whose unit you are unsure of.
It is worth internalising why getTime() needs no conversion for the millisecond case. A Date is, underneath its many accessor methods, a single number: the count of milliseconds since the Unix epoch of 1970-01-01T00:00:00Z, measured in UTC. getTime() simply exposes that number. There is no timezone involved and no formatting — the value is identical on every machine on Earth, which is exactly the property that makes it a good wire and storage format. When people say a timestamp is "host-independent," this is the number they mean, and it is why you serialise and transmit the epoch value rather than a formatted local string that only makes sense in one place.
Math.floor(date.getTime() / 1000) gives the whole-second Unix timestamp; date.getTime() alone gives milliseconds. The floor matters: a Date can carry a sub-second component, and truncating rather than rounding keeps the timestamp from creeping into the next second, which is the safe behaviour for anything an external system will compare against its own clock. That is the entire operation — the only judgement is choosing seconds versus milliseconds based on what receives the value.
const date = new Date('2024-03-15T14:30:00Z');
const ms = date.getTime(); // 1710513000000
const seconds = Math.floor(ms / 1000); // 1710513000
Full production version
If your stack has adopted Temporal, prefer converting from a Temporal.Instant rather than a Date, because the instant's accessors name their units and remove the last bit of ambiguity: epochMilliseconds and epochNanoseconds say exactly what they return, and Math.floor(instant.epochMilliseconds / 1000) reads as plainly as it behaves. Where a Date is what you are handed — from a library, a DOM API, or legacy code — convert it to an instant at the boundary with date.toTemporalInstant() and let the rest of the system work in the clearer type.
None of this changes the arithmetic, but it changes how many places a future reader has to reason about units. The fewer bare numbers of ambiguous unit that circulate, the fewer opportunities there are for the thousand-fold mistake.
The reason to be strict about units at boundaries is that a wrong unit fails silently and late. A JWT signed with a milliseconds exp is accepted by the signing service, passes local tests where nobody waits fifty thousand years, and only reveals itself when a security review asks why tokens never expire. A database bigint column that mixes seconds and milliseconds across two code paths produces rows that sort correctly among themselves but nonsensically against each other, and the bug survives for months because each writer is internally consistent. Converting once at the boundary and encoding the unit in the field name — exp_seconds, created_at_ms — turns an invisible convention into a visible contract that a reviewer and a schema can both check.
A production converter is really a units contract. Expose the two forms explicitly — one that returns milliseconds, one that floor-divides to seconds — and name the variables at each call site for the unit they hold, so a reviewer can see a seconds value is not being handed to a milliseconds API. If the source is a Temporal.Instant rather than a Date, the units are already named: instant.epochMilliseconds and instant.epochNanoseconds say exactly what they are, and seconds come from a single floor-divide. When the value crosses a protocol boundary — into a JWT, a signed URL, a database bigint — convert once, at that boundary, and record the unit in the column or field name so the next reader is never guessing.
import { Temporal } from '@js-temporal/polyfill';
// From a Temporal.Instant, the units are explicit.
function toEpoch(i: Temporal.Instant) {
return { ms: i.epochMilliseconds, seconds: Math.floor(i.epochMilliseconds / 1000) };
}
Verification snippet
Keeping these assertions in a shared helper means every service that converts timestamps inherits the same guarantees, so the correctness is enforced in one place rather than rediscovered in each codebase that needs it.
One more assertion earns its place in a shared library: that the conversion is stable across host zones. Since getTime() is UTC-based and independent of the machine's zone, a test that converts the same Date under several TZ values and asserts an identical epoch value documents that property and guards against anyone accidentally reintroducing a zone-dependent path.
Together these checks — round-trip equality, epoch-zero mapping, floor-not-round, magnitude sanity, and host-zone stability — cover the full surface of a conversion that looks trivial but fails expensively when a unit slips.
Beyond the round-trip and epoch checks, a good regression test asserts the unit is plausible for its destination. If a value is headed for a JWT exp, assert it is within a sane window of the current time expressed in seconds — a token that expires in fifty thousand years is a failing test, not a runtime surprise. If a value is stored as milliseconds, assert it is roughly a thousand times the seconds form for the same instant. These magnitude checks are cheap and they catch the exact class of unit-swap bug that is otherwise invisible until production.
Round-trip is the clearest assertion: convert a known Date to seconds and back and confirm you land on the same second, and convert to milliseconds and back to confirm exact equality. Assert that the seconds form is the floor of the milliseconds form divided by a thousand, and that the epoch (new Date(0)) maps to 0 in both units. A fractional-second Date should convert to the earlier whole second, which pins down that you floored rather than rounded.
Common pitfalls
To summarise the discipline in one line: a Date is already epoch milliseconds, so getTime() needs no conversion for the millisecond case and a single floored division by a thousand for the seconds case, done exactly once, at the boundary where the value leaves your system, with the unit written into the name of whatever holds it. Follow that and the thousand-fold class of bug simply cannot occur, because there is never a bare timestamp of unknown unit for anyone to misread.
A related and easily missed pitfall is comparing timestamps of different units. Two epoch values that were each produced correctly — one in seconds, one in milliseconds — will compare and sort nonsensically against each other, and because each is internally valid, nothing looks wrong until an ordering or an expiry check misbehaves. Normalise to a single unit before any comparison.
Finally, resist the urge to parseInt a timestamp of unknown unit and guess by digit count in ordinary code paths; that heuristic belongs only in a defensive ingestion layer for genuinely untrusted input. Inside your own system, the unit should be known and named, not inferred.
The classic mistake is passing getTime() (milliseconds) to a system expecting Unix seconds, making a timestamp roughly a thousand times too large, or dividing an already-seconds value by a thousand again and landing in 1970. The second is rounding to seconds instead of flooring, which can overshoot into the next second and trip skew checks on the receiving side. The third is losing track of the unit across layers; name it in the variable and the column. Convert once at the boundary with a floor-divide, and keep the unit visible.
Frequently Asked Questions
How do I convert a Date to a Unix timestamp?
Call date.getTime() for milliseconds, or Math.floor(date.getTime() / 1000) for the whole-second Unix timestamp most backends and JWTs use. Flooring rather than rounding avoids overshooting into the next second.
Is getTime() in seconds or milliseconds?
Milliseconds. JavaScript works in epoch milliseconds throughout, so getTime(), Date.now(), and Temporal.Instant.epochMilliseconds are all millisecond values; divide by 1000 when a system expects seconds.
How do I convert a Date to a Unix timestamp?
Call date.getTime() for milliseconds, or Math.floor(date.getTime() / 1000) for the whole-second Unix timestamp most backends and JWTs expect. Flooring rather than rounding avoids overshooting into the next second. From a Temporal.Instant, use epochMilliseconds and floor-divide by 1000 for seconds.
Is getTime() in seconds or milliseconds?
Milliseconds. JavaScript works in epoch milliseconds throughout, so getTime(), Date.now(), and Temporal.Instant.epochMilliseconds are all millisecond values. Divide by 1000 and floor when a system expects Unix seconds.
Should I round or floor when converting milliseconds to seconds?
Floor. A Date can carry a sub-second remainder, and rounding could push the timestamp into the next second, making an issued-at value briefly appear to be in the future and tripping clock-skew checks. Math.floor truncates to the earlier whole second, which is the safe choice.