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 internacionalización Adaptar un programa para que funcione correctamente en distintos idiomas y regiones: formatos de fecha, separadores de números, símbolos de moneda, orden de las palabras, etc. Se abrevia como i18n. 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:
Fechas y horas, en el formato de cualquier país.
"hace 3 horas", "ayer", "dentro de 2 días".
Números, monedas y porcentajes con su separador correcto.
"manzanas, plátanos y naranjas" a partir de un array.
Lo molón es que todos reciben un locale locale Un código que identifica idioma y región, como 'es-ES' (español de España) o 'ja-JP' (japonés de Japón). Le dice a Intl qué convenciones usar para formatear. 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 bundle El paquete final de JavaScript que el navegador descarga y ejecuta. Cuantas más librerías metas, más pesa y más tarda en cargar la web. . 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.
- 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
- 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:
AdvertenciaNo 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 Temporal La nueva API de fechas y horas que está llegando a JavaScript para sustituir al viejo y problemático objeto Date. Maneja zonas horarias, calendarios y aritmética de fechas de forma seria. , que precisamente viene a arreglar el desastre histórico del objetoDate.
PeligroNo parsea texto libre. Intl formatea fechas que ya tienes, no las lee.
new Date('5 de marzo de 2026')te va a seguir devolviendoInvalid 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í.
ConsejoLa próxima vez que vayas a meter una librería de formateo, prueba primero con
Intlen la consola del navegador. Abres las DevTools, escribes elnew Intl.loqueseay ves el resultado al momento. Si te vale, te acabas de ahorrar una dependencia.
EA! nos vemos en los bares! 🍻