Cómo calcular el tiempo entre dos fechas en JavaScript

Restar dos fechas da los días totales en una línea. Descomponerlos en años, meses y días es otra conversación: los meses tienen distinta longitud, el 31 de enero lleva a territorio pantanoso y los años bisiestos añaden un día que cambia el resultado.

Calendario de escritorio con varias páginas desplegadas sobre una mesa de madera, junto a un reloj analógico y una regla metálica, iluminación lateral cálida de estudio, fotografía editorial sobre fondo neutro

Parece una operación sencilla. Tienes una fecha de inicio y una fecha de fin, y quieres saber cuánto tiempo ha pasado entre las dos. En días es trivial. En años, meses y días empieza a haber trampas. No muchas, pero las que hay son las suficientes para que el resultado salga mal si no se piensan bien.

La operación fácil: días totales

La diferencia en días es directa. Un objeto Date en JavaScript es internamente un número de milisegundos desde el 1 de enero de 1970. Restar dos de ellos da milisegundos; dividir entre los milisegundos de un día da días.

function diasTotales(inicio, fin) {
	return Math.round((fin - inicio) / (1000 * 60 * 60 * 24));
}

El Math.round en lugar de Math.floor sirve para absorber el ruido de los cambios de hora por el horario de verano: en la transición de invierno a verano hay días de 23 horas, y en la inversa, de 25. Redondear da siempre el resultado calendario correcto.

Con los inputs de tipo date del HTML, el valor llega como cadena YYYY-MM-DD. Para evitar problemas de zona horaria hay que parsear en local:

const inicio = new Date(valorInput + 'T00:00:00');

Si se hace new Date('2024-01-31') sin la parte horaria, JavaScript lo interpreta como UTC y puede aparecer el día anterior dependiendo del navegador y la zona horaria del usuario.

El problema de los meses

Los meses no tienen todos los mismos días. Enero tiene 31, febrero tiene 28 o 29, y el resto oscilan entre 28 y 31. Eso hace que «un mes» no sea una unidad fija: no son siempre 30 días, no son siempre 31.

La consecuencia práctica es que «sumar un mes a una fecha» es una operación que necesita definirse con cuidado. Sumar un mes a 15 de enero da 15 de febrero, sin problema. Sumar un mes a 31 de enero da... el 31 de febrero, que no existe. La respuesta razonable es ir al último día del mes de destino: el 28 o 29 de febrero según el año.

Esa ambigüedad es exactamente la que hay que resolver para calcular correctamente años+meses+días.

El caso más traicionero: el 31 de enero

Toma este ejemplo concreto: ¿cuánto tiempo hay entre el 31 de enero de 2024 y el 1 de marzo de 2024?

La respuesta en días es 29 (2024 es bisiesto, febrero tiene 29 días). En años+meses+días, la respuesta intuitiva es 1 mes y 1 día: del 31 de enero, avanzando un mes, llegas al 29 de febrero (el 31 de febrero no existe, se captura al último día del mes); del 29 de febrero al 1 de marzo hay un día más.

El error habitual es hacer la resta de componentes ingenuamente:

// Esto está MAL para casos como el 31 de enero
let meses = fin.getMonth() - inicio.getMonth(); // 2 - 0 = 2
let dias  = fin.getDate()  - inicio.getDate();  // 1 - 31 = -30

// «Corrección» estándar: si dias < 0, resta un mes y suma días del mes anterior
if (dias < 0) {
	meses--;
	dias += new Date(fin.getFullYear(), fin.getMonth(), 0).getDate(); // +29
	// Resultado: dias = -30 + 29 = -1. Sigue siendo negativo. El algoritmo falla.
}

El problema es matemático: marzo empieza el día 1, enero el día 31. La diferencia de días es -30. Febrero tiene 29 días. Al «devolver» un mes tomando prestados 29 días, el saldo sigue siendo negativo. El algoritmo clásico no converge para este caso.

La función addMonths

La solución es no intentar reconstruir la diferencia a partir de componentes, sino hacerlo al revés: avanzar desde la fecha de inicio hasta la de fin usando meses correctamente definidos, y contar los días que sobran.

Para eso se necesita una función que sume meses capturando al último día del mes si se desborda:

function addMonths(fecha, meses) {
	const resultado = new Date(fecha);
	const diaOriginal = resultado.getDate();
	resultado.setMonth(resultado.getMonth() + meses);
	// Si setMonth() desbordó al mes siguiente (31 de feb → 2 o 3 de mar),
	// retrocede al último día del mes correcto.
	if (resultado.getDate() !== diaOriginal) {
		resultado.setDate(0);
	}
	return resultado;
}

Cuando JavaScript ejecuta setMonth(1) sobre un 31 de enero, el resultado es el 31 de febrero, que no existe. El motor avanza automáticamente al 2 o 3 de marzo dependiendo del año. La comparación getDate() !== diaOriginal detecta ese desbordamiento, y setDate(0) retrocede al último día del mes anterior, que es el último día de febrero.

La función completa

Con addMonths disponible, el algoritmo queda así:

function calcularDiferencia(inicio, fin) {
	// Garantizar que inicio <= fin
	if (inicio > fin) { const t = inicio; inicio = fin; fin = t; }

	let años  = fin.getFullYear() - inicio.getFullYear();
	let meses = fin.getMonth()    - inicio.getMonth();

	// Si el día de fin es anterior al de inicio, aún no se ha completado el mes
	if (fin.getDate() < inicio.getDate()) {
		meses--;
	}

	if (meses < 0) {
		años--;
		meses += 12;
	}

	// «Ancla»: inicio avanzado exactamente años+meses correctos
	const ancla = addMonths(
		new Date(inicio.getFullYear() + años, inicio.getMonth(), inicio.getDate()),
		meses
	);

	// Los días restantes son la diferencia exacta entre fin y el ancla
	const dias = Math.round((fin - ancla) / (1000 * 60 * 60 * 24));

	return { años, meses, dias };
}

Verificando el caso del 31 de enero → 1 de marzo de 2024:

  • años = 0, meses = 2
  • fin.getDate() (1) < inicio.getDate() (31) → meses = 1
  • ancla = addMonths(new Date(2024, 0, 31), 1) → intenta Feb 31 → desborda → retrocede a Feb 29
  • dias = (1 Mar − 29 Feb) = 1
  • Resultado: 0 años, 1 mes y 1 día

Y el caso bisiesto → bisiesto: del 29 de febrero de 2020 al 29 de febrero de 2024:

  • años = 4, meses = 0
  • fin.getDate() (29) ≥ inicio.getDate() (29) → sin ajuste
  • ancla = addMonths(new Date(2024, 1, 29), 0)29 Feb 2024
  • dias = 0
  • Resultado: 4 años exactos

Formatear el resultado en español

El castellano tiene singular y plural con nombres distintos para «mes», además del «año» que no cambia la raíz pero sí la terminación. Una función de ayuda evita los if dispersos:

function pluralizar(n, singular, plural) {
	return n + ' ' + (n === 1 ? singular : plural);
}

function formatearDiferencia(años, meses, dias) {
	const partes = [];
	if (años  > 0) partes.push(pluralizar(años,  'año',  'años'));
	if (meses > 0) partes.push(pluralizar(meses, 'mes',  'meses'));
	if (dias  > 0 || partes.length === 0) partes.push(pluralizar(dias, 'día', 'días'));

	if (partes.length === 1) return partes[0];
	if (partes.length === 2) return partes.join(' y ');
	return partes.slice(0, -1).join(', ') + ' y ' + partes[partes.length - 1];
}

La condición dias > 0 || partes.length === 0 garantiza que si todo es cero (misma fecha) se muestre «0 días» en lugar de una cadena vacía.

Integración en el formulario

Con inputs de tipo date la integración es directa. El evento change se dispara cada vez que el usuario selecciona una fecha, y basta con leer ambos valores para recalcular:

const inputInicio = document.getElementById('fecha-inicio');
const inputFin    = document.getElementById('fecha-fin');

function actualizar() {
	if (!inputInicio.value || !inputFin.value) return;

	const inicio = new Date(inputInicio.value + 'T00:00:00');
	const fin    = new Date(inputFin.value    + 'T00:00:00');

	const total = Math.round((Math.max(fin, inicio) - Math.min(fin, inicio)) / 86400000);
	const dif   = calcularDiferencia(inicio, fin);

	console.log(formatearDiferencia(dif.años, dif.meses, dif.dias));
	console.log(total + ' días en total');
}

inputInicio.addEventListener('change', actualizar);
inputFin.addEventListener('change', actualizar);

A continuación puedes probar la herramienta completa: introduce las dos fechas y obtén al instante la diferencia en ambos formatos.

Abrir en página propia

Herramienta · paigar.eu

Tiempo entre dos fechas

Introduce las dos fechas y calcula la diferencia en años, meses y días, y en número total de días.

Introduce las dos fechas para calcular
Ejemplos
1 año exacto2023 → 2024
bisiesto a bisiesto29 Feb 2020 → 2024
el 31 y el mes corto31 Ene → 1 Mar
10 años2014 → 2024
Navidad a Reyes25 Dic → 6 Ene
···
Otras entradas