Get the Current Unix Timestamp in JavaScript

To get the current Unix timestamp in seconds, use Math.floor(Date.now() / 1000); Date.now() alone gives milliseconds. Part of Unix Timestamps & Epoch Conversion.

Why this scenario is tricky

It helps to picture the two values side by side for the same instant. Right now Date.now() is a thirteen-digit number of milliseconds, while Math.floor(Date.now() / 1000) is a ten-digit number of seconds; the seconds value crossed ten digits back in 2001 and will stay ten digits until late 2286. That digit-count difference is the quickest sniff test when you are staring at a suspicious timestamp in a log, but it is a diagnostic aid, not a substitute for knowing the unit your code produces.

The stakes are highest at authentication and caching boundaries, where a timestamp is compared against another party's clock. A cache TTL, a rate-limit reset, and a token expiry are all seconds-based by convention, and each becomes a security or availability bug when handed milliseconds.

The whole difficulty is a units mismatch hiding in plain sight. JavaScript counts time in milliseconds — Date.now() returns milliseconds since the epoch — but the wider world of Unix timestamps, database EXTRACT(EPOCH …), JWT exp/iat claims, rate-limit windows, and cache TTLs counts in whole seconds. The two look almost identical (a 13-digit number versus a 10-digit one) and both are "the current timestamp," so it is easy to hand one system the other's unit. The consequence is not a subtle rounding error but a thousand-fold one: a token whose expiry is set from Date.now() in a field that expects seconds lands roughly fifty thousand years in the future and never expires.

JavaScript counts the epoch in milliseconds, but Unix time, database epoch functions, and JWT exp/iat claims count whole seconds. Emit the milliseconds where a millisecond value is expected and the seconds where seconds are — mixing them puts a token's expiry a thousand-fold too far in the future.

Milliseconds vs whole secondsDate.now() is ms; JWT exp is secondsMilliseconds vs whole secondsDate.now() as 'exp'expiry ~year 56 000Math.floor(Date.now()/1000)correct secondsDivide by 1000 and floor whenever a seconds-based system is on the other end.

Minimal working solution

If you find yourself unsure at a call site whether a timestamp is already seconds or milliseconds, that uncertainty is itself the bug to fix: name the value or the function for its unit so the question never arises again. A nowSeconds() that is only ever used where seconds are wanted is worth far more than a comment explaining a bare Date.now().

The floor is not a stylistic detail, so it is worth stating plainly why rounding is wrong. Many systems that consume a Unix timestamp compare it against their own clock and reject values that appear to be in the future, to defend against replay and skew. If you round Date.now() / 1000 up, an "issued at" value generated at, say, 12:00:00.6 becomes 12:00:01 — one second ahead of the instant it was actually created — and a strict receiver a few hundred milliseconds behind can reject it as future-dated. Flooring guarantees the whole-second value never exceeds the real instant, which is the conservative, interoperable choice for anything another party validates.

For seconds, take Math.floor(Date.now() / 1000). The division converts milliseconds to seconds, and the floor truncates the sub-second remainder rather than rounding it up, which matters because rounding could push the timestamp into the next second and make an "issued at" value momentarily appear to be in the future. For milliseconds, Date.now() already is the value you want, so no conversion is needed. Naming the two explicitly — one function that returns milliseconds, one that returns seconds — is the cheapest way to stop a caller from grabbing the wrong unit.

const ms = Date.now();                 // 1710513000123 (milliseconds)
const seconds = Math.floor(ms / 1000); // 1710513000 (whole seconds)

Now to secondsRead ms, floor to whole secondsNow to secondsDate.now()ms/ 1000floorseconds

Full production version

A concrete example ties the guidance together. Issuing a JWT, you set iat to Math.floor(Date.now() / 1000) and exp to that value plus a lifetime in seconds — iat + 3600 for an hour. Both fields are seconds because the standard says so, and flooring keeps iat from ever reading as future-dated to a validator whose clock trails yours slightly. Getting either the unit or the rounding wrong here is the difference between a token that behaves and one that never expires or expires on issue.

The same pattern recurs for cache control and rate limiting: a Retry-After or a reset timestamp is seconds, computed once from the current epoch and labelled for its unit, never a raw Date.now() passed through by habit.

There is a broader design point here about representing time in a system that spans protocols. The application's internal source of truth should be a single, unambiguous representation — epoch milliseconds, or a Temporal.Instant — and every protocol-specific form should be derived from it at the edge, never stored as the canonical value. When seconds and milliseconds both float around the core of a system as bare numbers, every function becomes a place to guess the unit. Pushing the conversion to the boundary, and naming the derived values for their unit and protocol, keeps the ambiguity contained to a thin translation layer that is easy to review and hard to get wrong twice.

In a system that mixes units it helps to make the unit part of the name and, where possible, part of the type. Expose nowMs() and nowSeconds() rather than a single ambiguous now(), and when you construct values destined for a specific protocol — a JWT, a rate limiter, an HTTP Date-like header — convert at that boundary and label the variable for its unit (expSeconds, issuedAtMs). If you have adopted Temporal, Temporal.Now.instant() gives you an instant whose epochMilliseconds and epochNanoseconds are explicitly named, removing the guesswork entirely; derive seconds from it with a single floor-divide when a seconds-based system is on the other end.

// Name the unit so callers cannot mix them up.
export const nowMs = () => Date.now();
export const nowSeconds = () => Math.floor(Date.now() / 1000);
export const nowInstant = () => nowMs(); // pass ms to new Date()

Unit-named helpersExpose ms and seconds explicitlyUnit-named helpersDate.now()nowMs()nowSeconds()named unit

Verification snippet

Centralising the current-timestamp helpers behind named, unit-explicit functions makes these tests short and keeps every caller on the same footing, which is the cheapest insurance against a unit slipping back in during a later change.

Because the current time moves, prefer assertions on relationships and magnitudes over exact values. Assert nowMs() divided by a thousand and floored equals nowSeconds() within the same tick, that both are positive and in the expected digit range for the current era, and that a computed expiry is a sensible number of seconds ahead of now.

If you wrap the clock behind an injectable seam for testability, these become fully deterministic: freeze the clock, and every derived timestamp — milliseconds, seconds, expiry — is a known value you can assert exactly.

It also pays to test the interaction with clock adjustments, at least in reasoning if not in an automated suite. Because Date.now() follows the wall clock, an NTP correction or a manual time change can make two successive readings go backwards, so any code that assumes monotonic increase from Date.now() is subtly wrong. If a piece of logic needs "a timestamp that only ever increases," that is a signal to use a monotonic source for the ordering and reserve the wall-clock timestamp for display and interchange. Separating those two concerns — ordering versus wall-clock labelling — prevents a whole category of rare, hard-to-reproduce bugs.

The assertions worth making are about magnitude and unit, not exact values, since the clock moves. Assert that nowSeconds() has ten digits and nowMs() has thirteen in the current era, that nowMs() is roughly nowSeconds() * 1000, and that a value destined for a JWT exp is in seconds by checking it is far smaller than a milliseconds value for the same instant. A regression test that a token's exp is a plausible number of seconds in the future — minutes, not millennia — catches the classic unit swap before it ships.

Get the Current Unix Timestamp in JavaScript — assertionsKey cases assert correctlyAssertions that prove the edge casenowSeconds() digits10nowMs() digits13ratio~1000xJWT exp usesseconds

Common pitfalls

The one-line takeaway is that Date.now() is milliseconds, Unix time is seconds, and the bridge between them is a single floored division performed at the boundary and labelled for its unit. Treat wall-clock timestamps as interchange and display values rather than a monotonic ordering source, reach for higher-resolution clocks when you need sub-second precision, and the current-timestamp operation stops being a source of subtle expiry and skew bugs.

Another quiet mistake is deriving "now in seconds" by formatting a date and parsing pieces of it, which is slower and can drift with locale settings. The epoch is a pure number; get it from Date.now() and divide, never from a formatted string.

And when a feature genuinely needs sub-second precision — high-resolution tracing, ordering events within the same second — reach for Temporal.Now.instant().epochNanoseconds or performance.now() rather than trying to squeeze fractional seconds out of a rounded Unix timestamp that, by definition, has thrown that precision away.

The signature pitfall is sending Date.now() (milliseconds) to something that expects Unix seconds, which makes a token effectively never expire, or the reverse, which expires it instantly. The second is rounding instead of flooring when converting to seconds, which can nudge an "issued at" timestamp into the future and trip clock-skew checks. The third is doing timing measurements with wall-clock timestamps at all — for elapsed time use performance.now(), which is monotonic and unaffected by clock adjustments. Convert units at the boundary, floor when going to seconds, and name variables for their unit.

Current Unix Timestamp pitfallsCommon mistakes and their fixesWrongRightsend Date.now() as exp1000x too largefloor(Date.now()/1000)secondsparse seconds with new Date()lands in 1970name variables by unitno mixups

Frequently Asked Questions

How do I get the current Unix timestamp in seconds?

Use Math.floor(Date.now() / 1000). Date.now() returns milliseconds since the epoch, and Unix time is measured in whole seconds, so you divide by 1000 and floor to drop the millisecond remainder.

Why does my JWT expire immediately or never?

You likely passed Date.now() (milliseconds) where the exp claim expects seconds, or vice versa. JWT exp and iat are Unix seconds, so use Math.floor(Date.now() / 1000) when signing and compare against seconds when validating.

How do I get the current Unix timestamp in seconds?

Use Math.floor(Date.now() / 1000). Date.now() returns milliseconds since the epoch, and Unix time is whole seconds, so you divide by 1000 and floor to drop the sub-second remainder. Flooring rather than rounding avoids nudging an issued-at value into the future.

Why does my JWT expire immediately or never?

You almost certainly mixed units. JWT exp and iat are Unix seconds, so passing Date.now() (milliseconds) makes exp about a thousand times too large and the token never expires, while passing seconds where milliseconds are expected expires it at once. Use Math.floor(Date.now() / 1000) when signing and compare against seconds when validating.

Should I use Date.now() to measure how long something takes?

No. Date.now() reads the wall clock, which can jump when the system time is adjusted, so a duration measured from two readings can be wrong or negative. Use performance.now(), which is monotonic, for elapsed-time measurements, and reserve Date.now() for actual timestamps.