Java 8: calcula la diferencia entre dos LocalDateTime

Estoy tratando de calcular la diferencia entre dos LocalDateTime .

La salida debe tener el formato y years m months d days h hours m minutes s seconds . Esto es lo que he escrito:

 import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.Period; import java.time.ZoneId; public class Main { static final int MINUTES_PER_HOUR = 60; static final int SECONDS_PER_MINUTE = 60; static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; public static void main(String[] args) { LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 9, 19, 46, 45); LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55); Period period = getPeriod(fromDateTime, toDateTime); long time[] = getTime(fromDateTime, toDateTime); System.out.println(period.getYears() + " years " + period.getMonths() + " months " + period.getDays() + " days " + time[0] + " hours " + time[1] + " minutes " + time[2] + " seconds."); } private static Period getPeriod(LocalDateTime dob, LocalDateTime now) { return Period.between(dob.toLocalDate(), now.toLocalDate()); } private static long[] getTime(LocalDateTime dob, LocalDateTime now) { LocalDateTime today = LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), dob.getHour(), dob.getMinute(), dob.getSecond()); Duration duration = Duration.between(today, now); long seconds = duration.getSeconds(); long hours = seconds / SECONDS_PER_HOUR; long minutes = ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE); long secs = (seconds % SECONDS_PER_MINUTE); return new long[]{hours, minutes, secs}; } } 

El resultado que obtengo es de 29 years 8 months 24 days 12 hours 0 minutes 50 seconds . He verificado el resultado de este sitio web (con valores 12/16/1984 07:45:55 y 09/09/2014 19:46:45 ). La siguiente captura de pantalla muestra el resultado:

Epoch Converter

Estoy bastante seguro de que los campos después del valor del mes están saliendo mal de mi código. Cualquier sugerencia sería muy útil.

Actualizar

He probado el resultado de otro sitio web y el resultado que obtuve es diferente. Aquí está: Calcule la duración entre dos fechas (resultado: 29 años, 8 meses, 24 días, 12 horas, 0 minutos y 50 segundos).

Actualizar

Como obtuve dos resultados diferentes de dos sitios diferentes, me pregunto si el algoritmo de mi cálculo es legítimo o no. Si utilizo los siguientes dos objetos LocalDateTime :

 LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 40, 45); LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55); 

Entonces la salida está llegando: 29 years 8 months 25 days -1 hours -5 minutes -10 seconds.

Desde este enlace debe ser de 29 years 8 months 24 days 22 hours, 54 minutes and 50 seconds . Entonces el algoritmo necesita manejar los números negativos también.

Tenga en cuenta que la pregunta no es sobre qué sitio me dio el resultado, necesito saber el algoritmo correcto y necesito tener los resultados correctos.

Lamentablemente, no parece haber una clase de período que abarque también el tiempo, por lo que es posible que tenga que hacer los cálculos usted mismo.

Afortunadamente, las clases de fecha y hora tienen muchos métodos de utilidad que simplifican eso hasta cierto punto. Esta es una forma de calcular la diferencia, aunque no necesariamente la más rápida:

 LocalDateTime fromDateTime = LocalDateTime.of(1984, 12, 16, 7, 45, 55); LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 40, 45); LocalDateTime tempDateTime = LocalDateTime.from( fromDateTime ); long years = tempDateTime.until( toDateTime, ChronoUnit.YEARS); tempDateTime = tempDateTime.plusYears( years ); long months = tempDateTime.until( toDateTime, ChronoUnit.MONTHS); tempDateTime = tempDateTime.plusMonths( months ); long days = tempDateTime.until( toDateTime, ChronoUnit.DAYS); tempDateTime = tempDateTime.plusDays( days ); long hours = tempDateTime.until( toDateTime, ChronoUnit.HOURS); tempDateTime = tempDateTime.plusHours( hours ); long minutes = tempDateTime.until( toDateTime, ChronoUnit.MINUTES); tempDateTime = tempDateTime.plusMinutes( minutes ); long seconds = tempDateTime.until( toDateTime, ChronoUnit.SECONDS); System.out.println( years + " years " + months + " months " + days + " days " + hours + " hours " + minutes + " minutes " + seconds + " seconds."); //prints: 29 years 8 months 24 days 22 hours 54 minutes 50 seconds. 

La idea básica es esta: crear una fecha de inicio temporal y obtener los años completos hasta el final. Luego ajuste esa fecha por el número de años para que la fecha de inicio sea inferior a un año desde el final. Repita eso para cada unidad de tiempo en orden descendente.

Finalmente un descargo de responsabilidad: no tomé en cuenta diferentes zonas horarias (ambas fechas deben estar en la misma zona horaria) y tampoco probé / verificaba cómo el horario de verano u otros cambios en un calendario (como la zona horaria cambia en Samoa) afecta este cálculo Entonces use con cuidado.

Encontré que la mejor manera de hacerlo es con ChronoUnit.

 long minutes = ChronoUnit.MINUTES.between(fromDate, toDate); long hours = ChronoUnit.HOURS.between(fromDate, toDate); 

La documentación adicional está aquí: https://docs.oracle.com/javase/tutorial/datetime/iso/period.html

Aquí un solo ejemplo usando Duration y TimeUnit para obtener el formato ‘hh: mm: ss’.

  Duration dur = Duration.between(LocalDateTimeIni, LocalDateTimeEnd); long millis = dur.toMillis(); String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(millis), TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); 

Y la versión de @Thomas en Groovy toma las unidades deseadas en una lista en lugar de codificarlas en forma rígida. Esta implementación (que puede trasladarse fácilmente a Java – hice explícita la statement de la función) hace que Thomas se aproxime más al reutilizable.

 def fromDateTime = LocalDateTime.of(1968, 6, 14, 0, 13, 0) def toDateTime = LocalDateTime.now() def listOfUnits = [ ChronoUnit.YEARS, ChronoUnit.MONTHS, ChronoUnit.DAYS, ChronoUnit.HOURS, ChronoUnit.MINUTES, ChronoUnit.SECONDS, ChronoUnit.MILLIS] println calcDurationInTextualForm(listOfUnits, fromDateTime, toDateTime) String calcDurationInTextualForm(List listOfUnits, LocalDateTime ts, LocalDateTime to) { def result = [] listOfUnits.each { chronoUnit -> long amount = ts.until(to, chronoUnit) ts = ts.plus(amount, chronoUnit) if (amount) { result << "$amount ${chronoUnit.toString()}" } } result.join(', ') } 

En el momento de escribir esto, el código anterior arroja 47 Years, 8 Months, 9 Days, 22 Hours, 52 Minutes, 7 Seconds, 140 Millis . Y, para la entrada de @Gennady Kolomoets, el código devuelve 23 Hours .

Cuando proporciona una lista de unidades, debe clasificarse por tamaño de las unidades (la más grande primero):

 def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS] // returns 2495 Weeks, 3 Days, 8 Hours 

Aquí hay una respuesta muy simple a tu pregunta. Funciona.

 import java.time.*; import java.util.*; import java.time.format.DateTimeFormatter; public class MyClass { public static void main(String args[]) { DateTimeFormatter T = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); Scanner h = new Scanner(System.in); System.out.print("Enter date of birth[dd/mm/yyyy hh:mm]: "); String b = h.nextLine(); LocalDateTime bd = LocalDateTime.parse(b,T); LocalDateTime cd = LocalDateTime.now(); int hr = cd.getHour() - bd.getHour(); int mn = cd.getMinute() - bd.getMinute(); Period time = Period.between(bd.toLocalDate(),cd.toLocalDate()); System.out.print("Age is: "+time.getYears()+ " years,"+time.getMonths()+ " months, " +time.getDays()+ " days, "+hr+ " hours, " +mn+ " minutes old"); } } 

Hay algún problema para el código Tapas Bose y el código Thomas. Si la diferencia de tiempo es negativa, la matriz obtiene los valores negativos. Por ejemplo si

 LocalDateTime toDateTime = LocalDateTime.of(2014, 9, 10, 6, 46, 45); LocalDateTime fromDateTime = LocalDateTime.of(2014, 9, 9, 7, 46, 45); 

devuelve 0 años 0 meses 1 días -1 horas 0 minutos 0 segundos.

Creo que el resultado correcto es: 0 años 0 meses 0 días 23 horas 0 minutos 0 segundos.

Propongo separar las instancias de LocalDateTime en instancias de LocalDate y LocalTime. Después de eso, podemos obtener las instancias de Java 8 Period and Duration. La instancia de Duración se separa en el número de días y en el valor del tiempo durante todo el día (<24 h) con la corrección posterior del valor del período. Cuando el segundo valor de LocalTime es anterior al valor de FirstLocalTime, es necesario reducir el período de un día.

Esta es mi forma de calcular la diferencia LocalDateTime:

 private void getChronoUnitForSecondAfterFirst(LocalDateTime firstLocalDateTime, LocalDateTime secondLocalDateTime, long[] chronoUnits) { /*Separate LocaldateTime on LocalDate and LocalTime*/ LocalDate firstLocalDate = firstLocalDateTime.toLocalDate(); LocalTime firstLocalTime = firstLocalDateTime.toLocalTime(); LocalDate secondLocalDate = secondLocalDateTime.toLocalDate(); LocalTime secondLocalTime = secondLocalDateTime.toLocalTime(); /*Calculate the time difference*/ Duration duration = Duration.between(firstLocalDateTime, secondLocalDateTime); long durationDays = duration.toDays(); Duration throughoutTheDayDuration = duration.minusDays(durationDays); Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO, "Duration is: " + duration + " this is " + durationDays + " days and " + throughoutTheDayDuration + " time."); Period period = Period.between(firstLocalDate, secondLocalDate); /*Correct the date difference*/ if (secondLocalTime.isBefore(firstLocalTime)) { period = period.minusDays(1); Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO, "minus 1 day"); } Logger.getLogger(PeriodDuration.class.getName()).log(Level.INFO, "Period between " + firstLocalDateTime + " and " + secondLocalDateTime + " is: " + period + " and duration is: " + throughoutTheDayDuration + "\n-----------------------------------------------------------------"); /*Calculate chrono unit values and write it in array*/ chronoUnits[0] = period.getYears(); chronoUnits[1] = period.getMonths(); chronoUnits[2] = period.getDays(); chronoUnits[3] = throughoutTheDayDuration.toHours(); chronoUnits[4] = throughoutTheDayDuration.toMinutes() % 60; chronoUnits[5] = throughoutTheDayDuration.getSeconds() % 60; } 

El método anterior se puede usar para calcular la diferencia de cualquier valor de fecha y hora local, por ejemplo:

 public long[] getChronoUnits(String firstLocalDateTimeString, String secondLocalDateTimeString) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime firstLocalDateTime = LocalDateTime.parse(firstLocalDateTimeString, formatter); LocalDateTime secondLocalDateTime = LocalDateTime.parse(secondLocalDateTimeString, formatter); long[] chronoUnits = new long[6]; if (secondLocalDateTime.isAfter(firstLocalDateTime)) { getChronoUnitForSecondAfterFirst(firstLocalDateTime, secondLocalDateTime, chronoUnits); } else { getChronoUnitForSecondAfterFirst(secondLocalDateTime, firstLocalDateTime, chronoUnits); } return chronoUnits; } 

Es conveniente escribir una prueba unitaria para el método anterior (ambos son miembros de la clase PeriodDuration). Aquí está el código:

 @RunWith(Parameterized.class) public class PeriodDurationTest { private final String firstLocalDateTimeString; private final String secondLocalDateTimeString; private final long[] chronoUnits; public PeriodDurationTest(String firstLocalDateTimeString, String secondLocalDateTimeString, long[] chronoUnits) { this.firstLocalDateTimeString = firstLocalDateTimeString; this.secondLocalDateTimeString = secondLocalDateTimeString; this.chronoUnits = chronoUnits; } @Parameters public static Collection periodValues() { long[] chronoUnits0 = {0, 0, 0, 0, 0, 0}; long[] chronoUnits1 = {0, 0, 0, 1, 0, 0}; long[] chronoUnits2 = {0, 0, 0, 23, 0, 0}; long[] chronoUnits3 = {0, 0, 0, 1, 0, 0}; long[] chronoUnits4 = {0, 0, 0, 23, 0, 0}; long[] chronoUnits5 = {0, 0, 1, 23, 0, 0}; long[] chronoUnits6 = {29, 8, 24, 12, 0, 50}; long[] chronoUnits7 = {29, 8, 24, 12, 0, 50}; return Arrays.asList(new Object[][]{ {"2015-09-09 21:46:44", "2015-09-09 21:46:44", chronoUnits0}, {"2015-09-09 21:46:44", "2015-09-09 22:46:44", chronoUnits1}, {"2015-09-09 21:46:44", "2015-09-10 20:46:44", chronoUnits2}, {"2015-09-09 21:46:44", "2015-09-09 20:46:44", chronoUnits3}, {"2015-09-10 20:46:44", "2015-09-09 21:46:44", chronoUnits4}, {"2015-09-11 20:46:44", "2015-09-09 21:46:44", chronoUnits5}, {"1984-12-16 07:45:55", "2014-09-09 19:46:45", chronoUnits6}, {"2014-09-09 19:46:45", "1984-12-16 07:45:55", chronoUnits6} }); } @Test public void testGetChronoUnits() { PeriodDuration instance = new PeriodDuration(); long[] expResult = this.chronoUnits; long[] result = instance.getChronoUnits(this.firstLocalDateTimeString, this.secondLocalDateTimeString); assertArrayEquals(expResult, result); } 

}

Todas las pruebas tienen éxito, independientemente de si el valor del primer LocalDateTime es anterior o no y de los valores de LocalTime.