🚨 ¡Nueva review! ✨ Mi ratón favorito para programar: el Logitech MX Master 3S . ¡Échale un ojo! 👀

Antes de instalar otra librería de fechas, mira lo que ya hace el navegador

La API Intl formatea fechas, monedas, tiempo relativo y listas sin un solo npm install.

Escrito por domin el 25 de junio de 2026

El problema de siempre con las fechas

Seguro que te ha pasado que necesitas mostrar una fecha bonita, 12 de mayo de 2026 en vez del churro que escupe new Date(). ¿Qué haces? npm install moment. ¿Ahora toca un hace 3 días? Pues date-fns. ¿Que hay que poner el separador de miles en un precio? numeral.js.

Y sin darte cuenta, tu proyecto arrastra tres librerías de formateo que pesan lo suyo. moment.js, por ejemplo, ronda los 70 KB comprimidos porque mete todos los idiomas por defecto. Setenta kilobytes para enseñar una fecha.

Lo que yo no sabía es que el navegador ya trae todo eso de fábrica. Se llama Intl, lleva años en todos los navegadores modernos y en Node, y no pesa nada porque ya está ahí. Te lo cuento con dibujitos, que es como yo lo entiendo mejor.


Qué es Intl

Intl es el motor de internacionalización que viene integrado en JavaScript, así que al venir integrado lo tendremos sin dependencias y sin npm install. Es un conjunto de formateadores, cada uno especializado en una cosa:

📅 Intl.DateTimeFormat

Fechas y horas, en el formato de cualquier país.

⏳ Intl.RelativeTimeFormat

"hace 3 horas", "ayer", "dentro de 2 días".

💶 Intl.NumberFormat

Números, monedas y porcentajes con su separador correcto.

📝 Intl.ListFormat

"manzanas, plátanos y naranjas" a partir de un array.

Lo molón es que todos reciben un locale como primer argumento ('es-ES', 'en-US', 'ja-JP'…) y se adaptan solos a las convenciones de esa región. Sin descargar paquetes de idiomas. Vamos a verlos.


bye moment.js

// Con moment.js (~70 KB comprimidos, todos los idiomas incluidos)
import moment from 'moment';
moment(new Date()).format('D [de] MMMM [de] YYYY'); // "12 de mayo de 2026"

// Con Intl, sin instalar nada
new Intl.DateTimeFormat('es-ES', {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
}).format(new Date()); // "12 de mayo de 2026"

Mismo resultado pero con cero peso en el bundle . Y si cambias el idioma, cambia el formato sin tocar nada más:

new Intl.DateTimeFormat('ja-JP', {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
}).format(new Date()); // "2026年5月12日"

Si solo quieres algo rápido sin configurar campo por campo, tienes los atajos dateStyle y timeStyle:

new Intl.DateTimeFormat('es-ES', { dateStyle: 'full' }).format(new Date());
// "martes, 12 de mayo de 2026"

new Intl.DateTimeFormat('es-ES', { dateStyle: 'short', timeStyle: 'short' }).format(new Date());
// "12/5/26, 14:30"

Tiempo relativo: el “hace 3 días”, sin librería

Esto es lo de publicado hace 3 días o vence en 2 horas. Lo hace Intl.RelativeTimeFormat:

const rtf = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
rtf.format(-3, 'day'); // "hace 3 días"
rtf.format(2, 'hour'); // "dentro de 2 horas"
rtf.format(-1, 'day'); // "ayer"
rtf.format(1, 'day'); // "mañana"

El truco está en numeric: 'auto': es lo que hace que salga ayer en lugar del más robótico hace 1 día.

Lo único que no te da hecho es calcular la diferencia entre dos fechas, eso lo pones tú. Pero son cuatro líneas más:

function tiempoRelativo(fecha) {
    const rtf = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
    const segundos = Math.floor((fecha - new Date()) / 1000);

    if (Math.abs(segundos) < 60) return rtf.format(Math.round(segundos), 'second');
    if (Math.abs(segundos) < 3600) return rtf.format(Math.round(segundos / 60), 'minute');
    if (Math.abs(segundos) < 86400) return rtf.format(Math.round(segundos / 3600), 'hour');
    return rtf.format(Math.round(segundos / 86400), 'day');
}

tiempoRelativo(new Date(Date.now() - 5 * 60 * 1000)); // "hace 5 minutos"

Monedas y números: adiós a numeral.js

// Con numeral.js (~7 KB)
numeral(1500000).format('$0,0.00'); // "$1,500,000.00"

// Con Intl
new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
}).format(1500000); // "$1,500,000.00"

Y aquí es donde se nota de verdad lo de la localización. El mismo número, en formato español:

new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency: 'EUR',
}).format(1500000); // "1.500.000,00 €"

Fíjate en el punto como separador de miles, la coma para los decimales y el símbolo del euro detrás. En España es así, y en Alemania, y en Estados Unidos cada cosa va en su sitio. Todo eso, que es un infierno de hacer a mano, fácil con esta vaina.

Un extra que yo no conocía: la notación compacta, ideal para dashboards y métricas.

new Intl.NumberFormat('es-ES', { notation: 'compact' }).format(1500000);
// "1,5 M"

new Intl.NumberFormat('es-ES', { style: 'percent' }).format(0.847);
// "85 %"

ListFormat y PluralRules

Intl.ListFormat convierte un array en una frase de verdad, con su “y” o su “o” en el sitio correcto:

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

const o = new Intl.ListFormat('es', { type: 'disjunction' });
o.format(['email', 'teléfono', 'Slack']); // "email, teléfono o Slack"

Parece una tontería hasta que te toca hacerlo a mano y te das cuenta de que el último elemento no lleva coma, lleva “y”, salvo cuando hay solo dos, y entonces… ya sabes. Esto lo resuelve y encima respeta las reglas de cada idioma.

Intl.PluralRules es el otro. Resuelve el clásico 1 artículo frente a 5 artículos sin llenar el código de condiciones:

const pr = new Intl.PluralRules('es');

function contar(n) {
    return pr.select(n) === 'one' ? `${n} artículo` : `${n} artículos`;
}

contar(1); // "1 artículo"
contar(5); // "5 artículos"

En español parece fácil (singular o plural e ya), pero hay idiomas con seis formas distintas de plural según la cantidad. PluralRules se sabe todas. Si tu app va a hablar varios idiomas, esto podría ser útil.


Reutilizar los formateadores

Crear un objeto Intl tiene un coste, internamente carga las reglas del idioma. Si lo haces dentro de un bucle o en cada render de una lista, lo estás creando mil veces para nada.

Hazlo así
  • Crea el formateador UNA vez, fuera del bucle
  • Reutiliza la misma instancia para todos los elementos
  • Guárdalo en una constante a nivel de módulo si lo usas en varios sitios
Evita esto
  • Crear un new Intl.NumberFormat() dentro del .map() de una lista
  • Instanciar el formateador en cada render del componente
  • Construir uno nuevo por cada fecha que vas a mostrar
// Mal: crea un formateador por cada fila
filas.map((f) => new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' }).format(f.precio));

// Bien: uno y a reutilizar
const euro = new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' });
filas.map((f) => euro.format(f.precio));

En una tabla de pocas filas ni lo notas, pero en listas grandes o renders frecuentes la diferencia es considerable.


Lo que Intl NO hace

No es magia y no sustituye a todo. Hay dos cosas que se le escapan:

Advertencia

No hace aritmética de fechas. Si necesitas súmale 3 meses a esta fecha o ¿cuántos días laborables hay entre estas dos?, Intl no es lo tuyo. Para eso siguen estando date-fns, o la nueva API nativa Temporal , que precisamente viene a arreglar el desastre histórico del objeto Date.

Peligro

No parsea texto libre. Intl formatea fechas que ya tienes, no las lee. new Date('5 de marzo de 2026') te va a seguir devolviendo Invalid Date. Convertir texto en fecha es otro problema distinto.

Para el 80% de los casos de una interfaz normal como por ejemplo mostrar fechas, precios, contadores, tiempos relativos, Intl te sobra. Si construyes un calendario serio o lógica de horarios compleja, mantén tus librerías.


Resumiendo

La plataforma ha mejorado mucho y arrastramos costumbres de cuando no era así. Instalar una librería de 70 KB para formatear una fecha tenía sentido en 2015. Hoy, antes de hacer npm install, merece la pena preguntarse si el navegador ya lo resuelve, y muchas veces será así.

Consejo

La próxima vez que vayas a meter una librería de formateo, prueba primero con Intl en la consola del navegador. Abres las DevTools, escribes el new Intl.loquesea y ves el resultado al momento. Si te vale, te acabas de ahorrar una dependencia.

EA! nos vemos en los bares! 🍻