Tema
Formato de moneda y fechas (Intl)
La API Intl viene integrada en todos los navegadores y en Node.js. Formatea monedas, números, fechas, tiempos relativos y listas según el país, sin librerías como Moment.js. $1,234.50, 18 de octubre de 2026 o hace 3 días en una línea.
Helpers listos
js
const LOCALE = 'es-MX'; // 'es-ES', 'es-AR', 'es-CO', 'en-US'...
// Los formateadores se crean una vez y se reutilizan (crearlos es costoso)
const fmtMoneda = new Intl.NumberFormat(LOCALE, { style: 'currency', currency: 'MXN' });
const fmtNumero = new Intl.NumberFormat(LOCALE);
const fmtPorcentaje = new Intl.NumberFormat(LOCALE, { style: 'percent', maximumFractionDigits: 1 });
const fmtCompacto = new Intl.NumberFormat(LOCALE, { notation: 'compact', maximumFractionDigits: 1 });
const fmtFecha = new Intl.DateTimeFormat(LOCALE, { dateStyle: 'long' });
const fmtFechaCorta = new Intl.DateTimeFormat(LOCALE, { day: '2-digit', month: '2-digit', year: 'numeric' });
const fmtFechaHora = new Intl.DateTimeFormat(LOCALE, { dateStyle: 'medium', timeStyle: 'short' });
const fmtHora = new Intl.DateTimeFormat(LOCALE, { hour: 'numeric', minute: '2-digit' });
const fmtRelativo = new Intl.RelativeTimeFormat(LOCALE, { numeric: 'auto' });
const fmtLista = new Intl.ListFormat(LOCALE, { style: 'long', type: 'conjunction' });
/** 1234.5 → "$1,234.50" */
export const moneda = (n, divisa) =>
divisa ? new Intl.NumberFormat(LOCALE, { style: 'currency', currency: divisa }).format(n) : fmtMoneda.format(n);
/** 1234567.891 → "1,234,567.891" */
export const numero = (n) => fmtNumero.format(n);
/** 0.256 → "25.6 %" */
export const porcentaje = (n) => fmtPorcentaje.format(n);
/** 1520000 → "1.5 M" (ideal para KPIs y contadores) */
export const compacto = (n) => fmtCompacto.format(n);
/** Acepta Date, timestamp o texto ISO */
const aFecha = (f) => (f instanceof Date ? f : new Date(f));
/** "18 de octubre de 2026" */
export const fecha = (f) => fmtFecha.format(aFecha(f));
/** "18/10/2026" */
export const fechaCorta = (f) => fmtFechaCorta.format(aFecha(f));
/** "18 oct 2026, 4:30 p.m." */
export const fechaHora = (f) => fmtFechaHora.format(aFecha(f));
/** "4:30 p.m." */
export const hora = (f) => fmtHora.format(aFecha(f));
/** "hace 5 minutos", "ayer", "dentro de 3 días" */
export function tiempoRelativo(f) {
const segundos = Math.round((aFecha(f) - Date.now()) / 1000);
const unidades = [
['year', 31536000], ['month', 2592000], ['week', 604800],
['day', 86400], ['hour', 3600], ['minute', 60], ['second', 1],
];
for (const [unidad, s] of unidades) {
if (Math.abs(segundos) >= s || unidad === 'second') {
return fmtRelativo.format(Math.round(segundos / s), unidad);
}
}
}
/** ["HTML", "CSS", "JS"] → "HTML, CSS y JS" */
export const lista = (arr) => fmtLista.format(arr);
/** Convierte texto con formato de moneda a número: "$1,234.50" → 1234.5 */
export const parsearMoneda = (txt) => Number(String(txt).replace(/[^0-9.-]/g, '')) || 0;Resultados
js
import * as f from './formato.js';
f.moneda(1234.5); // "$1,234.50"
f.moneda(99, 'USD'); // "USD 99.00"
f.moneda(1500, 'EUR'); // "EUR 1,500.00"
f.numero(1234567.891); // "1,234,567.891"
f.porcentaje(0.256); // "25.6 %"
f.compacto(1520000); // "1.5 M"
f.fecha('2026-10-18'); // "18 de octubre de 2026" (ver advertencia de zona horaria)
f.fechaCorta(new Date()); // "18/10/2026"
f.fechaHora(Date.now()); // "18 oct 2026, 4:30 p.m."
f.tiempoRelativo(Date.now() - 5 * 60 * 1000); // "hace 5 minutos"
f.tiempoRelativo(Date.now() - 86400000); // "ayer"
f.tiempoRelativo(Date.now() + 3 * 86400000); // "dentro de 3 días"
f.lista(['HTML', 'CSS', 'JavaScript']); // "HTML, CSS y JavaScript"Los resultados exactos varían
Cada navegador puede usar espacios especiales o abreviaturas ligeramente distintas (p.m. vs p. m.). No compares el texto formateado en pruebas automáticas; compara los números.
Uso típico en una tienda
js
import { moneda, tiempoRelativo } from './formato.js';
// Formatea todos los precios de la página: <span data-precio="1299">1299</span>
document.querySelectorAll('[data-precio]').forEach((el) => {
el.textContent = moneda(Number(el.dataset.precio));
});
// "Publicado hace 2 horas" que se actualiza solo cada minuto
function actualizarTiempos() {
document.querySelectorAll('time[datetime]').forEach((el) => {
el.textContent = tiempoRelativo(el.getAttribute('datetime'));
el.title = new Date(el.getAttribute('datetime')).toLocaleString('es-MX');
});
}
actualizarTiempos();
setInterval(actualizarTiempos, 60_000);Otras utilidades de Intl
js
// Nombres de meses y días para un calendario
const meses = Array.from({ length: 12 }, (_, i) =>
new Intl.DateTimeFormat('es-MX', { month: 'long' }).format(new Date(2026, i, 1)));
// ["enero", "febrero", ...]
const dias = Array.from({ length: 7 }, (_, i) =>
new Intl.DateTimeFormat('es-MX', { weekday: 'short' }).format(new Date(2026, 9, 18 + i)));
// ["dom", "lun", "mar", ...]
// Ordenar alfabéticamente respetando acentos y la ñ
['Ñandú', 'Árbol', 'nube', 'zorro', 'avión'].sort(new Intl.Collator('es').compare);
// ["Árbol", "avión", "nube", "Ñandú", "zorro"]
// Plurales correctos
const pr = new Intl.PluralRules('es-MX');
const texto = (n) => `${n} ${pr.select(n) === 'one' ? 'producto' : 'productos'}`;
texto(1); // "1 producto"
texto(3); // "3 productos"
// Fecha en otra zona horaria
new Intl.DateTimeFormat('es-MX', { timeStyle: 'short', timeZone: 'America/Cancun' }).format(new Date());Error común: la fecha sale un día antes
new Date('2026-10-18') se interpreta como medianoche UTC; en México (UTC-6) eso es el 17 a las 6 p.m., así que verás "17 de octubre". Para fechas sin hora usa new Date('2026-10-18T00:00:00') (hora local) o añade timeZone: 'UTC' al formateador.
Dinero: calcula en centavos
0.1 + 0.2 da 0.30000000000000004 en JavaScript. Para sumar precios en un carrito, trabaja en centavos enteros (12950 en lugar de 129.50) y divide entre 100 sólo al mostrar.