Java 8: Difference between two LocalDateTime in multiple units
I found the best way to do this is with ChronoUnit.
long minutes = ChronoUnit.MINUTES.between(fromDate, toDate);
long hours = ChronoUnit.HOURS.between(fromDate, toDate);
Additional documentation is here: https://docs.oracle.com/javase/tutorial/datetime/iso/period.html
Unfortunately, there doesn't seem to be a period class that spans time as well, so you might have to do the calculations on your own.
Fortunately, the date and time classes have a lot of utility methods that simplify that to some degree. Here's a way to calculate the difference although not necessarily the fastest:
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.
The basic idea is this: create a temporary start date and get the full years to the end. Then adjust that date by the number of years so that the start date is less then a year from the end. Repeat that for each time unit in descending order.
Finally a disclaimer: I didn't take different timezones into account (both dates should be in the same timezone) and I also didn't test/check how daylight saving time or other changes in a calendar (like the timezone changes in Samoa) affect this calculation. So use with care.
It should be simpler!
Duration.between(startLocalDateTime, endLocalDateTime).toMillis();
You can convert millis to whatever unit you like:
String.format("%d minutes %d seconds",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
Here a single example using Duration and TimeUnit to get 'hh:mm:ss' format.
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)));
TL;DR
// get the calendar period between the times (years, months & days)
Period period = Period.between(start.toLocalDate(), end.toLocalDate());
// make sure to get the floor of the number of days
period = period.minusDays(end.toLocalTime().compareTo(start.toLocalTime()) >= 0 ? 0 : 1);
// get the remainder as a duration (hours, minutes, etc.)
Duration duration = Duration.between(start, end);
// remove days, already counted in the period
duration = duration.minusDays(duration.toDaysPart());
and then use the methods period.getYears()
, period.getMonths()
, period.getDays()
, duration.toHoursPart()
, duration.toMinutesPart()
, duration.toSecondsPart()
.
Try it online!
Expanded answer
I'll answer the original question, i.e. how to get the time difference between two LocalDateTimes
in years, months, days, hours & minutes, such that the "sum" (see note below) of all the values for the different units equals the total temporal difference, and such that the value in each unit is smaller than the next bigger unit—i.e. minutes < 60
, hours < 24
, and so on.
Given two LocalDateTimes
start
and end
, e.g.
LocalDateTime start = LocalDateTime.of(2019, 11, 28, 17, 15);
LocalDateTime end = LocalDateTime.of(2020, 11, 30, 16, 44);
we can represent the absolute timespan between the two with a Duration
—perhaps using Duration.between(start, end)
. But the biggest unit we can extract out of a Duration
is days (as a temporal unit equivalent to 24h)—see the note below for an explanation. To use larger units (months, years) we can represent this Duration
with a pair of (Period
, Duration
), where the Period
measures the difference up to a precision of days and the Duration
represents the remainder.
To get the Period
:
Period period = Period.between(start.toLocalDate(), end.toLocalDate());
We need to be careful here, because a Period
is really a date difference, not an amount of time, and all its calculations are based on calendar dates (see the section below). For example, from 1st January 2000 at 23:59 to 2nd January 2000 at 00:01, a Period
would say there is a difference of 1 day, because that's the difference between the two dates, even though the time delta is less than 24h. So, if the time of day on the end datetime is earlier than the time of day on the start datetime, we need to subtract 1 from the day count, since it doesn't correspond to a complete 24h span (the corresponding remainder will be recorded by our Duration
):
period = period.minusDays(end.toLocalTime().compareTo(start.toLocalTime()) >= 0 ? 0 : 1);
To get the remainder, as a Duration
:
Duration duration = Duration.between(start, end);
Since the difference up to a precision of days is already taken care of by our period, we only need to keep smaller units (hours, minutes, etc.):
duration = duration.minusDays(duration.toDaysPart()); // essentially "duration (mod 1 day)"
Now we can simply use the methods defined on Period
and Duration
to extract the individual units:
System.out.printf(
"%d years, %d months, %d days, %d hours, %d minutes, %d seconds",
period.getYears(), period.getMonths(), period.getDays(),
duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart()
);
1 years, 0 months, 1 days, 23 hours, 29 minutes, 0 seconds
or, using the default format:
System.out.println(period + " + " + duration);
P1Y1D + PT23H29M
Note on years, months & days
Note that, in java.time
's conception, period "units" like "month" or "year" don't represent a fixed, absolute temporal value—they're date- and calendar-dependent, as the following example illustrates:
LocalDateTime
start1 = LocalDateTime.of(2020, 1, 1, 0, 0),
end1 = LocalDateTime.of(2021, 1, 1, 0, 0),
start2 = LocalDateTime.of(2021, 1, 1, 0, 0),
end2 = LocalDateTime.of(2022, 1, 1, 0, 0);
System.out.println(Period.between(start1.toLocalDate(), end1.toLocalDate()));
System.out.println(Duration.between(start1, end1).toDays());
System.out.println(Period.between(start2.toLocalDate(), end2.toLocalDate()));
System.out.println(Duration.between(start2, end2).toDays());
P1Y
366
P1Y
365
As another example, from 1st January 2000 at 23:59 to 2nd January 2000 at 00:01, a Period
would say there is a difference of 1 day, because that's the difference between the two dates, even though the time delta is less than 24h.