JavaScript date formatting with Intl.DateTimeFormat

Format dates in JavaScript with Intl.DateTimeFormat: locales, dateStyle and timeStyle, time zones, formatRange, formatToParts, and common TypeErrors. Includes tested sample output from Node.js (V8).

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

JavaScript date formatting with Intl.DateTimeFormat

When you need date formatting in JS—whether for logs, UI labels, or API payloads—reaching for string concatenation on Date getters goes wrong fast across languages, calendars, and time zones. The standard JavaScript date formatter for display strings is Intl.DateTimeFormat: one object captures locale and options, and its format() method turns a Date (or other supported temporal types in newer engines) into a human-readable string.

If you are building the underlying Date first, see how to get the current date in JavaScript and how to add days to a date. For ISO 8601 strings used in APIs and storage (a different problem from locale display), see converting JS dates to ISO date strings.

This guide focuses on Intl.DateTimeFormat, including dateStyle and timeStyle, formatRange, formatToParts, and pitfalls that show up in real apps (including confusion with jQuery Validate).

Tested on: Node.js v20.18.2 with full ICU. Exact strings can vary slightly by ICU version; each example fixes locale and often timeZone or process.env.TZ so your run should match the described pattern.


Quick reference

Use this table for date formatting js: style shortcuts vs granular fields and stability.

Goal Pattern
Locale display new Intl.DateTimeFormat(locale, options).format(date)
Preset length dateStyle / timeStyle (not mixed with per-field options)
Ranges formatRange(start, end)
Stable tests Fix locale, timeZone, and process.env.TZ when needed

Intl.DateTimeFormat basics

The Intl.DateTimeFormat constructor takes up to two arguments: a BCP 47 locale (string or array of fallbacks) and an options object. Call format(date) on the instance to obtain the string.

Like Intl.NumberFormat, the formatter resolves a pattern that best matches your options and the locale. You might ask for a sparse set of fields, but the engine can still include related parts if the locale’s pattern requires them for a coherent datetime formatting result.

javascript
const d = new Date("2020-01-02T13:14:15.000Z"); // instant: 2 Jan 2020, 13:14:15 UTC

console.log(new Intl.DateTimeFormat("en-US").format(d));
console.log(new Intl.DateTimeFormat("fr-FR").format(d));

const longDate = {
  weekday: "long",
  month: "long",
  year: "numeric",
  day: "numeric",
};
console.log(new Intl.DateTimeFormat("en-US", longDate).format(d));
console.log(new Intl.DateTimeFormat("es-ES", longDate).format(d));

const nyTime = {
  hour: "numeric",
  minute: "2-digit",
  timeZone: "America/New_York",
};
console.log(new Intl.DateTimeFormat("fr-CA", nyTime).format(d));
Output

You should get five lines: US short numeric date, French day/month order, long English weekday line, long Spanish weekday line, then a Canadian-French time fragment for New York (13:14 UTC → morning Eastern) such as 8 h 14 (exact spacing can vary slightly by ICU).

The first two lines illustrate locale-sensitive order and separators for the same instant. The fr-CA line shows time zone conversion: 13:14 UTC becomes morning in New York, formatted with Canadian French conventions. After you order instants for logic, compare dates in JavaScript with getTime() or operators; use Intl only for the human-readable layer.


Locale defaults without extra options

If you omit granular options, you get each locale’s default numeric-style date (still locale dependent). The following uses a fixed instant in UTC so your JS date formatting experiments match this article.

javascript
process.env.TZ = "UTC";
const d = new Date("2022-11-09T12:00:00.000Z");

console.log(new Intl.DateTimeFormat("en-US").format(d));
console.log(new Intl.DateTimeFormat("en-GB").format(d));
console.log(new Intl.DateTimeFormat("ko-KR").format(d));
Output

With TZ=UTC and this instant, you should see US month/day/year ordering, British day/month/year ordering, and a Korean-style year-first line—punctuation may differ slightly by ICU, but the field order matches each locale’s default numeric pattern.


dateStyle and timeStyle

For “preset” lengths of date and time, ECMAScript provides dateStyle and timeStyle. Each accepts only "full", "long", "medium", or "short"—not values like "numeric" (those belong to per-field options such as year: "numeric").

They are mutually exclusive with individual field options (weekday, month, hour, and so on). Mixing them triggers a TypeError (see below).

javascript
process.env.TZ = "Europe/London";
const d = new Date("2022-11-09T14:04:00"); // local wall-clock in London

console.log(
  new Intl.DateTimeFormat("en-GB", {
    dateStyle: "full",
    timeStyle: "short",
  }).format(d),
);
Output

With TZ=Europe/London and the given local wall time, en-GB + dateStyle: "full" + timeStyle: "short" typically prints a single line like Wednesday 9 November 2022 at 14:04 (comma or “at” wording follows locale data).

For quick calls without storing a formatter, Date.prototype.toLocaleString accepts the same arguments:

javascript
process.env.TZ = "UTC";
const d = new Date("2022-11-09T12:00:00.000Z");
console.log(d.toLocaleString("en-GB", { dateStyle: "medium", timeStyle: "short" }));
Output

With TZ=UTC, you should see a compact British-style datetime string for noon UTC on 9 Nov 2022, such as 9 Nov 2022, 12:00.


formatRange for concise intervals

formatRange(startDate, endDate) prints the shortest clear range for the configured locale and options—useful for bookings, calendars, and filters. If your source data is an unsorted array of Date values, sort by date in JavaScript before you build range labels.

javascript
const formatter = new Intl.DateTimeFormat("en", { dateStyle: "long" });
const christmas = new Date(1999, 11, 24);
const newYearsEve = new Date(1999, 11, 31);
const newYearsDay = new Date(2000, 0, 1);
console.log(formatter.formatRange(christmas, newYearsEve));
console.log(formatter.formatRange(newYearsEve, newYearsDay));
Output

Two lines: a merged range across late December (December 24 – 31, 1999 with a range separator character from Unicode), then a range that crosses into January 2000.


formatToParts and resolvedOptions

When you need a custom layout but still want locale-aware pieces, use formatToParts to get labeled segments (day, month, literal, etc.), or resolvedOptions() to see the effective locale, calendar, numbering system, hour cycle, and which style flags won.

javascript
const d = new Date("2020-01-02T13:14:15.000Z");
const f = new Intl.DateTimeFormat("en-GB", {
  dateStyle: "medium",
  timeStyle: "short",
  timeZone: "UTC",
});
console.log(JSON.stringify(f.formatToParts(d)));
console.log(JSON.stringify(f.resolvedOptions(), null, 2));
Output

formatToParts returns a short array of typed segments (day, literal spaces, month, year, comma, hour, colon, minute). resolvedOptions() prints a JSON object showing locale: "en-GB", calendar: "gregory", timeZone: "UTC", dateStyle / timeStyle, and related hour-cycle flags—useful when debugging formatter configuration.


Common TypeError when mixing styles and fields

If you pass dateStyle or timeStyle together with granular fields, initialization fails:

javascript
const d = new Date("2022-11-09T12:00:00.000Z");
try {
  new Intl.DateTimeFormat("en", { dateStyle: "full", weekday: "long" }).format(d);
} catch (e) {
  console.log(e.toString());
}
Output

The catch logs a TypeError whose message explains that weekday cannot be combined with dateStyle (wording may vary slightly by engine).

Pick either the style shortcuts or explicit year, month, day, hour, and similar options. Similar “invalid combination” mistakes show up elsewhere when APIs throw—see how to throw and handle errors in JavaScript if you are wiring this into validation flows.


Node.js, browsers, and reproducibility

Modern Node.js builds ship with full ICU data, so Intl.DateTimeFormat behaves like a capable browser for most sites. Strings can still vary by ICU version and default locale, so for tests and docs fix locales and, when needed, set process.env.TZ (as in the snippets above) instead of relying on the host’s zone. For how new Date interacts with the OS clock and zones in Node, read why Node’s Date constructor can look “wrong” at a glance.


Summary

JavaScript datetime formatting for display belongs in Intl.DateTimeFormat: pass locales and either dateStyle / timeStyle or fine-grained field options (not both), add timeZone when the wall clock must reflect a specific region, and use formatRange, formatToParts, and resolvedOptions() when you need ranges, custom assembly, or debugging. Pair this with proper parsing or validation for user input—Intl is a formatter, not a validator—so tools like jQuery Validate stay on the parsing/rule side while Intl handles the readable output.


References

MDN and ECMA-402 references for Intl.DateTimeFormat and internationalization.

Intl.DateTimeFormat - JavaScript | MDN
Internationalization - JavaScript | MDN
ECMA-402 DateTimeFormat objects


Frequently Asked Questions

1. What is the best way to do date formatting in JavaScript today?

For locale-aware output, use the built-in Intl.DateTimeFormat API (or the Date.prototype.toLocaleString shortcut with the same options). It avoids shipping your own locale tables and tracks user language and calendar preferences when you pass the right locale argument.

2. What values does dateStyle accept in JavaScript?

dateStyle must be one of full, long, medium, or short. It is a shortcut that expands to a bundle of field options; it cannot be mixed with granular options like weekday or hour in the same Intl.DateTimeFormat constructor.

3. Why does new Intl.DateTimeFormat throw TypeError about dateStyle?

If you set dateStyle or timeStyle, you cannot also set individual date or time component options such as weekday, month, or hour. Remove one side of the conflict or split the work into two formatters.

4. Does jQuery Validation use Intl.DateTimeFormat?

No. jQuery Validate checks user input with rules like date and dateISO or custom addMethod logic. Intl.DateTimeFormat is for formatting Date values to display strings. Use parsing or strict pattern checks for validation, and Intl for showing consistent locale-friendly text after you have a valid Date.

5. How do I format a JavaScript date in a specific time zone?

Pass timeZone in the options object, for example America/New_York or UTC. The formatter converts the instant to that zone before applying locale rules.

6. How is JavaScript datetime formatting different in Node and the browser?

The same ECMAScript API exists in both. Output can still differ by ICU/version and default locale. Set explicit locales and time zones when you need stable strings across environments.
Olorunfemi Akinlua

Boasting over five years of experience in JavaScript, specializing in technical content writing and UX design. With a keen focus on programming languages, he crafts compelling content and designs …