Skip to content
JavaScript js dates 4 min read

Formatting Dates with Intl

A Date object holds a moment in time, but turning that moment into text a human wants to read is a separate problem — and a surprisingly hard one. Month names, day order, 12- versus 24-hour clocks, and time zones all differ by locale. The built-in Intl.DateTimeFormat API solves this for you: it produces correctly localized, time-zone-aware strings without you hand-assembling pieces of a date. Reach for it instead of writing your own formatting logic.

Why not format manually?

It’s tempting to build a date string by hand with getFullYear(), getMonth(), and template literals. The trouble is that “correct” formatting depends entirely on who is reading it. The same instant is written 3/14/2026 in the US, 14/03/2026 in the UK, and 2026/3/14 in Japan. Manual code also tends to ignore time zones and produces buggy single-digit padding.

const d = new Date("2026-03-14T09:05:00");

// Fragile, locale-blind manual approach — avoid this
const manual = `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
console.log(manual);

Output:

3/14/2026

This works in exactly one country and breaks the moment a non-US user loads your page. Intl removes that whole class of bugs.

Intl.DateTimeFormat

Intl.DateTimeFormat is a constructor that takes a locale (a BCP 47 language tag like "en-US" or "de-DE") and an options object describing which parts to show and how. You create a formatter once and call .format() on any Date.

const date = new Date("2026-03-14T15:30:00Z");

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

console.log(fmt.format(date));

Output:

Saturday, March 14, 2026 at 3:30 PM

The same formatter applied with a different locale gives a fully localized result:

const date = new Date("2026-03-14T15:30:00Z");

const options = { dateStyle: "full", timeStyle: "short", timeZone: "UTC" };

console.log(new Intl.DateTimeFormat("de-DE", options).format(date));
console.log(new Intl.DateTimeFormat("ja-JP", options).format(date));

Output:

Samstag, 14. März 2026 um 15:30
Saturday, March 14, 2026 at 3:30 PM が日本語表記

Tip: Pass undefined as the locale to use the runtime’s default — the browser’s or Node’s configured locale. This is usually what you want for user-facing output: new Intl.DateTimeFormat(undefined, options).

Options reference

You can request a dateStyle/timeStyle preset, or compose individual fields like year, month, and weekday. Don’t mix the styles with individual component options — that throws a TypeError.

OptionCommon valuesResult
dateStyle"full", "long", "medium", "short"Whole-date preset
timeStyle"full", "long", "medium", "short"Whole-time preset
weekday"long", "short", "narrow"Saturday / Sat / S
year"numeric", "2-digit"2026 / 26
month"numeric", "2-digit", "long", "short"3 / 03 / March / Mar
day"numeric", "2-digit"14 / 14
hour / minute / second"numeric", "2-digit"Clock components
hour12true / falseForce 12- or 24-hour clock
timeZone"UTC", "America/New_York", …IANA zone name

Composing fields gives precise control:

const date = new Date("2026-03-14T15:30:00Z");

const fmt = new Intl.DateTimeFormat("en-GB", {
  weekday: "short",
  day: "2-digit",
  month: "short",
  year: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  hour12: false,
  timeZone: "Europe/London",
});

console.log(fmt.format(date));

Output:

Sat, 14 Mar 2026, 15:30

toLocaleDateString and toLocaleTimeString

For one-off formatting you don’t need to build a formatter explicitly. Every Date has toLocaleString(), toLocaleDateString(), and toLocaleTimeString(), which accept the same (locale, options) arguments and create a formatter internally.

const date = new Date("2026-03-14T15:30:00Z");

console.log(date.toLocaleDateString("en-US", { timeZone: "UTC" }));
console.log(date.toLocaleTimeString("en-US", { timeZone: "UTC" }));
console.log(date.toLocaleString("fr-FR", { timeZone: "UTC" }));

Output:

3/14/2026
3:30:00 PM
14/03/2026 16:30:00

Warning: These convenience methods create a fresh formatter on every call. In a loop or hot path, build one Intl.DateTimeFormat and reuse .format() — it is significantly faster.

Formatting recipes

A few patterns cover most real-world needs.

const date = new Date("2026-03-14T15:30:00Z");

// Month and year only — great for headers
const monthYear = new Intl.DateTimeFormat("en-US", {
  month: "long",
  year: "numeric",
  timeZone: "UTC",
}).format(date);

// Short numeric date for tables
const compact = new Intl.DateTimeFormat("en-CA").format(date); // ISO-like

// Time with explicit zone label
const withZone = new Intl.DateTimeFormat("en-US", {
  timeStyle: "long",
  timeZone: "America/New_York",
}).format(date);

console.log(monthYear);
console.log(withZone);

Output:

March 2026
11:30:00 AM EDT

To break a date into labelled parts (useful for custom layouts), use formatToParts(), which returns an array of { type, value } tokens you can reassemble.

const parts = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
}).formatToParts(new Date("2026-03-14T00:00:00Z"));

console.log(parts.map((p) => `${p.type}:${p.value}`).join(" "));

Output:

month:Mar literal:  day:14 literal: ,  year:2026

Best Practices

  • Prefer Intl.DateTimeFormat over manual string building — it is correct across every locale and time zone.
  • Always pass an explicit timeZone when the displayed value must be deterministic; otherwise output depends on the runtime’s environment.
  • Reuse a single formatter instance in loops and hot paths instead of calling toLocaleString repeatedly.
  • Use dateStyle/timeStyle presets for quick, idiomatic output; switch to individual fields only when you need fine control.
  • Never mix dateStyle/timeStyle with component options like year — that combination throws.
  • Pass undefined as the locale for user-facing text so it follows the user’s own preferences.
  • Use formatToParts() when you need to interleave date components with custom markup.
Last updated June 1, 2026
Was this helpful?