1. Información general
En este artículo rápido, exploraremos múltiples posibilidades de calcular la diferencia entre dos fechas en Java.
2. Core Java
2.1. Uso de java.util.Date para encontrar la diferencia en días
Comencemos por usar las API principales de Java para hacer el cálculo y determinar el número de días entre las dos fechas:
@Test public void givenTwoDatesBeforeJava8_whenDifferentiating_thenWeGetSix() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH); Date firstDate = sdf.parse("06/24/2017"); Date secondDate = sdf.parse("06/30/2017"); long diffInMillies = Math.abs(secondDate.getTime() - firstDate.getTime()); long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS); assertEquals(6, diff); }
2.2. Uso de java.time.temporal.ChronoUnit para encontrar la diferencia
La API de tiempo en Java 8 representa una unidad de fecha y hora, por ejemplo, segundos o días, utilizando la interfaz TemporalUnit . Cada unidad proporciona una implementación para un método nombrado entre para calcular la cantidad de tiempo entre dos objetos temporales en términos de esa unidad específica .
Por ejemplo, para calcular los segundos entre dos LocalDateTime s:
@Test public void givenTwoDateTimesInJava8_whenDifferentiatingInSeconds_thenWeGetTen() { LocalDateTime now = LocalDateTime.now(); LocalDateTime tenSecondsLater = now.plusSeconds(10); long diff = ChronoUnit.SECONDS.between(now, tenSecondsLater); assertEquals(10, diff); }
ChronoUnit proporciona un conjunto de unidades de tiempo concretas mediante la implementación de la interfaz TemporalUnit . Es muy recomendable para importación estática los ChronoUnit enumeración valores para lograr una mejor legibilidad:
import static java.time.temporal.ChronoUnit.SECONDS; // omitted long diff = SECONDS.between(now, tenSecondsLater);
Además, podemos pasar dos objetos temporales compatibles al método between , incluso al ZonedDateTime .
Lo bueno de ZonedDateTime es que el cálculo funcionará incluso si están configurados en diferentes zonas horarias:
@Test public void givenTwoZonedDateTimesInJava8_whenDifferentiating_thenWeGetSix() { LocalDateTime ldt = LocalDateTime.now(); ZonedDateTime now = ldt.atZone(ZoneId.of("America/Montreal")); ZonedDateTime sixMinutesBehind = now .withZoneSameInstant(ZoneId.of("Asia/Singapore")) .minusMinutes(6); long diff = ChronoUnit.MINUTES.between(sixMinutesBehind, now); assertEquals(6, diff); }
2.3. Usando java.time.temporal.Temporal # hasta ()
Cualquier objeto Temporal , como LocalDate o ZonedDateTime, proporciona un método hasta para calcular la cantidad de tiempo hasta otro Temporal en términos de la unidad especificada:
@Test public void givenTwoDateTimesInJava8_whenDifferentiatingInSecondsUsingUntil_thenWeGetTen() { LocalDateTime now = LocalDateTime.now(); LocalDateTime tenSecondsLater = now.plusSeconds(10); long diff = now.until(tenSecondsLater, ChronoUnit.SECONDS); assertEquals(10, diff); }
El Temporal # hasta y TemporalUnit # entre dos APIs diferentes para la misma funcionalidad.
2.4. Usando java.time.Duration y java.time.Period
En Java 8, la API de tiempo introdujo dos clases nuevas: Duración y Período .
Si queremos calcular la diferencia entre dos fechas y horas en una cantidad de tiempo basada en el tiempo (horas, minutos o segundos), podemos usar la clase Duración :
@Test public void givenTwoDateTimesInJava8_whenDifferentiating_thenWeGetSix() { LocalDateTime now = LocalDateTime.now(); LocalDateTime sixMinutesBehind = now.minusMinutes(6); Duration duration = Duration.between(now, sixMinutesBehind); long diff = Math.abs(duration.toMinutes()); assertEquals(6, diff); }
Sin embargo, debemos tener cuidado con un error si intentamos usar la clase Period para representar la diferencia entre dos fechas .
Un ejemplo lo explicará rápidamente. Calculemos cuántos días hay entre dos fechas usando la clase Period :
@Test public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenWorks() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixDaysBehind = aDate.minusDays(6); Period period = Period.between(aDate, sixDaysBehind); int diff = Math.abs(period.getDays()); assertEquals(6, diff); }
Si ejecutamos la prueba anterior, pasará. Podemos pensar que la clase Period es conveniente para resolver nuestro problema. Hasta aquí todo bien.
Si de esta manera funciona con una diferencia de seis días, no dudaremos de que funcionará también durante 60 días.
A continuación, cambiemos el 6 en la prueba anterior a 60 y veamos qué sucede:
@Test public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenDoesNotWork() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixtyDaysBehind = aDate.minusDays(60); Period period = Period.between(aDate, sixtyDaysBehind); int diff = Math.abs(period.getDays()); assertEquals(60, diff); }
Ahora, si volvemos a ejecutar la prueba, veremos:
java.lang.AssertionError: Expected :60 Actual :29
¡Ups! ¿Por qué la clase Period informó la diferencia como 29 días?
Esto se debe a que la clase Period representa una cantidad de tiempo basada en fechas en el formato de " x años, y meses yz días ". Cuando llamamos a su método getDays () , devuelve solo la parte " z días ".
Therefore, the period object in the test above holds the value: “0 years, 1 month and 29 days“:
@Test public void givenTwoDatesInJava8_whenUsingPeriod_thenWeGet0Year1Month29Days() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixtyDaysBehind = aDate.minusDays(60); Period period = Period.between(aDate, sixtyDaysBehind); int years = Math.abs(period.getYears()); int months = Math.abs(period.getMonths()); int days = Math.abs(period.getDays()); assertArrayEquals(new int[] { 0, 1, 29 }, new int[] { years, months, days }); }
If we want to calculate the difference in days using Java 8's Time API, the ChronoUnit.DAYS.between() method is the most straightforward way.
3. External Libraries
3.1. JodaTime
We can also do a relatively straightforward implementation with JodaTime:
joda-time joda-time 2.9.9
You can get the latest version of Joda-time from Maven Central.
LocalDate case:
@Test public void givenTwoDatesInJodaTime_whenDifferentiating_thenWeGetSix() { org.joda.time.LocalDate now = org.joda.time.LocalDate.now(); org.joda.time.LocalDate sixDaysBehind = now.minusDays(6); long diff = Math.abs(Days.daysBetween(now, sixDaysBehind).getDays()); assertEquals(6, diff); }
Similarly, with LocalDateTime it is:
@Test public void givenTwoDateTimesInJodaTime_whenDifferentiating_thenWeGetSix() { org.joda.time.LocalDateTime now = org.joda.time.LocalDateTime.now(); org.joda.time.LocalDateTime sixMinutesBehind = now.minusMinutes(6); long diff = Math.abs(Minutes.minutesBetween(now, sixMinutesBehind).getMinutes()); assertEquals(6, diff); }
3.2. Date4J
Date4j también proporciona una implementación sencilla y directa, sin la nota de que, en este caso, debemos proporcionar explícitamente una zona horaria.
Comencemos con la dependencia de Maven:
com.darwinsys hirondelle-date4j 1.5.1
Aquí hay una prueba rápida trabajando con el DateTime estándar :
@Test public void givenTwoDatesInDate4j_whenDifferentiating_thenWeGetSix() { DateTime now = DateTime.now(TimeZone.getDefault()); DateTime sixDaysBehind = now.minusDays(6); long diff = Math.abs(now.numDaysFrom(sixDaysBehind)); assertEquals(6, diff); }
4. Conclusión
En este tutorial, ilustramos algunas formas de calcular la diferencia entre fechas (con y sin tiempo), tanto en Java simple como utilizando bibliotecas externas.
El código fuente completo del artículo está disponible en GitHub.