I keep seeing people reach for moment.js, dayjs, date-fns, whatever, just to format a date. Every project I join has a different date library, half of them deprecated, and none of them do anything the browser can't already do.

The Intl API has been shipping in every major browser for years. It handles locales, timezones, currency, relative time, pluralization. All of it. No dependencies.

I stopped importing date libraries about two years ago. Here is what I use instead.

### The Problem

The code I see most often looks something like this:

// Don't do this
const date = new Date();
const formatted = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
// "16/9/2026" — works in Uruguay, confuses everyone in the US

// Or worse, the manual padding
const pad = (n) => String(n).padStart(2, '0');
const formatted2 = `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()}`;
// "16.09.2026" — Italian format, fine, but why am I writing this by hand in 2026?

This breaks the moment you need a different locale, or a different format, or a month name instead of a number. So someone adds moment.js. Then moment.js becomes a bundle problem. Then someone migrates to dayjs. Then to date-fns. It never ends.

Code on a laptop screen
The moment you import a date library, you've already lost.

Intl is built into the JavaScript runtime. It's not a polyfill, it's not a package, it's just there.

### Intl.DateTimeFormat

This is the one I use the most. Dates and times, formatted for any locale, with whatever options you need.

Here is the basic usage:

const date = new Date();

// Locale-specific short date
new Intl.DateTimeFormat('es-UY').format(date);
// "16/9/2026"

new Intl.DateTimeFormat('en-US').format(date);
// "9/16/2026"

new Intl.DateTimeFormat('it-IT').format(date);
// "16/9/2026"

new Intl.DateTimeFormat('de-DE').format(date);
// "16.9.2026"

Already more useful than anything I'd write by hand. But the options object is where it gets good:

const fmt = new Intl.DateTimeFormat('en-GB', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  timeZone: 'America/Montevideo'
});

fmt.format(new Date());
// "Wednesday, 16 September 2026, 08:00"

// Spanish, because I live in Uruguay
const fmtEs = new Intl.DateTimeFormat('es-ES', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  timeZone: 'America/Montevideo'
});

fmtEs.format(new Date());
// "miércoles, 16 de septiembre de 2026"
Programming code on screen
One API call replaces 40 lines of manual string wrangling.

I use the options pattern a lot. You create the formatter once, reuse it, done. No string concatenation, no manual month arrays, no `getMonth() + 1` nonsense.

The available option keys I actually use:

- `dateStyle`: "full", "long", "medium", "short" — shortcut for common formats

- `timeStyle`: same as above, for time

- `weekday`: "narrow", "short", "long"

- `era`: "narrow", "short", "long" — BC/AD stuff, never used it but it's there

- `year`, `month`, `day`, `hour`, `minute`, `second`, `timeZoneName` — each with their own format options

The `dateStyle` shortcut is the one I reach for most:

// Quick formats
new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date());
// "Wednesday, September 16, 2026"

new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long' }).format(new Date());
// "2026年9月16日水曜日"

new Intl.DateTimeFormat('fr-FR', { dateStyle: 'medium' }).format(new Date());
// "16 sept. 2026"

That's three locales, three formats, zero dependencies. Try doing that with manual string concatenation.

### Intl.NumberFormat

Dates are the obvious one. Numbers are the one people forget about. Currency formatting is different everywhere, and getting it wrong looks amateurish.

// Currency — the big one
new Intl.NumberFormat('es-UY', {
  style: 'currency',
  currency: 'UYU'
}).format(1500);
// "$ 1.500,00"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(1500);
// "$1,500.00"

new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
}).format(1500);
// "1.500,00 €"

new Intl.NumberFormat('it-IT', {
  style: 'currency',
  currency: 'EUR'
}).format(1500);
// "1.500,00 €"
Code editor with syntax highlighting
Currency formatting that actually respects the locale. Novel concept.

Note how the euro symbol goes after the number in Germany and Italy, but the dollar goes before in the US. If you're hardcoding `$` + amount, you're doing it wrong for half the planet.

Number formatting without currency:

// Grouping separators
new Intl.NumberFormat('en-US').format(1234567.89);
// "1,234,567.89"

new Intl.NumberFormat('de-DE').format(1234567.89);
// "1.234.567,89"

new Intl.NumberFormat('es-ES').format(1234567.89);
// "1.234.567,89"

// Significant digits
new Intl.NumberFormat('en-US', {
  maximumSignificantDigits: 3
}).format(1234567.89);
// "1,230,000"

Unit formatting is also there, if you need it:

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"

new Intl.NumberFormat('es-ES', {
  style: 'unit',
  unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'celsius'
}).format(25);
// "25°C"

### Intl.RelativeTimeFormat

"3 days ago", "in 2 hours", "yesterday" — every app needs this, and everyone writes their own broken version.

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

rtf.format(-1, 'day');
// "yesterday"

rtf.format(-3, 'day');
// "3 days ago"

rtf.format(2, 'hour');
// "in 2 hours"

rtf.format(1, 'week');
// "next week"

// Spanish
const rtfEs = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });

rtfEs.format(-1, 'day');
// "ayer"

rtfEs.format(-3, 'day');
// "hace 3 días"

rtfEs.format(2, 'hour');
// "dentro de 2 horas"
Team working with code on screen
Relative time formatting in 2 lines. Your custom function was 30 lines and wrong in Japanese.

The `numeric: 'auto'` option is what gives you "yesterday" instead of "1 day ago". Without it you get the numeric version, which is fine for most cases.

### Intl.ListFormat

This one is lesser-known but I use it more than I expected. Formatting lists with proper conjunctions:

const lf = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });

lf.format(['Python', 'JavaScript', 'Rust']);
// "Python, JavaScript, and Rust"

// Disjunction (or)
const lfOr = new Intl.ListFormat('en', { style: 'long', type: 'disjunction' });

lfOr.format(['Python', 'JavaScript', 'Rust']);
// "Python, JavaScript, or Rust"

// Spanish
const lfEs = new Intl.ListFormat('es', { style: 'long', type: 'conjunction' });

lfEs.format(['Python', 'JavaScript', 'Rust']);
// "Python, JavaScript y Rust"

Yes, the "and" / "or" / "y" is locale-aware. The Oxford comma is handled automatically in English. I'm not writing a function to figure that out.

### Putting It Together

A practical example. I have a blog post with a publish date and a view count. I want to display both formatted for the user's locale:

function formatPostMeta(date, views, locale = 'en') {
  const dateFmt = new Intl.DateTimeFormat(locale, {
    dateStyle: 'long',
    timeStyle: 'short'
  });
  
  const numberFmt = new Intl.NumberFormat(locale, {
    notation: 'compact'
  });
  
  return {
    date: dateFmt.format(new Date(date)),
    views: `${numberFmt.format(views)} views`
  };
}

const meta = formatPostMeta('2026-09-16T08:00:00-03:00', 15420, 'es-UY');
// { date: "16 de septiembre de 2026, 8:00", views: "15 mil views" }

const meta2 = formatPostMeta('2026-09-16T08:00:00-03:00', 15420, 'en-US');
// { date: "September 16, 2026 at 8:00 AM", views: "15K views" }

const meta3 = formatPostMeta('2026-09-16T08:00:00-03:00', 15420, 'de-DE');
// { date: "16. September 2026 um 08:00", views: "15 Tsd. views" }

That's the whole thing. No libraries, no utilities file with 200 lines of date helpers, no bundle bloat. The browser does it all.

### Browser Support

I checked caniuse before writing this, expecting to find some caveat. There isn't one.

- `Intl.DateTimeFormat`: 97%+ global support, since 2017

- `Intl.NumberFormat`: 97%+ global support, since 2017

- `Intl.RelativeTimeFormat`: 95%+ global support, since 2020

- `Intl.ListFormat`: 95%+ global support, since 2020

If you need to support IE11, you have bigger problems than date formatting. Move on.

The `dateStyle` and `timeStyle` options are slightly newer (2020ish) but still at 95%+. If you're worried, the long-form options object approach works everywhere.

### Conclusion

I deleted moment.js from a project last month. Replaced 12 imports with Intl calls. Bundle dropped 67KB. Nothing broke.

The Intl API is not new, it's not experimental, it's not a polyfill. It's been sitting there while everyone keeps installing date libraries. Stop installing date libraries.