Cómo formatear rangos de fechas como 1 ene - 5 ene
Usa JavaScript para mostrar rangos de fechas con formato apropiado según la configuración regional y eliminación inteligente de redundancias
Introducción
Los rangos de fechas aparecen en toda clase de aplicaciones web. Los sistemas de reservas muestran disponibilidad desde el 1 de enero hasta el 5 de enero, los calendarios de eventos muestran conferencias de varios días, los paneles de análisis muestran datos para períodos de tiempo específicos y los informes cubren trimestres fiscales o intervalos de fechas personalizados. Estos rangos comunican que algo se aplica a todas las fechas entre dos puntos extremos.
Cuando formateas rangos de fechas manualmente concatenando dos fechas formateadas con un guion, creas una salida innecesariamente verbosa. Un rango del 1 de enero de 2024 al 5 de enero de 2024 no necesita repetir el mes y el año en la segunda fecha. La salida "1 - 5 de enero de 2024" transmite la misma información de manera más concisa al omitir componentes redundantes.
JavaScript proporciona el método formatRange() en Intl.DateTimeFormat para manejar el formateo de rangos de fechas automáticamente. Este método determina qué componentes de fecha incluir en cada parte del rango, aplicando convenciones específicas del idioma para separadores y formateo, mientras elimina repeticiones innecesarias.
Por qué los rangos de fechas necesitan un formateo inteligente
Diferentes rangos de fechas requieren diferentes niveles de detalle. Un rango dentro del mismo mes necesita menos información que un rango que abarca varios años. El formato óptimo depende de qué componentes de fecha difieren entre las fechas de inicio y fin.
Para un rango dentro del mismo día, solo necesitas mostrar la diferencia de tiempo: "10:00 AM - 2:00 PM". Repetir la fecha completa para ambas horas no añade información.
Para un rango dentro del mismo mes, muestras el mes una vez y enumeras ambos números de día: "1-5 de enero de 2024". Incluir "enero" dos veces hace que la salida sea más difícil de leer sin añadir claridad.
Para un rango que abarca diferentes meses en el mismo año, muestras ambos meses pero puedes omitir el año de la primera fecha: "25 de diciembre de 2024 - 2 de enero de 2025" necesita la información completa, pero "15 de enero - 20 de febrero de 2024" puede omitir el año de la primera fecha en algunos idiomas.
Para rangos que abarcan varios años, debes incluir el año para ambas fechas: "1 de diciembre de 2023 - 15 de marzo de 2024".
La implementación manual de estas reglas requiere verificar qué componentes de fecha difieren y construir cadenas de formato en consecuencia. Diferentes idiomas también aplican estas reglas de manera diferente, utilizando diferentes separadores y convenciones de ordenamiento. El método formatRange() encapsula esta lógica.
Uso de formatRange para formatear rangos de fechas
El método formatRange() acepta dos objetos Date y devuelve una cadena formateada. Crea una instancia de Intl.DateTimeFormat con la configuración regional y las opciones deseadas, luego llama a formatRange() con las fechas de inicio y fin.
const formatter = new Intl.DateTimeFormat("en-US");
const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);
console.log(formatter.formatRange(start, end));
// Resultado: "1/1/24 – 1/5/24"
El formateador aplica el formato de fecha predeterminado del inglés estadounidense a ambas fechas y las conecta con un guion medio. Para fechas dentro del mismo mes, la salida sigue mostrando ambas fechas completas porque el formato predeterminado es bastante corto.
Puedes especificar qué componentes de fecha incluir utilizando las mismas opciones disponibles para el formateo regular de fechas.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);
console.log(formatter.formatRange(start, end));
// Resultado: "January 1 – 5, 2024"
Ahora el formateador omite inteligentemente el mes y el año de la segunda fecha porque coinciden con la primera fecha. La salida muestra solo el número del día para la fecha final, haciendo que el rango sea más legible.
Cómo formatRange optimiza la salida de rangos de fechas
El método formatRange() examina ambas fechas y determina qué componentes difieren. Incluye solo los componentes necesarios en cada parte del rango.
Para fechas en el mismo mes y año, solo aparece el día final en la segunda parte.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(formatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Resultado: "January 1 – 5, 2024"
console.log(formatter.formatRange(
new Date(2024, 0, 15),
new Date(2024, 0, 20)
));
// Resultado: "January 15 – 20, 2024"
Ambos rangos muestran el mes y el año una sola vez, con solo los números de día conectados por el separador de rango.
Para fechas en diferentes meses del mismo año, aparecen ambos meses pero el año aparece solo una vez.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(formatter.formatRange(
new Date(2024, 0, 15),
new Date(2024, 1, 20)
));
// Resultado: "January 15 – February 20, 2024"
El formateador incluye ambos nombres de mes pero coloca el año al final, aplicándolo a todo el rango.
Para fechas que abarcan diferentes años, aparecen ambas fechas completas.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(formatter.formatRange(
new Date(2023, 11, 25),
new Date(2024, 0, 5)
));
// Resultado: "December 25, 2023 – January 5, 2024"
Cada fecha incluye su año completo porque los años difieren. El formateador no puede omitir ningún año sin crear ambigüedad.
Formateo de rangos de fecha y hora
Cuando tu formato incluye componentes de tiempo, el método formatRange() aplica la misma omisión inteligente a los campos de tiempo.
Para horas en el mismo día, solo los componentes de tiempo difieren en el resultado.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 14, 30);
console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024, 10:00 AM – 2:30 PM"
La fecha aparece una vez, seguida del rango de tiempo. El formateador reconoce que repetir la fecha y hora completas para el final no añadiría información útil.
Para horas en diferentes días, aparecen ambos valores completos de fecha y hora.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric"
});
const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 2, 14, 30);
console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024, 10:00 AM – January 2, 2024, 2:30 PM"
Ambas fechas y horas aparecen porque representan días diferentes. El formateador no puede omitir con seguridad ningún componente.
Formateo de rangos de fecha en diferentes locales
El formateo de rangos se adapta a las convenciones de cada locale para el orden de los componentes de fecha, separadores y qué componentes omitir.
const enFormatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(enFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"
const deFormatter = new Intl.DateTimeFormat("de-DE", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(deFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "1.–5. Januar 2024"
const jaFormatter = new Intl.DateTimeFormat("ja-JP", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(jaFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "2024年1月1日~5日"
El inglés coloca el mes primero y muestra el año al final. El alemán coloca los números de día primero con puntos, luego el nombre del mes, luego el año. El japonés usa el orden año-mes-día y el guion ondulado (~) como separador de rango. Cada locale aplica sus propias convenciones sobre qué componentes mostrar una vez frente a dos veces.
Estas diferencias se extienden a rangos entre meses.
const enFormatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(enFormatter.formatRange(
new Date(2024, 0, 15),
new Date(2024, 1, 20)
));
// Output: "January 15 – February 20, 2024"
const deFormatter = new Intl.DateTimeFormat("de-DE", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(deFormatter.formatRange(
new Date(2024, 0, 15),
new Date(2024, 1, 20)
));
// Output: "15. Januar – 20. Februar 2024"
const frFormatter = new Intl.DateTimeFormat("fr-FR", {
year: "numeric",
month: "long",
day: "numeric"
});
console.log(frFormatter.formatRange(
new Date(2024, 0, 15),
new Date(2024, 1, 20)
));
// Output: "15 janvier – 20 février 2024"
Los tres locales muestran ambos nombres de mes pero los posicionan de manera diferente en relación con los números de día. Los formateadores manejan estas variaciones automáticamente.
Formateo de rangos de fechas con diferentes estilos
Puedes usar la opción dateStyle para controlar la longitud general del formato, al igual que con el formateo de fechas individuales.
const shortFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "short"
});
console.log(shortFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "1/1/24 – 1/5/24"
const mediumFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "medium"
});
console.log(mediumFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "Jan 1 – 5, 2024"
const longFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "long"
});
console.log(longFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"
const fullFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "full"
});
console.log(fullFormatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
));
// Output: "Monday, January 1 – Friday, January 5, 2024"
El estilo short produce fechas numéricas y no aplica omisión inteligente porque el formato ya es compacto. Los estilos medium y long abrevian o escriben completamente el mes y omiten componentes redundantes. El estilo full incluye nombres de días de la semana para ambas fechas.
Uso de formatRangeToParts para estilización personalizada
El método formatRangeToParts() devuelve un array de objetos que representan los componentes del rango formateado. Esto permite estilizar o manipular partes individuales de la salida del rango.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const parts = formatter.formatRangeToParts(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
);
console.log(parts);
La salida es un array de objetos, cada uno con propiedades type, value y source.
[
{ type: "month", value: "January", source: "startRange" },
{ type: "literal", value: " ", source: "startRange" },
{ type: "day", value: "1", source: "startRange" },
{ type: "literal", value: " – ", source: "shared" },
{ type: "day", value: "5", source: "endRange" },
{ type: "literal", value: ", ", source: "shared" },
{ type: "year", value: "2024", source: "shared" }
]
La propiedad type identifica el componente: mes, día, año o texto literal. La propiedad value contiene el texto formateado. La propiedad source indica si el componente pertenece a la fecha de inicio, fecha de fin, o es compartido entre ambas.
Puedes usar estas partes para crear HTML personalizado con estilos para diferentes componentes.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const parts = formatter.formatRangeToParts(
new Date(2024, 0, 1),
new Date(2024, 0, 5)
);
let html = "";
parts.forEach(part => {
if (part.type === "month") {
html += `<span class="month">${part.value}</span>`;
} else if (part.type === "day") {
html += `<span class="day">${part.value}</span>`;
} else if (part.type === "year") {
html += `<span class="year">${part.value}</span>`;
} else if (part.type === "literal" && part.source === "shared" && part.value.includes("–")) {
html += `<span class="separator">${part.value}</span>`;
} else {
html += part.value;
}
});
console.log(html);
// Output: <span class="month">January</span> <span class="day">1</span><span class="separator"> – </span><span class="day">5</span>, <span class="year">2024</span>
Esta técnica preserva el formateo específico del idioma mientras permite la estilización visual personalizada.
Qué sucede cuando las fechas son iguales
Cuando pasas la misma fecha para ambos parámetros de inicio y fin, formatRange() muestra una única fecha formateada en lugar de un rango.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const date = new Date(2024, 0, 1);
console.log(formatter.formatRange(date, date));
// Output: "January 1, 2024"
El formateador reconoce que un rango con puntos extremos idénticos no es realmente un rango y lo formatea como una fecha única. Este comportamiento se aplica incluso cuando los objetos Date son instancias diferentes con el mismo valor.
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 10, 0);
console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024"
Aunque estos son objetos Date separados, representan la misma fecha y hora. El formateador muestra una única fecha porque el rango tiene duración cero en el nivel de precisión de las opciones de formato. Como el formato no incluye componentes de tiempo, las horas son irrelevantes y las fechas se consideran iguales.
Cuándo usar formatRange vs formateo manual
Utiliza formatRange() cuando muestres rangos de fechas a los usuarios. Esto aplica a rangos de reservas, duración de eventos, períodos de informes, ventanas de disponibilidad o cualquier otro intervalo de tiempo. El método asegura un formateo correcto específico del idioma y una omisión óptima de componentes.
Evita formatRange() cuando necesites mostrar múltiples fechas no relacionadas. Una lista de plazos como "1 de enero, 15 de enero, 1 de febrero" debería usar llamadas regulares a format() para cada fecha en lugar de tratarlas como rangos.
También evita formatRange() cuando muestres comparaciones o diferencias entre fechas. Si estás mostrando cuánto antes o después es una fecha comparada con otra, eso representa un cálculo de tiempo relativo en lugar de un rango.