← All posts

JavaScript Date.now() — Get and Convert Unix Timestamps

Date.now() returns the current Unix timestamp in milliseconds. This guide covers every common JavaScript timestamp pattern — Date.now() vs performance.now() vs Temporal, seconds-vs-milliseconds conversion, timezone-aware formatting with Intl.DateTimeFormat, safe parsing, sub-second precision, and the migration path from legacy Date to Temporal now that Temporal has shipped in Chrome 144, Firefox 139, and Node.js 26.

How JavaScript stores time internally

A JavaScript Date is a UTC millisecond count. There is no timezone stored inside the object — the value is the number of milliseconds elapsed since 1970-01-01 00:00:00 UTC, positive for later instants and negative for earlier ones. The timezone is applied only when you format the value for display. This single fact explains every Date quirk: timezone-dependent output, the toLocaleString trap, the surprise that new Date(0) is 1970-01-01 UTC but renders as 1969-12-31 in any zone west of UTC.

  • Internal representation: signed 64-bit integer (interpreted as float64 in V8/SpiderMonkey) of milliseconds since epoch
  • Range: ±100,000,000 days from 1970-01-01 — roughly 273,790 years either side
  • Date.now() returns the current value; Date.getTime() and Date.valueOf() are equivalent
  • Timezone is applied at format time — toString(), toLocaleString(), Intl.DateTimeFormat
  • Equality: new Date(0) === new Date(0) is false (different objects); use .getTime() comparison

Getting the current Unix timestamp with Date.now()

Date.now() is the canonical way to get the current Unix timestamp in JavaScript. It returns milliseconds since the epoch as an integer — exactly what JavaScript Date and most browser APIs use natively. For Unix seconds (the format most server APIs and Unix tools expect), divide by 1000 and floor. Always do the divide-by-1000 at the system boundary, then never speak in the other unit inside that code path.

  • Date.now() // 1750409100000 — current Unix milliseconds
  • Math.floor(Date.now() / 1000) // 1750409100 — current Unix seconds
  • new Date().getTime() // same as Date.now() but creates a Date object first (slightly slower)
  • new Date().valueOf() // same; the unary + operator gives the shortcut: +new Date()
  • Performance: Date.now() is faster than new Date().getTime() in hot loops; the difference is negligible for occasional reads

Date.now() vs performance.now()

Date.now() reads the system wall-clock; performance.now() reads a monotonic clock that never moves backward. Use Date.now() when you need a wall-clock timestamp — 'when did this happen?'. Use performance.now() when you need an elapsed-time measurement — 'how long did it take?'. The distinction matters for benchmarks, animation timing, and rate limiting: NTP can step the wall clock backward at any moment, producing negative durations when you measure with Date.now().

  • Date.now() // wall-clock; can step backward via NTP; returns integer milliseconds
  • performance.now() // monotonic; never decreases; returns fractional ms (sub-ms precision)
  • performance.timeOrigin // ms since epoch when the page/process started
  • performance.timeOrigin + performance.now() // absolute Unix ms with sub-ms precision
  • Benchmark: const t = performance.now(); doWork(); console.log(performance.now() - t)
  • Display ('when did X happen'): const at = Date.now()
  • Anti-pattern: const elapsed = Date.now() - start // can go negative on NTP step

Converting a Unix timestamp to a JavaScript Date

If the value is milliseconds (13 digits), pass it directly to the Date constructor. If it's seconds (10 digits), multiply by 1000 first. The most common JS timestamp bug — the date showing 1970 — is exactly the missed × 1000. A safe wrapper auto-detects the unit by comparing the value against the millisecond threshold for the current year, which lets the same code accept either form from an upstream API that isn't strict about its contract.

  • new Date(1700000000000) // milliseconds — direct
  • new Date(1700000000 * 1000) // seconds — multiply first
  • Auto-detect: const toDate = ts => new Date(ts < 1e11 ? ts * 1000 : ts)
  • ISO 8601 string: new Date('2026-06-20T09:25:00Z') // Z suffix forces UTC parsing
  • Date components: new Date(Date.UTC(2026, 5, 20, 9, 25)) // months are 0-indexed (5 = June)
  • From an existing Date: const ms = existingDate.getTime() // unwraps to ms

Timezone-aware formatting with Intl.DateTimeFormat

Intl.DateTimeFormat is JavaScript's built-in, zero-dependency API for locale- and timezone-aware formatting. The single key option is timeZone — once you pass an IANA name, the runtime uses the tz database to pick the correct offset for the date you're formatting, automatically handling DST. The API has shipped in every modern browser since 2017 and in Node.js 13+ (full ICU data). Cache the formatter instance when you reuse it; constructing one is the slow part.

  • new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
  • date.toLocaleString('en-US', { timeZone: 'America/New_York' }) // newcomer's one-liner
  • Force UTC: new Intl.DateTimeFormat('en-US', { timeZone: 'UTC' }).format(date)
  • Performance: const fmt = new Intl.DateTimeFormat(...); rows.forEach(r => fmt.format(r.date))
  • Get the user's timezone: Intl.DateTimeFormat().resolvedOptions().timeZone // 'America/Los_Angeles'
  • Always pass timeZone explicitly in server code — never rely on process.env.TZ

Parsing date strings safely

Date.parse() and new Date(string) accept ISO 8601 reliably and almost nothing else portably. The ECMAScript specification explicitly allows implementation-defined parsing for non-ISO formats, so Date.parse('01/02/2026') returns Jan 2 in some runtimes and Feb 1 in others. Always pass strict ISO 8601 with a Z or offset suffix. For any other input format, use a library that takes the format string explicitly — date-fns parse, Luxon DateTime.fromFormat, or Temporal.PlainDateTime.from.

  • Portable: new Date('2026-06-20T09:25:00Z') // ISO 8601 with Z = always UTC
  • Portable: new Date('2026-06-20T09:25:00-05:00') // explicit offset
  • Implementation-defined: new Date('06/20/2026') // ambiguous dd/mm vs mm/dd
  • Implementation-defined: new Date('Jun 20, 2026') // locale-dependent; fails in some runtimes
  • Library route: date-fns parse('06/20/2026', 'MM/dd/yyyy', new Date())
  • Temporal: Temporal.PlainDate.from('2026-06-20') // strict, no ambiguity

Sub-millisecond precision — performance.timeOrigin and monotonic clocks

JavaScript's Date API only exposes millisecond precision; for anything finer, performance.now() is the canonical answer. It returns a fractional millisecond value — most browsers quantize to microsecond precision, some to coarser values for fingerprinting protection. performance.timeOrigin is the ms-since-epoch when the current execution context started; adding performance.now() to it gives an absolute Unix timestamp with sub-millisecond precision. For Node.js, perf_hooks exposes the same API plus performance.timeOrigin and process.hrtime.bigint() for nanosecond integers.

  • performance.now() // 1234.567 — fractional ms since context start
  • performance.timeOrigin // 1750409100000.0 — wall-clock ms at context start
  • performance.timeOrigin + performance.now() // sub-ms-precision Unix milliseconds
  • Node.js: process.hrtime.bigint() // nanoseconds as a BigInt
  • Quantization: browsers may round to 0.1ms or 1ms (Spectre mitigation); this varies
  • Cross-context: timeOrigin differs between workers, iframes, and the main thread

Temporal — the modern alternative to Date

The TC39 Temporal proposal reached Stage 4 in March 2026 — it's now part of the ECMAScript specification and ships natively in Chrome 144, Firefox 139, and Node.js 26 by default. Temporal replaces Date with five separate types that each represent exactly one concept: Instant (a moment in time), ZonedDateTime (instant + IANA zone), PlainDateTime (wall-clock without a zone), PlainDate, and PlainTime. The arithmetic is exact, the API is immutable, and DST gaps and overlaps are exposed instead of silently resolved. Use Temporal for any non-trivial date math; keep Date for simple Date.now() reads and broad browser support.

  • Temporal.Now.instant() // current Instant (equivalent to Date.now() but with nanosecond precision)
  • Temporal.Now.zonedDateTimeISO('America/New_York') // current ZonedDateTime
  • Temporal.Instant.fromEpochMilliseconds(Date.now()) // bridge from Date
  • Temporal.ZonedDateTime.from('2026-06-20T09:25-05:00[America/New_York]')
  • zdt.add({ days: 1 }) // calendar arithmetic that respects DST
  • zdt.epochSeconds // unwrap to Unix seconds
  • Polyfill (for older runtimes): @js-temporal/polyfill (~50 KB)

Common JavaScript timestamp mistakes

Most JavaScript timestamp bugs fall into a small set of patterns. Reading them once is cheaper than debugging them in production. The recurring theme is the same: the developer assumes a fixed unit, a fixed timezone, or that Date does something it doesn't. The companion bug catalog covers all 10 — every one appears in JavaScript code that handles a timestamp at some point.

  • new Date(1700000000) → 1970-01-20 — passed seconds where ms expected; multiply by 1000
  • Date.now() - start goes negative — wall-clock NTP step; use performance.now() instead
  • toLocaleString() output differs between dev and prod — relied on system tz; pass timeZone explicitly
  • new Date('01/02/2026') interprets differently in different runtimes — use strict ISO 8601
  • new Date(today.getTime() + 86_400_000) skips a day on DST — use Temporal or a library for local arithmetic
  • date1 === date2 is false even when equal — compare .getTime() values, not Date objects

Recommended JavaScript timestamp checklist

Adopt these defaults at the start of a project and most JavaScript timestamp bugs become impossible to ship. Each is cheap to set up and pays back the first time someone almost ships a regression. The checklist also works as a code-review prompt for any PR that touches a Date or timestamp field.

  • Server code: run with TZ=UTC; pass timeZone explicitly to every Intl call
  • Storage / API contracts: ISO 8601 strings; if numeric, name fields with the unit (_ms / _seconds)
  • Compare Date objects with .getTime() — never with === or ==
  • Use Date.now() for wall-clock, performance.now() for elapsed time
  • Parse: only strict ISO 8601 via Date constructor; anything else needs a library
  • Local-time arithmetic: use Temporal or Luxon; reserve raw ms math for UTC-only flows
  • Cross-browser: test the Temporal polyfill until Safari ships native support
  • Hot loops: cache Intl.DateTimeFormat instances; prefer Date.now() over new Date().getTime()

Cross-cluster JavaScript timestamp references

These companion articles cover specific JavaScript timestamp topics in more depth. Together with this guide they form the JavaScript portion of the unixepochtime.com Unix-timestamp content cluster.

  • Timezone-correct date formatting — Intl, DST handling, getting the user's zone
  • 10 common bugs — the production catalog with ripgrep recipes
  • Epoch milliseconds to date — the cross-language perspective on 13-digit timestamps
  • Unix time to date — the conversion-direction primer covering 8 runtimes
  • Milliseconds vs seconds — the unit decision when designing an API

FAQ

Does Date.now() return seconds or milliseconds?
Milliseconds. Date.now() returns the count of milliseconds since 1970-01-01 UTC — a 13-digit number in 2026. For Unix seconds (the format most server APIs and Unix tools expect), divide by 1000: Math.floor(Date.now() / 1000).
Why does new Date(1700000000) show 1970?
Because new Date(number) expects milliseconds, not seconds. 1700000000 is a 10-digit Unix-seconds value; the Date constructor interprets it as 1,700,000,000 milliseconds — about 20 days after the epoch. Multiply by 1000 first: new Date(1700000000 * 1000).
Does a JavaScript Date store a timezone?
No. A Date is internally a UTC millisecond count. The timezone is applied only when you call toString() or toLocaleString() — and only by reading the runtime's local zone. Always pass an explicit timeZone option to Intl.DateTimeFormat or toLocaleString for predictable output across environments.
How do I convert a Unix timestamp to a JavaScript Date?
If the timestamp is milliseconds (13 digits), call new Date(ms). If it's seconds (10 digits), multiply by 1000 first: new Date(seconds * 1000). To format in a specific zone, pipe through Intl.DateTimeFormat with a timeZone option.
When should I use performance.now() instead of Date.now()?
Use performance.now() for any elapsed-time measurement. It reads a monotonic clock that never moves backward; Date.now() reads the wall-clock, which NTP can step backward mid-benchmark. Date.now() is correct for 'when did this happen?'; performance.now() is correct for 'how long did it take?'
Should I use Temporal or Date for new code?
Temporal for any non-trivial date arithmetic — adding days across DST, constructing instants from wall-clock + zone, comparing zoned values. Temporal reached TC39 Stage 4 in March 2026 and ships natively in Chrome 144, Firefox 139, and Node.js 26 by default. Date is still fine for simple Date.now() reads, ISO 8601 formatting, and any code that needs to support older runtimes.
How do I get sub-millisecond precision in JavaScript?
performance.now() returns a fractional millisecond value (microsecond precision in most browsers, with quantization for fingerprinting protection). For an absolute Unix-timestamp form: performance.timeOrigin + performance.now() returns ms-since-epoch with sub-ms precision. Note that high-resolution timers are clamped by some runtimes for spectre mitigations.
Is Date.now() faster than new Date().getTime()?
Yes — Date.now() is a static method that skips constructing a Date object. For tight loops or hot paths, prefer Date.now(). The difference is negligible for occasional reads but adds up when called per request or per render frame.
Why does Date.parse('01/02/2026') return a different value in different browsers?
Because the ECMAScript specification explicitly allows implementation-defined parsing for non-ISO 8601 formats. '01/02/2026' is January 2 in some runtimes and February 1 in others. Always pass strict ISO 8601 strings (YYYY-MM-DDTHH:mm:ss.sssZ) to guarantee portable parsing.
How do I format a Date in a specific timezone?
Pass a timeZone option to Intl.DateTimeFormat or to Date.prototype.toLocaleString. The option takes an IANA name like America/New_York. Without it, the runtime uses the system timezone, which differs between developer laptops and production servers.