Sumérgete en JavaScript Temporal Duration, la API moderna para aritmética, comparación y formateo precisos de intervalos de tiempo. Aprende a gestionar periodos de tiempo con confianza y de forma global, evitando los errores comunes de los objetos Date.
JavaScript Temporal Duration: Dominando la Aritmética y el Formateo de Intervalos de Tiempo para Aplicaciones Globales
Gestionar el tiempo en el desarrollo de software es notoriamente complejo. Desde el seguimiento de cronogramas de proyectos en distintos continentes hasta la programación de videoconferencias internacionales, los matices de los intervalos de tiempo, las zonas horarias y el horario de verano pueden conducir rápidamente a errores sutiles pero críticos. Durante décadas, los desarrolladores de JavaScript han luchado con el objeto Date incorporado, una herramienta que, si bien es funcional para puntos simples de fecha y hora, se queda corta cuando se trata de aritmética precisa de intervalos de tiempo y una gestión del tiempo robusta y globalmente consciente.
Aquí entra la API Temporal de JavaScript – una propuesta innovadora diseñada para proporcionar una API moderna, robusta y fácil de usar para trabajar con fechas y horas en JavaScript. Entre sus nuevos y potentes tipos, Temporal.Duration se destaca como la solución definitiva para manejar intervalos de tiempo. Este artículo te llevará a una inmersión profunda en Temporal.Duration, explorando sus capacidades para la aritmética, la comparación y el formateo inteligente, asegurando que tus aplicaciones puedan gestionar el tiempo con precisión y claridad global.
Ya sea que estés construyendo un sistema de logística global, una plataforma de trading financiero o un planificador de eventos multizona horaria, comprender Temporal.Duration es crucial para eliminar las ambigüedades relacionadas con el tiempo y ofrecer experiencias de usuario fiables e internacionalizadas.
Las Insuficiencias del Objeto Date de JavaScript para Intervalos de Tiempo
Antes de celebrar la llegada de Temporal.Duration, es esencial comprender las limitaciones del objeto Date existente, especialmente cuando se trata de intervalos de tiempo. El objeto Date representa un punto específico en el tiempo, medido en milisegundos desde la época de Unix (1 de enero de 1970, UTC). Si bien puede usarse para realizar aritmética básica, conlleva varias fallas inherentes que lo hacen inadecuado para una gestión robusta de duraciones:
-
Mutabilidad: los objetos
Dateson mutables. Cualquier operación sobre un objetoDatecambia su estado interno, lo que puede provocar efectos secundarios inesperados y errores difíciles de rastrear, particularmente en aplicaciones complejas o entornos concurrentes.const d = new Date('2023-01-15T10:00:00Z'); const d2 = d; // d2 ahora hace referencia al mismo objeto que d d.setHours(d.getHours() + 1); console.log(d.toISOString()); // 2023-01-15T11:00:00.000Z console.log(d2.toISOString()); // 2023-01-15T11:00:00.000Z (¡d2 también cambió!) -
Falta de un Concepto de Duración: El objeto
Dateno tiene un concepto directo de "duración" o "período". Calcular la diferencia entre dos fechas resulta en un número de milisegundos, que luego debe convertirse manualmente en años, meses, días, etc. Esta conversión manual es propensa a errores, especialmente al tratar con meses de duración variable o años bisiestos.const date1 = new Date('2023-01-01T00:00:00Z'); const date2 = new Date('2023-03-01T00:00:00Z'); const diffMs = date2.getTime() - date1.getTime(); // ¿Cuántos meses es esto? ¿Y qué pasa con los años bisiestos? // (diffMs / (1000 * 60 * 60 * 24 * 30)) es, en el mejor de los casos, una aproximación. console.log(`Diferencia en milisegundos: ${diffMs}`); console.log(`Días aproximados: ${diffMs / (1000 * 60 * 60 * 24)}`); // Funciona para días, pero no es robusto para meses/años -
Ambigüedad de la Zona Horaria: los objetos
Datea menudo confunden la hora local y el UTC. Aunque internamente almacenan milisegundos UTC, sus métodos operan con frecuencia en la zona horaria local del sistema por defecto, lo que genera confusión e inconsistencias al trabajar con sistemas distribuidos o usuarios internacionales. - Desafíos del Horario de Verano (DST): Las transiciones de DST pueden hacer que los días duren 23 o 25 horas. La aritmética simple (por ejemplo, sumar 24 horas a una fecha) podría no siempre resultar en el siguiente día calendario, lo que lleva a cálculos incorrectos cuando se asume que un "día" es un período fijo de 24 horas.
Estas limitaciones han obligado históricamente a los desarrolladores a depender de bibliotecas de terceros como Moment.js o date-fns, o a escribir código personalizado complejo y propenso a errores para manejar los intervalos de tiempo correctamente. Temporal tiene como objetivo traer estas capacidades de forma nativa a JavaScript.
Introduciendo JavaScript Temporal: Un Enfoque Moderno del Tiempo
La API Temporal es un nuevo objeto global y completo en JavaScript, diseñado para ser un reemplazo moderno del obsoleto objeto Date. Sus principios fundamentales son la inmutabilidad, el manejo explícito de la zona horaria y una clara separación de responsabilidades entre diferentes conceptos de tiempo. Temporal introduce varias clases nuevas, cada una representando un aspecto distinto del tiempo:
Temporal.Instant: Un punto específico e inequívoco en el tiempo, independiente de cualquier calendario o zona horaria (similar a una marca de tiempo de Unix, pero con precisión de nanosegundos).Temporal.ZonedDateTime: Un punto específico en el tiempo en un calendario y zona horaria particulares. Esta es la representación más completa de una fecha y hora específicas para un usuario.Temporal.PlainDate: Una fecha de calendario (año, mes, día) sin hora ni zona horaria.Temporal.PlainTime: Una hora de reloj de pared (hora, minuto, segundo, etc.) sin fecha ni zona horaria.Temporal.PlainDateTime: Una fecha de calendario y una hora de reloj de pared juntas, sin zona horaria.Temporal.PlainYearMonth: Un año y mes específicos en un sistema de calendario.Temporal.PlainMonthDay: Un mes y día específicos en un sistema de calendario.Temporal.Duration: Una duración de tiempo con signo, como "5 horas y 30 minutos" o "2 días". Este es nuestro enfoque para esta guía.
Todos los objetos de Temporal son inmutables, lo que significa que operaciones como sumar o restar tiempo crean nuevos objetos en lugar de modificar los existentes, mejorando la previsibilidad y reduciendo los errores.
Entendiendo Temporal.Duration
Un Temporal.Duration representa una cantidad de tiempo. Crucialmente, es independiente de un punto de inicio o fin específico. Es simplemente "cuánto tiempo" transcurrió o transcurrirá. Puede estar compuesto por años, meses, semanas, días, horas, minutos, segundos, milisegundos, microsegundos y nanosegundos. Cada componente es un entero y puede ser positivo o negativo.
Por ejemplo, "2 horas y 30 minutos" es una duración. "El período desde el 1 de enero hasta el 1 de marzo" es una duración entre dos puntos específicos, que puede ser *representada* por un Temporal.Duration, pero la Duration en sí misma es solo el intervalo.
Creando una Duración
Hay varias formas sencillas de crear objetos Temporal.Duration:
1. Usando el Constructor
El constructor te permite especificar cada componente directamente. Ten en cuenta que los argumentos se ordenan de la unidad más grande (años) a la más pequeña (nanosegundos).
// new Temporal.Duration(años, meses, semanas, días, horas, minutos, segundos, milisegundos, microsegundos, nanosegundos)
// Una duración de 2 horas y 30 minutos
const duration1 = new Temporal.Duration(0, 0, 0, 0, 2, 30, 0, 0, 0, 0);
console.log(duration1.toString()); // P2H30M
// Una duración de 1 año, 2 meses, 3 días
const duration2 = new Temporal.Duration(1, 2, 0, 3);
console.log(duration2.toString()); // P1Y2M3D
// Una duración de -5 días
const duration3 = new Temporal.Duration(0, 0, 0, -5);
console.log(duration3.toString()); // P-5D
2. Usando Temporal.Duration.from() con un Objeto
Esta suele ser la forma más legible de crear duraciones, permitiéndote especificar solo los componentes que necesitas.
// Duración de 1.5 horas
const halfHourDuration = Temporal.Duration.from({ hours: 1, minutes: 30 });
console.log(halfHourDuration.toString()); // P1H30M
// Duración de 7 días (1 semana)
const oneWeekDuration = Temporal.Duration.from({ days: 7 });
console.log(oneWeekDuration.toString()); // P7D
// Duración con segundos fraccionarios (ej., 2.5 segundos)
const twoPointFiveSeconds = Temporal.Duration.from({ seconds: 2, milliseconds: 500 });
console.log(twoPointFiveSeconds.toString()); // PT2.5S
// Duración negativa
const negativeDuration = Temporal.Duration.from({ minutes: -45 });
console.log(negativeDuration.toString()); // PT-45M
3. Usando Temporal.Duration.from() con una Cadena ISO 8601
Temporal aprovecha el formato de duración ISO 8601, que es un estándar para representar duraciones. Esto es excelente para analizar duraciones de fuentes de datos externas.
El formato generalmente se ve como P[años]Y[meses]M[semanas]W[días]DT[horas]H[minutos]M[segundos]S. La T separa los componentes de fecha de los componentes de tiempo.
// 1 año, 2 meses, 3 días
const isoDuration1 = Temporal.Duration.from('P1Y2M3D');
console.log(isoDuration1.toString()); // P1Y2M3D
// 4 horas, 5 minutos, 6 segundos
const isoDuration2 = Temporal.Duration.from('PT4H5M6S');
console.log(isoDuration2.toString()); // PT4H5M6S
// Una duración combinada
const isoDuration3 = Temporal.Duration.from('P7DT12H30M');
console.log(isoDuration3.toString()); // P7DT12H30M
// También se admiten segundos fraccionarios
const isoDuration4 = Temporal.Duration.from('PT1.5S');
console.log(isoDuration4.toString()); // PT1.5S
Realizando Aritmética con Duraciones
El verdadero poder de Temporal.Duration brilla en sus capacidades aritméticas. Puedes sumar, restar, multiplicar y dividir duraciones, y también sumarlas/restarlas de otros tipos de fecha-hora de Temporal. Todas las operaciones devuelven nuevos objetos Temporal.Duration debido a la inmutabilidad.
Sumando Duraciones
El método add() combina dos duraciones.
const sprintDuration = Temporal.Duration.from({ weeks: 2 });
const bufferDuration = Temporal.Duration.from({ days: 3 });
const totalProjectTime = sprintDuration.add(bufferDuration);
console.log(totalProjectTime.toString()); // P2W3D (o P17D si se normaliza más tarde)
// Sumar una duración negativa es equivalente a restar
const result = Temporal.Duration.from({ hours: 5 }).add({ hours: -2 });
console.log(result.toString()); // PT3H
También puedes sumar una duración a cualquier objeto de fecha/hora de Temporal. Aquí es donde ocurre la magia, ya que Temporal maneja correctamente los cambios de zona horaria y las transiciones de DST cuando es relevante.
const projectStart = Temporal.PlainDateTime.from('2023-10-26T09:00:00');
const projectDuration = Temporal.Duration.from({ days: 10, hours: 4 });
const projectEnd = projectStart.add(projectDuration);
console.log(projectEnd.toString()); // 2023-11-05T13:00:00
// Con un ZonedDateTime, las reglas de la zona horaria se aplican correctamente
const meetingStartUTC = Temporal.ZonedDateTime.from('2024-03-09T14:00:00[UTC]');
const meetingDuration = Temporal.Duration.from({ hours: 1, minutes: 45 });
const meetingEndUTC = meetingStartUTC.add(meetingDuration);
console.log(meetingEndUTC.toString()); // 2024-03-09T15:45:00+00:00[UTC]
// Ejemplo cruzando un límite de DST (asumiendo que 'Europe/Berlin' cambia a las 03:00 el 2024-03-31)
const springForwardStart = Temporal.ZonedDateTime.from('2024-03-30T22:00:00[Europe/Berlin]');
const twentyFourHours = Temporal.Duration.from({ hours: 24 });
const nextDay = springForwardStart.add(twentyFourHours); // Suma 24 horas reales
console.log(springForwardStart.toString()); // 2024-03-30T22:00:00+01:00[Europe/Berlin]
console.log(nextDay.toString()); // 2024-03-31T23:00:00+02:00[Europe/Berlin] (La hora local saltó una hora)
Observa cómo sumar 24 horas a 2024-03-30T22:00:00 en Berlín (que es UTC+1) resulta en 2024-03-31T23:00:00 (ahora UTC+2). El reloj se adelantó una hora, por lo que la hora del reloj de pared es una hora más tarde en la misma fecha en relación con la hora del reloj de pared inicial. Esto demuestra con precisión la conciencia de Temporal sobre la zona horaria y el DST al realizar aritmética en `ZonedDateTime`.
Restando Duraciones
El método subtract() funciona de manera similar a add(), pero resta tiempo.
const deadlineDuration = Temporal.Duration.from({ days: 30 });
const gracePeriod = Temporal.Duration.from({ days: 5 });
const effectiveDeadline = deadlineDuration.subtract(gracePeriod);
console.log(effectiveDeadline.toString()); // P25D
const taskEnd = Temporal.PlainDateTime.from('2023-12-01T17:00:00');
const taskDuration = Temporal.Duration.from({ hours: 8, minutes: 30 });
const taskStart = taskEnd.subtract(taskDuration);
console.log(taskStart.toString()); // 2023-12-01T08:30:00
Multiplicando y Dividiendo Duraciones
Los métodos multiply() y divide() escalan los componentes de una duración por un factor dado. Esto es útil para escenarios como calcular el tiempo total para múltiples iteraciones de una tarea.
const trainingSession = Temporal.Duration.from({ minutes: 45 });
const weeklyTraining = trainingSession.multiply(5); // Cinco sesiones a la semana
console.log(weeklyTraining.toString()); // PT225M
const totalProjectHours = Temporal.Duration.from({ hours: 160 });
const teamMembers = 4;
const hoursPerMember = totalProjectHours.divide(teamMembers);
console.log(hoursPerMember.toString()); // PT40H
Negando Duraciones
El método negate() invierte el signo de todos los componentes de una duración. Una duración positiva se vuelve negativa, y viceversa.
const delayDuration = Temporal.Duration.from({ hours: 3 });
const advanceDuration = delayDuration.negate();
console.log(delayDuration.toString()); // PT3H
console.log(advanceDuration.toString()); // PT-3H
Valor Absoluto de Duraciones
El método abs() devuelve un nuevo Temporal.Duration con todos los componentes convertidos en positivos, dándote efectivamente la magnitud de la duración independientemente de su signo.
const negativeDelay = Temporal.Duration.from({ minutes: -60 });
const positiveDuration = negativeDelay.abs();
console.log(negativeDelay.toString()); // PT-60M
console.log(positiveDuration.toString()); // PT60M
Comparando y Normalizando Duraciones
Comparar duraciones puede ser complicado, especialmente cuando hay diferentes unidades involucradas (por ejemplo, ¿es 1 mes igual a 30 días?). Temporal proporciona herramientas tanto para la comparación como para la normalización para manejar estas complejidades.
Comparando Duraciones con compare()
El método estático Temporal.Duration.compare(duration1, duration2, options) devuelve:
-1siduration1es menor queduration20siduration1es igual aduration21siduration1es mayor queduration2
Crucialmente, al comparar duraciones que incluyen unidades de longitud variable como años, meses o semanas, a menudo necesitas proporcionar una opción relativeTo. Este parámetro es un objeto `Temporal.ZonedDateTime` o `Temporal.PlainDateTime` que proporciona contexto sobre cómo interpretar estas unidades (por ejemplo, cuántos días hay en un mes o año específico).
const oneHour = Temporal.Duration.from({ hours: 1 });
const sixtyMinutes = Temporal.Duration.from({ minutes: 60 });
console.log(Temporal.Duration.compare(oneHour, sixtyMinutes)); // 0 (Son equivalentes)
const oneMonth = Temporal.Duration.from({ months: 1 });
const thirtyDays = Temporal.Duration.from({ days: 30 });
// Sin relativeTo, las comparaciones de mes/año son difíciles
console.log(Temporal.Duration.compare(oneMonth, thirtyDays)); // 0 (Temporal hace una suposición razonable sin contexto, a menudo basada en promedios)
// Con relativeTo, la comparación es precisa basada en el calendario del contexto
const startOfJanuary = Temporal.PlainDate.from('2023-01-01');
const endOfFebruaryLeap = Temporal.PlainDate.from('2024-02-01'); // Año bisiesto
// En enero de 2023, 1 mes son 31 días
const comparisonJan = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: startOfJanuary });
console.log(`1 mes vs 30 días en Ene 2023: ${comparisonJan}`); // 1 (1 mes > 30 días)
// En febrero de 2024 (año bisiesto), 1 mes son 29 días
const comparisonFeb = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: endOfFebruaryLeap });
console.log(`1 mes vs 30 días en Feb 2024: ${comparisonFeb}`); // -1 (1 mes < 30 días)
Normalizando Duraciones con normalize() y round()
Las duraciones se pueden representar de muchas maneras (por ejemplo, 90 minutos o 1 hora y 30 minutos). La normalización y el redondeo ayudan a estandarizar estas representaciones para mayor consistencia y visualización.
normalize()
El método normalize() simplifica los componentes de la duración siempre que sea posible (por ejemplo, 60 minutos se convierte en 1 hora, 24 horas se convierte en 1 día, siempre que el contexto de `relativeTo` lo permita si hay meses/años involucrados).
const longMinutes = Temporal.Duration.from({ minutes: 90 });
console.log(longMinutes.toString()); // PT90M
console.log(longMinutes.normalize().toString()); // PT1H30M
const multipleDays = Temporal.Duration.from({ hours: 48 });
console.log(multipleDays.toString()); // PT48H
console.log(multipleDays.normalize().toString()); // P2D
round()
El método round() es más potente para transformar y redondear duraciones a unidades específicas. Toma un objeto de opciones con:
largestUnit: La unidad más grande para incluir en la salida (p. ej., 'years', 'days', 'hours').smallestUnit: La unidad más pequeña para incluir en la salida (p. ej., 'minutes', 'seconds', 'milliseconds').roundingIncrement: Un entero por el cual redondear la unidad más pequeña (p. ej., 5 para redondear al múltiplo de 5 minutos más cercano).roundingMode: Cómo manejar los empates (p. ej., 'halfExpand', 'trunc', 'ceil', 'floor').relativeTo: Requerido para redondear duraciones que contienen años, meses o semanas.
const complexDuration = Temporal.Duration.from({ hours: 2, minutes: 45, seconds: 30 });
// Redondear a la hora más cercana
const roundedToHours = complexDuration.round({ smallestUnit: 'hour' });
console.log(roundedToHours.toString()); // PT3H
// Redondear a los 30 minutos más cercanos, manteniendo las horas
const roundedTo30Minutes = complexDuration.round({
largestUnit: 'hour',
smallestUnit: 'minute',
roundingIncrement: 30
});
console.log(roundedTo30Minutes.toString()); // PT3H
const preciseDuration = Temporal.Duration.from({ minutes: 123, seconds: 45 });
// Mostrar como horas y minutos
const formattedDuration = preciseDuration.round({ largestUnit: 'hour', smallestUnit: 'minute' });
console.log(formattedDuration.toString()); // PT2H4M
// El redondeo con meses/años requiere relativeTo
const longTermDuration = Temporal.Duration.from({ months: 1, days: 10 });
const referenceDate = Temporal.PlainDate.from('2023-01-15');
// Redondear a meses, relativo a una fecha
const roundedToMonths = longTermDuration.round({ largestUnit: 'month', smallestUnit: 'month', relativeTo: referenceDate });
console.log(roundedToMonths.toString()); // P1M
Calculando Duraciones Entre Objetos de Temporal
Uno de los usos más frecuentes de las duraciones es calcular el intervalo de tiempo entre dos puntos específicos en el tiempo. Temporal proporciona los métodos until() y since() en sus objetos de fecha-hora para este propósito.
Método until()
El método until() calcula la duración desde el objeto receptor hasta el objeto del argumento. Es inclusivo del punto de inicio y exclusivo del punto final. Toma un objeto de opciones similar a round() para especificar las unidades deseadas y el comportamiento de redondeo.
const startDate = Temporal.PlainDate.from('2023-01-01');
const endDate = Temporal.PlainDate.from('2023-03-15');
// Duración en las unidades más grandes posibles (meses, luego días)
const projectLength = startDate.until(endDate);
console.log(projectLength.toString()); // P2M14D
// Duración puramente en días
const totalDays = startDate.until(endDate, { largestUnit: 'day' });
console.log(totalDays.toString()); // P73D
// Duración entre dos momentos específicos, respetando las zonas horarias
const meetingStart = Temporal.ZonedDateTime.from('2024-07-20T10:00:00[America/New_York]');
const meetingEnd = Temporal.ZonedDateTime.from('2024-07-20T11:30:00[America/New_York]');
const elapsedMeetingTime = meetingStart.until(meetingEnd, { largestUnit: 'hour', smallestUnit: 'minute' });
console.log(elapsedMeetingTime.toString()); // PT1H30M
// Duración entre zonas horarias (de NYC a Londres)
const nyStartTime = Temporal.ZonedDateTime.from('2024-08-01T09:00:00[America/New_York]');
const londonEndTime = Temporal.ZonedDateTime.from('2024-08-01T17:00:00[Europe/London]');
const travelDuration = nyStartTime.until(londonEndTime);
console.log(travelDuration.toString()); // PT13H (Tiempo transcurrido real, no la diferencia del reloj de pared)
El último ejemplo es particularmente revelador. Aunque Nueva York está 5 horas por detrás de Londres, y las horas del reloj de pared son las 9 AM y las 5 PM del mismo día, el método until() calcula correctamente el tiempo transcurrido real de 13 horas. Esto se debe a que ZonedDateTime maneja implícitamente la diferencia de zona horaria.
Método since()
El método since() es el inverso de until(). Calcula la duración desde el objeto del argumento hasta el objeto receptor, lo que resulta en una duración negativa si el argumento está en el futuro en relación con el receptor.
const currentDateTime = Temporal.ZonedDateTime.from('2024-06-15T12:00:00[Europe/Paris]');
const historicEvent = Temporal.ZonedDateTime.from('2024-01-01T00:00:00[Europe/Paris]');
const timeSinceEvent = currentDateTime.since(historicEvent, { largestUnit: 'month', smallestUnit: 'day' });
console.log(timeSinceEvent.toString()); // P5M14D
const futureDate = Temporal.PlainDate.from('2025-01-01');
const pastDate = Temporal.PlainDate.from('2024-01-01');
const durationFromFuture = pastDate.since(futureDate);
console.log(durationFromFuture.toString()); // P-1Y
Manejando Diferentes Unidades y Redondeo para Duraciones Calculadas
Al calcular duraciones, a menudo es necesario especificar `largestUnit` y `smallestUnit` para obtener una representación legible para los humanos, especialmente para la edad, el tiempo transcurrido o las cuentas regresivas.
const birthDate = Temporal.PlainDate.from('1990-07-15');
const today = Temporal.PlainDate.from('2024-06-15');
// Calcular la edad en años, meses y días
const age = birthDate.until(today, { largestUnit: 'year', smallestUnit: 'day' });
console.log(`Edad: ${age.years} años, ${age.months} meses, ${age.days} días`); // Edad: 33 años, 11 meses, 0 días
// Calcular el tiempo restante para una tarea en horas y minutos
const now = Temporal.Instant.fromEpochSeconds(Date.now() / 1000);
const deadline = Temporal.Instant.from('2024-07-01T09:00:00Z');
const timeLeft = now.until(deadline, { largestUnit: 'hour', smallestUnit: 'minute', roundingMode: 'ceil' });
console.log(`Tiempo restante: ${timeLeft.hours} horas y ${timeLeft.minutes} minutos.`); // Ejemplo: Tiempo restante: 355 horas y 38 minutos.
Formateando Duraciones para Audiencias Globales
Si bien Temporal.Duration proporciona representaciones programáticas y precisas de intervalos de tiempo, no tiene un método toLocaleString() incorporado. Esto es por diseño: las duraciones son longitudes de tiempo abstractas, y su visualización puede variar enormemente dependiendo del contexto, la configuración regional y el nivel de detalle deseado. Tú, como desarrollador, eres responsable de presentar las duraciones de una manera fácil de usar y comprensible a nivel mundial.
Representación de Cadena ISO 8601
El método toString() por defecto de un objeto Temporal.Duration devuelve su representación en cadena ISO 8601. Esto es excelente para la comunicación de máquina a máquina, la serialización y el almacenamiento, pero rara vez para la visualización directa a los usuarios finales.
const examDuration = Temporal.Duration.from({ hours: 2, minutes: 15 });
console.log(examDuration.toString()); // PT2H15M
const holidayDuration = Temporal.Duration.from({ weeks: 2, days: 3 });
console.log(holidayDuration.toString()); // P2W3D
Formateo Manual para Legibilidad e Internacionalización
Para la visualización orientada al usuario, normalmente extraerás los componentes de una duración y los formatearás usando interpolación de cadenas y la API Intl de JavaScript.
Aquí hay un ejemplo de función personalizada que formatea una duración:
function formatDurationToHumanReadable(duration, locale = 'es-ES') {
const parts = [];
// Usando Intl.NumberFormat para el formato de números según la configuración regional
const numberFormatter = new Intl.NumberFormat(locale);
if (duration.years !== 0) {
parts.push(numberFormatter.format(duration.years) + ' ' + (duration.years === 1 ? 'año' : 'años'));
}
if (duration.months !== 0) {
parts.push(numberFormatter.format(duration.months) + ' ' + (duration.months === 1 ? 'mes' : 'meses'));
}
if (duration.weeks !== 0) {
parts.push(numberFormatter.format(duration.weeks) + ' ' + (duration.weeks === 1 ? 'semana' : 'semanas'));
}
if (duration.days !== 0) {
parts.push(numberFormatter.format(duration.days) + ' ' + (duration.days === 1 ? 'día' : 'días'));
}
if (duration.hours !== 0) {
parts.push(numberFormatter.format(duration.hours) + ' ' + (duration.hours === 1 ? 'hora' : 'horas'));
}
if (duration.minutes !== 0) {
parts.push(numberFormatter.format(duration.minutes) + ' ' + (duration.minutes === 1 ? 'minuto' : 'minutos'));
}
if (duration.seconds !== 0) {
// Redondear segundos para la visualización si tienen partes fraccionarias
const roundedSeconds = numberFormatter.format(duration.seconds.toFixed(0)); // O toFixed(1) para un decimal
parts.push(roundedSeconds + ' ' + (duration.seconds === 1 ? 'segundo' : 'segundos'));
}
if (parts.length === 0) {
// Manejar casos donde la duración es cero o muy pequeña (p. ej., solo nanosegundos)
if (duration.milliseconds !== 0 || duration.microseconds !== 0 || duration.nanoseconds !== 0) {
const totalMs = duration.milliseconds + duration.microseconds / 1000 + duration.nanoseconds / 1_000_000;
return numberFormatter.format(totalMs.toFixed(2)) + ' milisegundos';
}
return '0 segundos';
}
// Unir las partes con coma y 'y' para la última parte (unión básica en español)
if (parts.length > 1) {
const lastPart = parts.pop();
return parts.join(', ') + ' y ' + lastPart;
} else {
return parts[0];
}
}
const tripDuration = Temporal.Duration.from({ days: 3, hours: 10, minutes: 45 });
console.log(formatDurationToHumanReadable(tripDuration, 'en-US')); // 3 days, 10 hours and 45 minutes
console.log(formatDurationToHumanReadable(tripDuration, 'es-ES')); // 3 días, 10 horas y 45 minutos
const meetingReminder = Temporal.Duration.from({ minutes: 5 });
console.log(formatDurationToHumanReadable(meetingReminder, 'en-GB')); // 5 minutes
const microDuration = Temporal.Duration.from({ nanoseconds: 1234567 });
console.log(formatDurationToHumanReadable(microDuration, 'es-ES')); // 1,23 milisegundos
Para pluralización más avanzada y formateo de listas localizado, podrías combinar esto con Intl.RelativeTimeFormat o bibliotecas de plantillas de cadenas más complejas. La clave es separar el cálculo de la duración (manejado por Temporal) de su presentación (manejada por tu lógica de formato y Intl).
Usando Intl.DurationFormat (Futuro/Propuesta)
Hay una propuesta de TC39 en curso para Intl.DurationFormat, que tiene como objetivo proporcionar una forma nativa y consciente de la configuración regional para formatear duraciones. Si se estandariza e implementa, simplificaría enormemente el formateo manual mostrado anteriormente, ofreciendo una solución robusta para la internacionalización directamente dentro del ecosistema de JavaScript.
Probablemente funcionaría de manera similar a otros objetos Intl:
// Esto es hipotético y se basa en el estado actual de la propuesta
// Verifica la compatibilidad del navegador antes de usar en producción
/*
const duration = Temporal.Duration.from({ days: 1, hours: 2, minutes: 30 });
const formatter = new Intl.DurationFormat('es-ES', {
style: 'long',
years: 'long',
days: 'long',
hours: 'long',
minutes: 'long',
});
console.log(formatter.format(duration)); // "1 día, 2 horas, 30 minutos"
const shortFormatter = new Intl.DurationFormat('es-ES', { style: 'short', hours: 'numeric', minutes: 'numeric' });
console.log(shortFormatter.format(duration)); // "1 d, 2 h, 30 min"
*/
Aunque aún no está disponible en todos los entornos, mantente atento a esta propuesta, ya que promete ser la solución definitiva para el formateo global de duraciones en el futuro.
Casos de Uso Prácticos y Consideraciones Globales
Temporal.Duration no es solo una mejora académica; resuelve problemas del mundo real en aplicaciones que operan a través de diferentes zonas horarias y culturas.
1. Programación y Gestión de Eventos
Para plataformas que gestionan eventos, conferencias o citas internacionales, calcular la duración de los eventos, el tiempo hasta un evento o cuánto dura un descanso es crítico. Temporal.Duration asegura que estos cálculos sean precisos independientemente de la ubicación del usuario o las reglas de su zona horaria local.
// Calcular el tiempo restante para un webinar global
const now = Temporal.ZonedDateTime.from('2024-07-25T10:00:00[Europe/London]'); // Ejemplo de hora actual
const webinarStart = Temporal.ZonedDateTime.from('2024-07-26T14:30:00[Asia/Tokyo]');
const timeUntilWebinar = now.until(webinarStart, {
largestUnit: 'hour',
smallestUnit: 'minute',
roundingMode: 'ceil' // Redondear hacia arriba para asegurar que los usuarios no se lo pierdan
});
console.log(`El webinar comienza en ${timeUntilWebinar.hours} horas y ${timeUntilWebinar.minutes} minutos.`);
// La salida será precisa basada en el tiempo real transcurrido entre estos dos ZonedDateTimes.
2. Métricas de Rendimiento y Registro (Logging)
Medir el tiempo de ejecución de operaciones, los tiempos de respuesta de una API o la duración de trabajos por lotes requiere alta precisión. Temporal.Duration, especialmente cuando se combina con Temporal.Instant (que ofrece precisión de nanosegundos), es ideal para esto.
const startTime = Temporal.Instant.now();
// Simular una operación compleja
for (let i = 0; i < 1_000_000; i++) { Math.sqrt(i); }
const endTime = Temporal.Instant.now();
const executionDuration = startTime.until(endTime);
// Formatear a segundos con milisegundos para la visualización
const formattedExecution = executionDuration.round({ smallestUnit: 'millisecond', largestUnit: 'second' });
console.log(`La operación tardó ${formattedExecution.seconds}.${String(formattedExecution.milliseconds).padStart(3, '0')} segundos.`);
3. Cálculos Financieros
En finanzas, los intervalos de tiempo precisos son primordiales para calcular intereses, plazos de préstamos o períodos de inversión. El número exacto de días, meses o años en un período debe ser preciso, especialmente cuando se trata de años bisiestos o longitudes de meses específicas.
const loanStartDate = Temporal.PlainDate.from('2023-04-01');
const loanEndDate = Temporal.PlainDate.from('2028-03-31');
const loanTerm = loanStartDate.until(loanEndDate, { largestUnit: 'year', smallestUnit: 'month' });
console.log(`Plazo del préstamo: ${loanTerm.years} años y ${loanTerm.months} meses.`); // 4 años y 11 meses
4. Desafíos de Internacionalización Revisitados
-
Zonas Horarias y DST:
Temporal.Durationen sí mismo es agnóstico a la zona horaria; representa una cantidad fija de tiempo. Sin embargo, cuando sumas o restas unaDurationa/de unZonedDateTime, Temporal aplica correctamente las reglas de la zona horaria, incluidos los cambios del Horario de Verano. Esto asegura que una "duración de 24 horas" realmente avance unZonedDateTimepor 24 horas reales, incluso si eso significa cruzar un límite de DST que hace que el día del *reloj de pared* sea más corto o más largo. Esta consistencia es una gran victoria sobre `Date`. -
Variaciones Culturales: Diferentes culturas expresan las duraciones de manera diferente. Mientras que
Temporal.Durationproporciona los componentes brutos (años, meses, días, etc.), es tu responsabilidad usar las API de `Intl` o lógica personalizada para presentarlos de una manera que resuene con la configuración regional del usuario. Por ejemplo, algunas culturas podrían expresar "1 hora y 30 minutos" como "90 minutos" o usar diferentes reglas de pluralización. -
Sistemas de Calendario: Temporal también admite diferentes sistemas de calendario (p. ej., calendarios japonés, persa, islámico). Aunque
Durationen sí es agnóstico al calendario, cuando interactúa con tipos que sí son conscientes del calendario comoPlainDateoZonedDateTime, la aritmética respeta las reglas específicas del calendario (p. ej., número de días en un mes, reglas de año bisiesto dentro de ese calendario). Esto es crucial para aplicaciones globales que podrían necesitar mostrar fechas en calendarios no gregorianos.
Estado Actual y Adopción de Temporal
A finales de 2023/principios de 2024, la API Temporal de JavaScript es una propuesta de Etapa 3 de TC39. Esto significa que la especificación es en gran medida estable y está siendo implementada y probada en varios motores de JavaScript y navegadores. Navegadores importantes como Chrome, Firefox y Safari están trabajando activamente en la implementación de Temporal, con banderas experimentales o versiones tempranas ya disponibles.
Aunque aún no está universalmente disponible sin un polyfill, su etapa avanzada indica que es muy probable que se convierta en una parte estándar de JavaScript. Los desarrolladores pueden comenzar a experimentar con Temporal hoy mismo usando polyfills o habilitando características experimentales en sus entornos de desarrollo de navegador. La adopción temprana te permite adelantarte, comprender sus beneficios e integrarlo en tus proyectos a medida que se implementa el soporte en los navegadores.
Su eventual adopción generalizada reducirá significativamente la necesidad de bibliotecas externas de fecha/hora, proporcionando una solución nativa y robusta para la gestión del tiempo en JavaScript.
Mejores Prácticas para Usar Temporal Duration
Para maximizar los beneficios de Temporal.Duration en tus aplicaciones, considera estas mejores prácticas:
-
Prefiere
Temporal.Duration.from(): Al crear duraciones a partir de componentes conocidos o cadenas ISO,Temporal.Duration.from()con un objeto literal suele ser más legible y menos propenso a errores que los argumentos posicionales del constructor. -
Usa
relativeTopara Comparaciones Ambiguas: Siempre proporciona una opciónrelativeToal comparar o redondear duraciones que contienen años, meses o semanas. Esto asegura cálculos precisos al proporcionar el contexto de calendario necesario. -
Aprovecha
until()ysince(): Para calcular el intervalo entre dos puntos específicos en el tiempo, prefiere los métodosuntil()ysince()en los objetos de fecha-hora de Temporal. Manejan las complejidades de las zonas horarias y el DST correctamente. -
Normaliza y Redondea para la Visualización: Antes de presentar duraciones a los usuarios, considera usar
normalize()y especialmenteround()para convertir la duración en las unidades más apropiadas y comprensibles (p. ej., convertir 90 minutos en "1 hora y 30 minutos"). -
Separa la Representación Interna de la Visualización: Mantén tus cálculos de duración internos precisos con
Temporal.Duration. Solo transforma y formatea para la visualización al renderizar la interfaz de usuario, usando funciones de formato personalizadas y la APIIntlpara una precisión global. - Mantente Actualizado: Sigue de cerca el progreso de la API Temporal y la compatibilidad de los navegadores. A medida que avanza hacia la estandarización completa, el ecosistema evolucionará y podrían surgir nuevas herramientas o mejores prácticas.
- Ten Cuidado con la Precisión: Aunque Temporal admite precisión de nanosegundos, usa solo la precisión que realmente necesites. Una mayor precisión a veces puede dificultar la depuración o resultar en redondeos inesperados al convertir a pantallas de menor precisión.
Conclusión
La introducción de Temporal.Duration marca un salto significativo para los desarrolladores de JavaScript que luchan con la aritmética de intervalos de tiempo. Al proporcionar una API inmutable, explícita y globalmente consciente, Temporal aborda las limitaciones de larga data del obsoleto objeto Date.
Ahora puedes realizar con confianza cálculos de tiempo complejos, medir períodos con precisión y presentar duraciones de una manera que sea tanto precisa como culturalmente apropiada para usuarios de todo el mundo. Ya sea que estés calculando la duración de un ciclo de lanzamiento de software, el tiempo restante hasta el lanzamiento de un producto global o la edad exacta de una persona, Temporal.Duration ofrece las herramientas que necesitas para construir aplicaciones robustas y fiables.
Adopta Temporal.Duration y transforma cómo gestionas el tiempo en tus proyectos de JavaScript. El futuro del manejo de fechas y horas en JavaScript está aquí, prometiendo una experiencia de desarrollo más predecible, potente y globalmente compatible.