learning-front

Nivel 3 · JavaScript moderno y asíncrono

Fechas e internacionalización: Date e Intl

Cómo trabaja el objeto Date (y sus trampas), y por qué Intl.DateTimeFormat e Intl.NumberFormat son la forma correcta de mostrar fechas, porcentajes y monedas al usuario: el navegador ya conoce las reglas de cada idioma.

Fechas: Date y por qué hoy se usa Intl#

JavaScript representa un instante con el objeto Date. Puedes crear uno con la fecha actual, desde un texto ISO o con números:

javascript
// el instante actual
const ahora = new Date();

// desde un texto ISO (solo-fecha: se interpreta como UTC a medianoche,
// lo que puede mostrar el día anterior en husos horarios negativos)
const desdeIso = new Date("2024-03-15");

// año, mes, día… ¡con una trampa!
const conNumeros = new Date(2024, 2, 15);

La trampa clásica de Date: cuando lo construyes con números, el mes es 0-based. Enero es 0, febrero 1, marzo 2… Por eso new Date(2024, 2, 15) es el 15 de marzo, no de febrero:

javascript
// El mes va de 0 (enero) a 11 (diciembre). El día y el año son normales.
// 2 = MARZO
const fichaje = new Date(2024, 2, 15);

// getMonth devuelve el mes 0-based: 2 = marzo
console.log(fichaje.getMonth());
// -> 2

// getFullYear devuelve el año completo
console.log(fichaje.getFullYear());
// -> 2024

La otra trampa viene de los strings ISO. new Date("2024-03-15") (solo fecha, sin hora) se interpreta como UTC a medianoche, no en la hora local. Si tu navegador está en un huso negativo (p. ej. UTC-5), ese instante cae el día 14 en hora local. Para evitarlo, construye la fecha con números: new Date(2024, 2, 15) usa medianoche local.

Para convertir un string ISO a Date de forma segura, parte la cadena:

javascript
// "2024-03-15" -> [2024, 3, 15] -> new Date con números (medianoche local)
function aFecha(iso) {
  // split("-") parte el string en un array de tres trozos
  const partes = iso.split("-").map(Number);
  // partes[1] es el mes del calendario (1-12); Date lo quiere 0-based
  return new Date(partes[0], partes[1] - 1, partes[2]);
}

Lo que no debes hacer es montar el texto de la fecha a mano (dia + "/" + mes + "/" + anio): eso ignora el idioma del usuario, los nombres de los meses y mil detalles.

Mostrar fechas con Intl.DateTimeFormat#

Para mostrar una fecha está Intl.DateTimeFormat, que la formatea según un locale (el idioma y región, como "es-ES" o "en-US"):

javascript
// fecha de fichaje
const fecha = new Date(2024, 2, 15);

// dateStyle 'long' pide la versión larga; el locale decide el idioma y el orden
console.log(new Intl.DateTimeFormat("es-ES", { dateStyle: "long" }).format(fecha));
// -> "15 de marzo de 2024"

console.log(new Intl.DateTimeFormat("en-US", { dateStyle: "long" }).format(fecha));
// -> "March 15, 2024"

El mismo formato, dos salidas distintas. El locale controla el idioma del nombre del mes, el orden de día/mes/año y el separador. Tú solo describes qué quieres (dateStyle: "long") y el navegador aplica las reglas del idioma.

Formatear números con Intl.NumberFormat#

El mismo objeto Intl tiene Intl.NumberFormat para porcentajes, monedas y decimales:

javascript
// un winrate de 0 a 1
const winrate = 0.652;

// style 'percent' multiplica por 100 y coloca el % según el locale
console.log(new Intl.NumberFormat("es-ES", { style: "percent", maximumFractionDigits: 1 }).format(winrate));
// -> "65,2 %" (coma decimal y espacio antes del %, como en español)

console.log(new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1 }).format(winrate));
// -> "65.2%" (punto decimal, sin espacio)

Y para dinero:

javascript
// style 'currency' formatea moneda; necesita la divisa
console.log(new Intl.NumberFormat("es-ES", { style: "currency", currency: "EUR" }).format(1999.9));
// -> "1999,90 €"

El navegador ya conoce dónde va el símbolo de divisa, cuántos decimales son convención, si la coma o el punto separan los decimales… Nada de eso lo decides tú.

Reutilizar los formateadores#

Crear un Intl.NumberFormat o un Intl.DateTimeFormat tiene un coste: el motor carga las reglas de internacionalización del locale. Si los creas dentro de un .map() con 50 héroes, pagas ese coste 50 veces. La solución es crearlos una sola vez y llamar a .format() en cada iteración:

javascript
// se crea UNA vez, fuera del bucle
const fmtPorcentaje = new Intl.NumberFormat("es-ES", {
  style: "percent",
  maximumFractionDigits: 1,
});

// .format() es barato: solo aplica las reglas ya cargadas
const filas = EQUIPO.map((heroe) => {
  // reutiliza el mismo formateador
  return fmtPorcentaje.format(heroe.winrate);
});

Apunte de futuro: manipular fechas con Date (sumar días, comparar) es incómodo y lleno de trampas. Hay un sustituto moderno, Temporal, que lo arregla; a mediados de 2026 ya está disponible en los navegadores modernos (Chrome, Firefox, Safari recientes), aunque si necesitas soportar versiones antiguas conviene verificar compatibilidad antes de usarlo. Por ahora lo normal en proyectos con soporte amplio es Date para guardar instantes e Intl para mostrarlos.

Pruébalo tú#

Dos locales, la misma fecha y el mismo winrate. Cambia "es-ES" por "en-US" en las llamadas a Intl.DateTimeFormat y a Intl.NumberFormat y pulsa Ejecutar (o Ctrl+Enter) para observar cómo cambia el formato sin que toques los datos.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Qué fecha representa `new Date(2024, 2, 15)`?

Tu turno#

Toma el roster en bruto y conviértelo en un informe presentable: formatea winrates y fechas como los espera un usuario español. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en cómo el nivel Excelente crea los formateadores una sola vez y parametriza el locale.

Ejercicio · en esta página

Formatea el informe del equipo según el idioma

Recibes el roster en bruto: winrates de 0 a 1 y fechas en formato ISO. Móntalo en un informe presentable formateando winrates y fechas como los espera un usuario español.

Paso 1: Que funcione

  • Muestras una fila por héroe con su nombre, rol, winrate y fecha.
  • Vale formatear el winrate a mano (concatenando '%').
  • Vale formatear la fecha a mano (recolocando los trozos del ISO).
Ver soluciones
// ════════════════════════════════════════════════════════════════════════════
// NIVEL OK — "funciona"
//
// Formatea todo A MANO: el winrate multiplicando por 100 y pegando un '%',
// la fecha recolocando los trozos del string ISO con split("-").
// El informe sale, pero...
//   - el porcentaje va en formato fijo, sin respetar el idioma: un español
//     espera "65,2 %" (coma decimal, espacio antes del %), no "65.2%".
//   - la fecha queda como "15/03/2024": funciona, pero pierde el nombre del
//     mes y el tono natural del idioma ("15 de marzo de 2024").
//   - tanto el formateo del porcentaje como el de la fecha son fragmentos
//     de código que la librería estándar ya resuelve por ti.
// ════════════════════════════════════════════════════════════════════════════

// Copia local del roster para que esta solución sea autocontenida.
const EQUIPO = [
  { nombre: "Tracer", rol: "DPS", winrate: 0.652, fichaje: "2024-03-15" },
  { nombre: "Mercy", rol: "Soporte", winrate: 0.681, fichaje: "2023-11-02" },
  { nombre: "Reinhardt", rol: "Tanque", winrate: 0.54, fichaje: "2024-01-20" },
  { nombre: "Genji", rol: "DPS", winrate: 0.477, fichaje: "2025-02-10" },
  { nombre: "Ana", rol: "Soporte", winrate: 0.633, fichaje: "2024-09-08" },
];

// Winrate (0-1) a porcentaje a mano: multiplico por 100, redondeo a un
// decimal y pego el '%'. Sale con punto decimal (formato inglés).
// 0.652 -> "65.2%"
function porcentajeAMano(winrate) {
  return (winrate * 100).toFixed(1) + "%";
}

// Fecha ISO a "DD/MM/AAAA", recolocando los trozos del string.
// Solo manipulación de texto: atado a este formato exacto, sin idioma.
function fechaAMano(iso) {
  // "2024-03-15" -> ["2024", "03", "15"]
  const partes = iso.split("-");
  // -> "15/03/2024"
  return partes[2] + "/" + partes[1] + "/" + partes[0];
}

const lineas = EQUIPO.map((heroe) => {
  return (
    heroe.nombre +
    " | " +
    heroe.rol +
    " | " +
    porcentajeAMano(heroe.winrate) +
    " | " +
    fechaAMano(heroe.fichaje)
  );
});

function mostrar(lineas) {
  console.log("=== Informe del equipo ===");
  for (var i = 0; i < lineas.length; i++) {
    console.log(lineas[i]);
  }
}

mostrar(lineas);

Por qué este nivel

  • Formatea a mano: el winrate con (w*100).toFixed(1)+'%' y la fecha recolocando los trozos del string ISO. Funciona, pero sale en formato inglés (65.2%) dentro de una app en español.
  • La fecha queda como '15/03/2024': útil, pero sin el nombre del mes ni el tono natural del idioma ('15 de marzo de 2024').
  • Es justo el trabajo que Intl ya hace por ti: código frágil para algo que la librería estándar resuelve.