Javascript Logo
en español

Cómo formatear precios y fechas con Intl

Fácil
-
3 min. lectura

Para pintar un precio en pantalla seguramente hayas escrito algo así

const precio = 1234.5;
`${precio.toFixed(2).replace(".", ",")} €`; // "1234,50 €"

Funciona. Hasta que llega un usuario de otro país, una fecha, o un porcentaje.

Intl ya viene en el navegador y en Node, sin instalar nada, y se encarga de todo eso.

Precios y monedas

const eur = new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR"
});

eur.format(1234.5);      // "1234,50 €"
eur.format(1234567.891); // "1.234.567,89 €"

El símbolo, la posición, el separador decimal y el de millares salen del idioma. Cambia el primer argumento y cambia todo

new Intl.NumberFormat("en-US", { style: "currency", currency: "EUR" }).format(1234567.891);
// "€1,234,567.89"

Fíjate en que la moneda y el idioma son cosas distintas. Puedes mostrar dólares a un español

new Intl.NumberFormat("es-ES", { style: "currency", currency: "USD" }).format(1234.5);
// "1234,50 US$"

El punto de millar que no aparece

Habrás notado algo raro arriba

eur.format(1234.5);   // "1234,50 €"   sin punto
eur.format(12345.5);  // "12.345,50 €" con punto

No es un bug, es que en español no se agrupan los números de cuatro dígitos. Y es una regla de nuestro idioma, no de Intl. El mismo importe en alemán o en francés sí lleva separador

const precio = (locale) =>
  new Intl.NumberFormat(locale, { style: "currency", currency: "EUR" }).format(1234.5);

precio("es-ES"); // "1234,50 €"
precio("it-IT"); // "1234,50 €"
precio("de-DE"); // "1.234,50 €"
precio("fr-FR"); // "1 234,50 €"
precio("en-US"); // "€1,234.50"

Esto sale de CLDR, la base de datos de convenciones locales que usan los navegadores. Ahí cada idioma declara a partir de cuántos dígitos empieza a agrupar. Y es justo el tipo de detalle que nunca vas a acertar a mano.

Si aun así prefieres agrupar siempre, pídelo

new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR",
  useGrouping: "always"
}).format(1234.5); // "1.234,50 €"

Porcentajes, unidades y números compactos

El mismo objeto sirve para bastante más que dinero

const n = (opts) => new Intl.NumberFormat("es-ES", opts);

n({ style: "percent", maximumFractionDigits: 1 }).format(0.256); // "25,6 %"
n({ style: "unit", unit: "kilometer-per-hour" }).format(120);    // "120 km/h"
n({ notation: "compact" }).format(1234567);                      // "1,2 M"

En percent le pasas la fracción, no el número ya multiplicado. Le das 0.256 y escribe 25,6 %.

Fechas

Misma idea, otro constructor

const fecha = new Date("2026-09-08T14:30:00Z");
const opts = { timeZone: "Europe/Madrid" };

new Intl.DateTimeFormat("es-ES", { dateStyle: "long", ...opts }).format(fecha);
// "8 de septiembre de 2026"

new Intl.DateTimeFormat("es-ES", { dateStyle: "short", ...opts }).format(fecha);
// "8/9/26"

new Intl.DateTimeFormat("es-ES", { dateStyle: "long", timeStyle: "short", ...opts }).format(fecha);
// "8 de septiembre de 2026, 16:30"

Pasa siempre la timeZone. Si no la pones, se usa la del navegador y el mismo instante se ve distinto según quién mire.

Hace 3 días

Esta es la que ahorra más código propio

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

rtf.format(-1, "day");     // "ayer"
rtf.format(-3, "day");     // "hace 3 días"
rtf.format(-5, "minute");  // "hace 5 minutos"
rtf.format(2, "week");     // "dentro de 2 semanas"

Con numeric: "auto" escribe "ayer" en lugar de "hace 1 día". Con numeric: "always" no lo hace nunca.

Listas

Y para no volver a pelearte con la coma final

new Intl.ListFormat("es", { type: "conjunction" }).format(["React", "Vue", "Svelte"]);
// "React, Vue y Svelte"

new Intl.ListFormat("es", { type: "disjunction" }).format(["React", "Vue", "Svelte"]);
// "React, Vue o Svelte"

Crea el formateador una sola vez

Esto importa. Construir un formateador es caro, formatear con él es barato.

// mal
lista.map(p => new Intl.NumberFormat("es-ES", { style: "currency", currency: "EUR" }).format(p));

// bien
const eur = new Intl.NumberFormat("es-ES", { style: "currency", currency: "EUR" });
lista.map(p => eur.format(p));

En una prueba de 50.000 iteraciones la primera versión tardó unos 1.340 ms y la segunda 24 ms. Unas 56 veces más lento por crear el objeto dentro del bucle.

Declara tus formateadores una vez, arriba del módulo, y reutilízalos.

Intl formatea, no calcula

Un último aviso

eur.format(0.1 + 0.2); // "0,30 €"

Se ve bien, pero por dentro sigue siendo 0.30000000000000004. Intl decide cómo se muestra un número, no lo arregla.

Si estás sumando importes, el problema es anterior y lo tienes en errores de coma flotante en JavaScript.

Formatea al final, en el borde de la aplicación. Nunca en medio de un cálculo.