How to get current epoch milliseconds with Date.now() in JavaScript? useful answer is: Date.now().

Date.now() Returns the number of milliseconds since the Unix epoch (January 1st, 1970 at midnight UTC). It is the default for timestamps, time comparisons, and turning “right now” into a value a computer can sort.

const nowMs = Date.now();                    // 13 digits, epoch milliseconds
const nowSeconds = Math.floor(nowMs / 1000); // 10 digits, Unix seconds
const date = new Date(nowMs);                // Date expects milliseconds

A small but important detail: JavaScript’s built-in Date APIs operate in milliseconds while many APIs, databases and command line tools expect Unix time in seconds. A date in 2026 accidentally becomes a date in 1970 when the two are mixed together – the technological equivalent of sending an email with the wrong attachment.

The quickest sanity check whether it's milliseconds or seconds is the number of digits:

  • A modern Unix timestamp in seconds is typically 10 digits.
  • A JavaScript epoch timestamp in milliseconds is typically 13 digits.
Task Copy this Unit
Get the current timestamp for JavaScript Date Date.now() milliseconds
Get the current Unix timestamp for many APIs Math.floor(Date.now() / 1000) seconds
Convert Unix seconds to Date new Date(seconds * 1000) input: seconds
Convert epoch milliseconds to Date new Date(milliseconds) input: milliseconds
Format a timestamp as ISO UTC new Date(ms).toISOString() output: string
Measure how long code takes performance.now() monotonic milliseconds

If need to convert between epoch and date, you can use below tools:

What Date.now() really returns

Date.now() returns a number representing the number of milliseconds since 1970-01-01 00:00:00 UTC. It does not return a Date object, carry its own timezone, or measure how long a function took to run. It is simply a timestamp: a point on the shared UTC timeline.

JavaScript Date objects do the same thing under the hood. Each holds a single millisecond value relative to the Unix epoch. Timezones are only useful for formatting that value for display, e.g. with UTC methods, local-time methods, or Intl.DateTimeFormat with an IANA timezone like "America/Los_Angeles".

const ms = Date.now()

console.log(ms);    // 1781947500000 
console.log( new Date(ms)); // Date object representing that moment
console.log(new Date(ms).toISOString()); // UTC string

Here is the same moment expressed correctly and incorrectly:

Value Interpretation Result
1781947500000 JavaScript / Unix milliseconds 2026-06-20T09:25:00.000Z
1781947500 Unix seconds 2026-06-20T09:25:00.000Z
1781947500 passed directly to new Date() milliseconds by mistake 1970-01-21T14:59:07.500Z

Use Seconds or Milliseconds

The right unit depends on where the timestamp is going to next.

// For Browser UI, JS Date, localStorage, client events
const createdAtMs = Date.now();

// For Server APIs, JWT exp/iat, Unix-style database filters
const createdAtSeconds = Math.floor(Date.now() / 1000);

If the next line of code is JavaScript, use milliseconds:

new Date(createdAtMs);

Typical examples are:

  • Analytics events generated in browser
  • Frontend cache expiration times
  • React / Vue / Svelte UI state
  • API fields documented explicitly as epoch milliseconds

When you're talking about Unix-style systems, use seconds.

  • JWT exp, iat and nbf claims
  • API fields documented as Unix seconds (many payment and infrastructure APIs)
  • SQL epoch filters expect seconds
  • Linux shell commands e.g. date -d @SECONDS
  • Payloads for PHP's time() or Python's int(time.time())

The trick is not to make your code's readers guess. The unit is not described by the field createdAt. Names like createdAtMs, createdAtUnixSeconds and expiresAtEpochMs may be a little verbose, but they avoid a surprisingly expensive class of bugs.

Often the most considerate names are boring names.

The classic 1970 bug

This is the timestamp mistake every JavaScript developer eventually meets:

new Date(1700000000).toISOString()
// "1970-01-20T16:13:20.000Z" <-- wrong if this is Unix seconds

new Date(1700000000000).toISOString()
// "2023-11-14T22:13:20.000Z" <-- correct

The reason is simple: new Date(number) always interprets a numeric value as milliseconds. Pass it a 10-digit Unix timestamp in seconds and JavaScript treats it as a very small millisecond value, placing the date in January 1970.

You can convert seconds before constructing the date:

const unixSeconds = 1700000000;
const date = new Date(unixSeconds * 1000);

If your code is supposed to work with timestamps from different sources it may be reasonable to have a small boundary helper. But think of it as a compatibility layer, not a permanent replacement for a well-defined API contract.

function unixToDate(value) {
  const n = Number(value);

  if (!Number.isFinite(n)) {
    throw new TypeError("Timestamp must be numeric");
  }

  return new Date(Math.abs(n) < 1e11 ? n * 1000 : n);
}

The 1e11 threshold is for normal contemporary timestamps because Unix seconds are below it for thousands of years but current epoch milliseconds are well above it. If the caller asks for an archival, scientific, or intentionally ancient/future date, ask for the unit.

new Date().getTime() vs Date.now()

These expressions produce the same kind of value: the current epoch time in milliseconds.

Date.now();
new Date().getTime()

Because time keeps moving, they are not guarantyd to return the same number if called one after the other. But in practice they both answer the same thing.

The difference is in their output. Use the static method Date.now() which returns a number immediately. new Date().getTime() creates a Date object and then gets the milliseconds from that object.

The difference is insignificant for one-off code. But if all you need is a timestamp, Date.now() is the clearer default, especially if you’re in a tight loop, request logger, analytics collector or render-frame callback.

If you want a Date object right away, make one:

const date = new Date();
const iso = date.toISOString();

If you just want epoch milliseconds, use Date.now():

const sentAtMs = Date.now();

If a Date object already exists, call date.getTime() to retrieve its epoch milliseconds. +new Date() also produces the same numeric value, but it is terse enough to slow down a code review; prefer the named methods in production code.

Date.now() and performance.now()

Use these APIs for different jobs.

Date.now() reads the wall clock, so it is useful for recording when something happened. But the wall clock can change: a user can change the system time, a virtual machine can come back, or a time synchronization service can correct the clock.

Utilize performance.now() for timing elapsed time. It is a high resolution monotonically increasing timer in terms of performance.timeOrigin so it is meant to keep moving forward during the current run-time.

//Good for “when did this event occur?”
const receivedAtMs = Date.now();

// Good for "how long did this operation take?"
const startTime = performance.now();
await doWork();
const elapsedMs = performance.now() - startTime;

A useful rule of thumb:

Question API
When did it happen? Date.now()
How long did it take? performance.now()
What do I put in an API payload? documented in seconds or milliseconds
What do I show the user? Intl.DateTimeFormat

Format a Timestamp in UTC or the User’s Time Zone

A Date is a single point in time. Formatting determines what that instant looks like to a human: in UTC, in a customer’s local time zone, and in the conventions of their locale.

const date = new Date(1781947500000);

date.toISOString();
// "2026-06-20T09:25:00.000Z"

new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short",
  timeZone: "America/New_York",
}).format(date);
// "Jun 20, 2026, 5:25 AM"

toISOString() is awesome for logs, API responses, and other machine readable output. The trailing Z always indicates that it is in UTC. It is deliberately consistent, not overly friendly.

Use Intl for text shown to the user.DateTimeFormat The locale controls language and presentation conventions, the timeZone controls the clock used to display the instant. Those are other options. For example, an American living in Tokyo might want "en-US" formatting in "Asia/Tokyo".

Use IANA time-zone names such as "UTC", "America/New_York", and "Asia/Tokyo". They account for daylight-saving changes where applicable. Avoid hard-coded offsets such as "-05:00" for a location that observes daylight saving time; New York is not always five hours behind UTC.

Using the same formatter for repeated output There is no need to create a formatter for each table cell or activity log row. Create a formatter for a given locale and time zone and reuse it:

const fmt = new Intl.DateTimeFormat('en-US', {
  timeZone: "UTC",
  dateStyle: "medium",
  timeStyle: "medium",
}); 

const formattedRows = rows.map((row) =>
  fmt.format(new Date(row.createdAtMs)),
);

The practical split is easy : store and transmit an instant as epoch milliseconds or an ISO string . Format it only at the boundary where a person reads it . This maintains the underlying time, but allows each reader to see it on the clock that makes sense to them.

Intl.DateTimeFormat accepts IANA time zone names and falls back to the runtime’s local zone when no timeZone is passed.

toISOString() is the safest built-in output for a stored or exchanged instant because it is always UTC. toUTCString() produces a human-readable UTC string, while toString() and toLocaleString() depend on the host locale or timezone unless you give Intl.DateTimeFormat explicit options. Do not persist those display strings when you need to preserve an instant.

Safely parse date strings

JavaScript reliably supports the ECMAScript date-time string format, which closely resembles ISO 8601. Anything else may be accepted by one runtime and rejected, or interpreted differently, by another. That is not a parsing strategy; it is a future debugging session in disguise.

Use explicit UTC timestamps or timestamps with an explicit offset at system boundaries:

new Date("2026-06-20T09:25:00Z");      // UTC
new Date("2026-06-20T09:25:00-04:00"); // explicit offset
Date.parse("1970-01-01T00:00:00Z");    // 0

The Z means UTC. An offset such as -04:00 also identifies a specific instant, so JavaScript can convert it safely to its internal epoch-millisecond value.

Avoid relying on these formats in APIs, database imports, and other system boundaries:

new Date("06/20/2026");   // Legacy, implementation-dependent parsing
new Date("01/02/2026");   // Ambiguous to humans and brittle in code
new Date("Jun 20, 2026"); // Another legacy-parser gamble

A string that looks familiar is not necessarily portable. 01/02/2026 is particularly treacherous: does it mean January 2 or February 1? Even if every browser your team uses agrees today, the format has already failed the most important test: a reviewer cannot know its meaning at a glance.

There is one subtle rule:

new Date("2019-01-01");           // Treated as UTC
new Date("2019-01-01T00:00:00");  // Treated as local time

The first is a date-only string, and is interpreted as midnight UTC. The second has a time but no offset, so JavaScript assumes it is midnight in the runtime’s local time zone. They may be different points in time on a developer laptop and a server in different regions .

Also, don’t use the wishful new Date(input) for dates that came from users – better to parse it with a format aware library or Temporal.

Validate timestamp input before conversion

Never trust timestamps coming from API requests, CSV imports, webhooks, browser events and query strings. Check the value before you create a Date, and importantly, make the sender declare the unit.

A parser called parseEpochMs() should not make the implicit assumption that a small value is seconds. Which might be convenient for a legacy migration, but makes the contract harder to understand elsewhere.

function parseEpochMs(input) {
  if (typeof input !== "number" && typeof input !== "string") {
    throw new TypeError("timestamp must be a number or numeric string");
  }

  const n = Number(input);
  if (!Number.isFinite(n)) throw new TypeError("timestamp is not finite");

  const ms = Math.abs(n) < 1e11 ? n * 1000 : n;
  const date = new Date(ms);
  if (Number.isNaN(date.getTime())) throw new RangeError("timestamp is outside Date range");

  return { ms, date };
} 

Also a few guardrails are worth keeping:

  • Reject empty strings before Number("") turns them into 0.
  • Reject booleans before Number(true) turns them into 1.
  • Keep the original raw value and a request or row identifier when rejecting a webhook or CSV record. It makes a bad upstream payload much easier to diagnose.
  • If an API legitimately permits fractional seconds, define and document its rounding policy instead of letting conversion happen accidentally.
  • Use a legacy seconds-versus-milliseconds heuristic only in a clearly named compatibility adapter. It should be temporary plumbing, not your public contract.

Sub-millisecond accuracy in JavaScript

Date.now() returns whole milliseconds. That is exactly what most event timestamps need.

But elapsed time performance is better.now(); It returns a high resolution, relative, monotonic millisecond value.timeOrigin, so a duration can’t go backward if the clock changes.

const start = performance.now();

await doWork();

const elapsedMs = performance.now() - start;

This is useful when correlating measurements within one page or process, but it’s not a better replacement for Date.now() in an API payload. Every browsing context or worker has its own time origin, browsers may reduce the timer resolution for privacy reasons, and a fractional value does not guaranty equally precise real-world time. More numbers can be confidence-inspiring camouflage.

The other two were sub-millisecond values. Do not send them to an API that only documents Unix seconds or Unix milliseconds.

Temporal: better dates but check availability

Temporal is the new JavaScript date-time API. It distinguishes between the things that Date groups together: instants, plain dates, wall-clock times, zoned date-times, durations, and calendars.

Use Temporal for:

  • adding days or months during the daylight savings time
  • representing a calendar date without timezone
  • making a wall-clock appointment plus IANA timezone instant
  • not calling mutable date methods
  • codebases that may require Node.js 26+ or ship a polyfill

For example:

const instant = Temporal.Instant.fromEpochMilliseconds(Date.now());
const zdt = instant.toZonedDateTimeISO("America/New_York");
const tomorrowSameWallClock = zdt.add({ days: 1 });

As of July 15, 2026, MDN still lists Temporal as limited availability because it does not work in some widely used browsers. Node.js 26 comes with Temporal built-in. For public web apps, keep the polyfill or feature-detect before using native Temporal everywhere.

Some Timestamp Mistakes You Should Avoid

Symptom Reason Solution
Date is January 1970 Unix seconds passed to new Date() multiply by 1000
Date in the distant future milliseconds sent to seconds API divide by 1000 and floor
Test passes locally but fails in CI runtime timezone differs pass timeZone, or test ISO UTC
Elapsed time is negative wall clock moved use performance.now()
Equality check fails comparing Date objects compare .getTime() values
User date shifts a day date-only vs timezone mismatch model date-only values separately
DST adds/subtracts an hour raw millisecond calendar math use Temporal or a timezone-aware library
January becomes month 0 getMonth() and the component constructor are zero-indexed add 1 for display; write January as 0 in new Date(year, month, day)
“Tomorrow” changes the wall clock adding 86400000 milliseconds crosses a DST transition use a calendar-aware API; for local Date code, use setDate(getDate() + 1) deliberately

The most useful one-line code-review question is: What unit and time zone does this value have at this boundary?

Some copy and paste recipes

// Current epoch milliseconds
const nowMs = Date.now();

// Seconds of current unix
const nowSeconds = Math.floor(Date.now()/1000);

// iso string -> unix seconds
const iso = new Date(1700000000*1000).toISOString()

// epoch milliseconds to iso string
const isoFromMs = (new Date(1700000000000)).toISOString();

// Date -> epoch milliseconds
const ms = new Date("2026-06-20T09:25:00Z").getTime();

// Date -> Unix seconds
const seconds = Math.floor(new Date("2026-06-20T09:25:00Z").getTime() / 1000);

// Format in UTC
const utcText = new Intl.DateTimeFormat("en-US", {
  timeZone: "UTC",
  dateStyle: "medium",
  timeStyle: "medium",
}).format(new Date());

// Format in a named time zone
const nyText = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "medium",
  timeStyle: "medium",
}).format(new Date());

Thanks for reading.

Frequent questions:

Q: Does a JavaScript Date store a timezone?
A: No. A Date holds a single timestamp value. Local timezone, UTC, and named timezones are display choices, applied later by methods such as toString(), toISOString(), toLocaleString(), and Intl.DateTimeFormat.
Q: How do I convert Unix seconds to a JavaScript Date?
A: You can multiply seconds by 1000 before building the Date: new Date(seconds * 1000). If the input is already epoch milliseconds, pass it directly: new Date(milliseconds).
Q: How do I format a JavaScript Date in a specific timezone?
A: You can use Intl.DateTimeFormat or toLocaleString with a timeZone option, such as America/New_York or UTC. If the option is not provided, JavaScript uses the runtime's local timezone by default.
Q: Is Date.parse() safe for all date strings?
A: No. The ISO date-time string format is portable, but other formats are implementation-defined. Prefer strings like 2026-06-20T09:25:00Z or include an explicit offset.
Q: Should I use Temporal instead of Date?
A: You can use Temporal where it is available or where you can ship a polyfill, especially for timezone and calendar arithmetic. MDN still marks Temporal as limited availability, while Node.js 26 enables it by default. Date remains fine for simple timestamp reads and broad browser support.
Q: Is Date.now() faster than new Date().getTime()?
A: Usually yes, because Date.now() does not allocate a Date object. The difference rarely matters for occasional reads, but Date.now() is the cleaner default in hot paths.