385

I am trying to calculate the difference between two LocalDateTime.

The output needs to be of the format y years m months d days h hours m minutes s seconds. Here is what I have written:

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};
    }
}

The output that I am getting is 29 years 8 months 24 days 12 hours 0 minutes 50 seconds. I have checked my result from this website (with values 12/16/1984 07:45:55 and 09/09/2014 19:46:45). The following screenshot shows the output:

Epoch Converter

I am pretty sure that the fields after the month value is coming wrong from my code. Any suggestion would be very helpful.

Update

I have tested my result from another website and the result I got is different. Here it is: Calculate duration between two dates (result: 29 years, 8 months, 24 days, 12 hours, 0 minutes and 50 seconds).

Update

Since I got two different results from two different sites, I am wondering if the algorithm of my calculation is legitimate or not. If I use following two LocalDateTime objects:

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

Then the output is coming: 29 years 8 months 25 days -1 hours -5 minutes -10 seconds.

From this link it should be 29 years 8 months 24 days 22 hours, 54 minutes and 50 seconds. So the algorithm needs to handle the negative numbers too.

Note the question is not about which site gave me what result, I need to know the right algorithm and need to have right results.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
Tapas Bose
  • 28,796
  • 74
  • 215
  • 331
  • Just a guess, but might `Period.between()` apply some rounding? – Thomas Sep 09 '14 at 14:48
  • 3
    I just looked at the code once more and it seems the website is wrong (Try calculating yourself). If you omit the date, i.e. differences in year, month and day, you'll get the start time `7:45:55` and the end time `19:46:45` (or `7:46:45` PM). So the difference between those two times is 12 hours, 0 minutes and 50 seconds and _never_ 23 hours, 34 minutes and 12 seconds. So your calculation actualle seems to be correct, at least on the time part. – Thomas Sep 09 '14 at 15:30
  • 1
    Interesting phenomenon on that website: add 10 years to starting date and the difference in hours changes from 23 to 8 - surely a sign of bug. – Thomas Sep 09 '14 at 15:34
  • added 1 year, 4 and 10 - no change in hours – tgkprog Sep 09 '14 at 18:05
  • @Thomas did you see the second link in my update? – Tapas Bose Sep 09 '14 at 18:29
  • 10
    Note that since `LocalDateTime` has no time zone, there might not be a unique answer. Even if you assume the start and end time zones are the same, in certain zones dates like 2014-09-09 will be in Daylight Saving Time or Summer Time and in others it will not. This might throw things off by an hour. So computing the difference to the second is meaningless unless this is resolved. – Stuart Marks Sep 09 '14 at 19:39
  • "***I have tested my result from another website and the result I got is different***" > So that matches your Java code. Seems like this question is now redundant - the other website is broken. – Duncan Jones Sep 10 '14 at 06:47
  • @Duncan please see update, the question is not redundant. – Tapas Bose Sep 10 '14 at 07:33
  • @StuartMarks then I need to use `ZonedDateTime`; right? – Tapas Bose Sep 10 '14 at 07:38
  • @TapasBose if you look at your code for getting the date period you'll notice that the time part is stripped and thus only the date is considered. That's why you get 24 days instead of 23. – Thomas Sep 10 '14 at 08:18
  • `ZonedDateTime` will help avoid the particular problem I mentioned. – Stuart Marks Sep 10 '14 at 14:26
  • What about `java.time.Duration.between(Temporal a, Temporal b)` ? According to javadoc: Obtains a Duration representing the duration between two temporal objects. This calculates the duration between two temporal objects. If the objects are of different types, then the duration is calculated based on the type of the first object. For example, if the first argument is a LocalTime then the second argument is converted to a LocalTime. – GeT May 09 '16 at 09:20
  • 2
    Do you understand that using `LocalDateTime` yields unrealistic results, as that class purposely lacks any concept of time zone or offset-from-UTC? For realistic values, assign a time zone via `ZoneId` to use `ZonedDateTime`. – Basil Bourque Nov 24 '17 at 17:54

12 Answers12

692

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: Period and Duration

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
satnam
  • 10,719
  • 5
  • 32
  • 42
  • 9
    I don't see a real improvement. Compared with accepted Thomas' answer you just replace `tempDateTime.until( toDateTime, ChronoUnit.YEARS)` with `ChronoUnit.YEARS.between(fromDate, toDate)`. But the important additional lines like `tempDateTime.plusYears( years )` etc. are completely missing in your answer so it does not help the OP. The complete algorithm matters! – Meno Hochschild Jan 17 '15 at 04:10
  • 20
    I like this better since it is more succinct, and more read readable. This is the best way to find the difference between two dates. – Somaiah Kumbera May 23 '16 at 08:20
  • 8
    @SomaiahKumbera No you completely miss the critical point. **The OP does not want a duration in only one unit but multiple units**, and therefore this answer is not a real answer at all. The OP's concern is simply not addressed. Please be so kind to read again the question. (A lot of upvoters seem to have misunderstood the question). – Meno Hochschild Jun 08 '16 at 20:49
  • 180
    @MenoHochschild, I think you are actually the one missing the critical point. The OP got their answer over a year ago. The 36 thousand people who are viewing this question do not care about the OP's specific question. They were lead here by google and my answer provided them with what they were looking for -- a clean and simple solution for getting the difference between two dates. – satnam Jun 08 '16 at 22:01
  • 4
    "do not care about the OP's specific question" is a honest statement and confirms my observation that many people simply misunderstand the question in a shortened way. The aspect about `ChronoUnit.between(...)` is nothing new but can be found several times here on SO - [example link with an answer of the main author of JSR-310](http://stackoverflow.com/q/11187639) – Meno Hochschild Jun 09 '16 at 05:24
  • 16
    I needed the seconds difference between two date times and started implementing my own solution. Shortly it seems to complex and I took on google. satnam answer may not answer OP question but for sure helped me a lot and I bet many many others like me. – Julian Nov 15 '16 at 21:02
  • 1
    This answers the title of the question 'Calculate difference between two LocalDateTime'. The actual question has little to do with the title, thus the confusion and the many upvotes. – DHa Oct 19 '17 at 12:34
  • In my case I want to check if the date is yesterday and it will fail if the difference between 24 hours although the days are different. So I had to use my custom checker. I shared the checker here if anyone want to use or can help me to optimize https://gist.github.com/hendrawd/9f9b77557b16579c84759c5c699b5343 – HendraWD Jun 09 '18 at 13:34
  • This is by far the most clear and straightforward solution. Funny seeing people trying all sorts of wizardry for something so simple. – saran3h Mar 11 '23 at 10:31
227

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.

Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42
Thomas
  • 87,414
  • 12
  • 119
  • 157
  • 1
    You have the same basic idea, therefore my upvote, but please try to use the same input otherwise the different result is confusing. – Meno Hochschild Sep 10 '14 at 12:01
  • @MenoHochschild it _is_ the same input, just taken from the update where the OP has issues with negative times. ;) – Thomas Sep 10 '14 at 12:06
  • Ah I see. Just saw the first input at the start of OPs posting. – Meno Hochschild Sep 10 '14 at 12:18
  • thats bad of JSR310 because JodaTime hat it: Period p = new Period(date1, date2, PeriodType.yearMonthDayTime()); int yrs = p.getYears(); int mon = p.getMonths(); int day = p.getDays(); int hrs = p.getHours(); int min = p.getMinutes(); int sec = p.getSeconds(); int mil = p.getMillis(); – Bernhard Sep 19 '14 at 11:07
  • 1
    Good algorithm, but wrong type (see Thomas disclaimer). Before doing your calculation, you should convert your LocalDateTime variables to ZonedDateTime using default time zone for example (or any time zone you need) : ZonedDateTime fromZonedDateTime = fromDateTime.atZone(ZoneId.systemDefault()); – Tristan Mar 17 '15 at 19:00
  • No one should be doing it this awkwardly in the presence of `ChronoUnit`, which is part of `java.time`, as presented by satnam. – Evgeniy Berezovsky Jun 03 '16 at 03:01
  • @EugeneBeresovsky you're right, if one is able to use Java 8 (which wasn't widely spread when I posted the answer) - and even today not everyone is able to use Java 8. – Thomas Jun 03 '16 at 07:37
  • 2
    @Thomas Your example is using Java 8 as well - and the question is tagged as java-8. In fact your example is even using `ChronoUnit` :) but doing the job "by hand". – Evgeniy Berezovsky Jun 05 '16 at 22:24
  • 3
    @EugeneBeresovsky oh yes, you're right. Totally missed that ;) - Besides that, you'd still have to subtract the larger units from the interval since for example `long minutes = ChronoUnit.MINUTES.between(fromDate, toDate);` would return a number greater than 59 for any interval of at least 1 hour - and that's not what the OP wants. Taking that into account you'd not be able to just make 6 calls to `ChronoUnit#between()` and be done. – Thomas Jun 06 '16 at 07:37
122

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)));
Zon
  • 18,610
  • 7
  • 91
  • 99
57

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)));
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Junior Batista
  • 587
  • 4
  • 2
  • 6
    It's actually possible to do it like this: `String.format("%02d:%02d:%02d",dur.toHoursPart(), dur.toMinutesPart(), dur.toSecondsPart());`. The part methods give you the right numbers to build a string. I think it' more readable. – Daantie Mar 13 '19 at 16:20
  • 1
    Side note to my previous comment: the part methods are only available from JDK 9 and up. – Daantie Mar 14 '19 at 09:24
30

Keep in mind: calendar months and years do not represent a fixed temporal length. For example, if this code shows a difference of 1 month, it could mean anything from 28 days to 31 days (where 1 day = 24h). If that's not ok for your use case, I strongly recommend ditching months and years entirely (so you can just use the Duration class), or at most setting a fixed temporal value for months and years (e.g. 30 days for a month and 365 days for a year).

If you still want to go ahead (for example because your users already expect months and years to mean calendar months and years), you will notice the code is not as straightforward as one would initially think it should be (this also means it's more error-prone; thanks to @Alex for noticing a bug, which has now been fixed), and that is precisely because Java library designers were smart enough not to mix calendar periods (Period class) and exact durations (Duration class) together.


TL;DR

Precondition: start <= end.

// Closest start datetime with same time-of-day as the end datetime.
// This is to make sure to only count full 24h days in the period.
LocalDateTime closestFullDaysStart = LocalDateTime.of(
    start.toLocalDate()
         .plusDays(end.toLocalTime().compareTo(start.toLocalTime()) < 0 ? 1 : 0),
    end.toLocalTime()
);

// Get the calendar period between the dates (full years, months & days).
Period period = Period.between(closestFullDaysStart.toLocalDate(), end.toLocalDate());

// Get the remainder as a duration (hours, minutes, etc.).
Duration duration = Duration.between(start, closestFullDaysStart);

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.

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 2 minutes, much less than 24h. So, we need to start counting the calendar period at the next closest point in time which has the same time of day as the end point, so that any calendar days that we count actually correspond to full 24h durations:

LocalDateTime closestFullDaysStart = LocalDateTime.of(
    start.toLocalDate()
         // if the end time-of-day is earlier than the start time-of-day,
         // the next point in time with that time-of-day is one calendar day ahead
         // (the clock "wraps around" at midnight while advancing to it)
         .plusDays(end.toLocalTime().compareTo(start.toLocalTime()) < 0 ? 1 : 0),
    end.toLocalTime()
);

Now we have effectively split the timespan between start and end into two parts: the span from start to the closestFullDaysStart, which by construction will be less than 24h, so we can measure it with a Duration object with no days part,

Duration duration = Duration.between(start, closestFullDaysStart);

and the span from closestFullDaysStart and end, which we know we can now reliably* measure with a Period.

Period period = Period.between(closestFullDaysStart.toLocalDate(), end.toLocalDate());

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.

Negative spans

If start > end, the code above produces an answer which is technically correct if we sum all the units but is presented in an unexpected way.

For example, for

LocalDateTime start = LocalDateTime.of(2020, 1, 1, 1, 00);
LocalDateTime end = LocalDateTime.of(2020, 1, 1, 0, 0);

we get:

0 years, 0 months, -1 days, 23 hours, 0 minutes, 0 seconds

-1 day plus 23 hours is -1 hour, which is correct. But we would expect the answer to be just -1 hour.

Currently durations shown are always positive, and this is because closestFullDaysStart is always in the future with respect to start.

If start > end, however, the direction from start to end is "back in time", and so we need to retrocede from start in order to find the closest datetime with the end time-of-day that lies between start and end. Also, the wrap-around condition changes: since we are winding the clock backwards, we wrap around the clock (and thus need to subtract one calendar day) if the start time-of-day is earlier than the end time.

Combining all of this yields:

int endStartComparison = end.compareTo(start);
int endStartTimeComparison = end.toLocalTime().compareTo(start.toLocalTime());
LocalDateTime closestFullDaysStart = LocalDateTime.of(
    start.toLocalDate()
         .plusDays(
             (
                 (endStartComparison > 0)
                 ? (endStartTimeComparison < 0)
                 : (endStartTimeComparison > 0)
             )
             ? (long) Math.signum(endStartComparison)  // advance one unit in the direction start->end 
             : 0
         ),
    end.toLocalTime()
);

We can rewrite that ugly nested conditional in a more compact but more hacky way:

(endStartComparison * endStartTimeComparison < 0)

Try it online!

Anakhand
  • 2,838
  • 1
  • 22
  • 50
  • 1
    I like your answer, but it has a bug. In case when `Period.between()` returns 1, 2 etc. months without any days, the subsequent call to `period.minusDay()` will subtract 1 day from 0 days, relulting in period having -1 days. The months do not translate to days, because the period object does not have enough context to do this correctly (is 1 month = 30 days, or 31?). Thus, subtracting should happen before measuring period. Fo example, like this: `Period period = Period.between(start.toLocalDate().plusDays( (end.toLocalTime() >= start.toLocalTime()) ? 0L : 1L), end.toLocalDate())` – Alex Semeniuk Dec 14 '22 at 10:41
  • @AlexSemeniuk Thank you for noticing the bug and suggesting a solution! I have now fixed it; I used your idea but wrote it slightly differently, I think it also helped clarify/simplify the solution overall. PS: when I wrote the `.minusDays` code, it felt so hacky that I felt it should go wrong somewhere, and it indeed does, I guess I didn't test thoroughly at all! – Anakhand Dec 20 '22 at 11:15
7

Here is a very simple answer to your question. It works.

import java.time.*;
import java.util.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

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();

    long minutes = ChronoUnit.MINUTES.between(bd, cd);
    long hours = ChronoUnit.HOURS.between(bd, cd);

    System.out.print("Age is: "+hours+ " hours, or " +minutes+ " minutes old");
}
}
Tinashe
  • 1,052
  • 12
  • 20
  • 1
    I should mention this is not accurate. try with this. it says 1 minute difference. LocalDateTime bd = LocalDateTime.of(2019, 11, 26, 15, 03, 55); LocalDateTime cd = LocalDateTime.of(2019, 11, 26, 15, 04, 45); – Mark Amabile Nov 26 '19 at 04:28
  • I entered a time last night and got `Age is: 0 years,0 months, 1 days, -10 hours, -48 minutes old`. I don’t think this is what was desired. – Ole V.V. May 02 '20 at 11:13
  • @OleV.V. Fixed that issue – Tinashe May 06 '20 at 19:10
6

There is some problem for Tapas Bose code and Thomas code. If time differenсe is negative, array gets the negative values. For example if

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

it returns 0 years 0 months 1 days -1 hours 0 minutes 0 seconds.

I think the right output is: 0 years 0 months 0 days 23 hours 0 minutes 0 seconds.

I propose to separate the LocalDateTime instances on LocalDate and LocalTime instances. After that we can obtain the Java 8 Period and Duration instances. The Duration instance is separated on the number of days and throughout-the-day time value (< 24h) with subsequent correction of the period value. When the second LocalTime value is before the firstLocalTime value, it is necessary to reduce the period for one day.

Here's my way to calculate the LocalDateTime difference:

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;
}

The above method can be used to calculate the difference of any local date and time values, for example:

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;
}

It is convenient to write a unit test for the above method (both of them are PeriodDuration class members). Here's the code:

@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<Object[]> 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);
}

}

All tests are successful whether or not the value of the first LocalDateTime is before and for any LocalTime values.

Patrick
  • 4,532
  • 2
  • 26
  • 32
  • I cannot reproduce your statement that Thomas code using your input above produces mixed signs. My output is: "0 years 0 months 0 days 23 hours 0 minutes 0 seconds.". And I have tested just now. – Meno Hochschild Jun 15 '15 at 10:21
  • Thank you for the comment. I thoroughly tested the Thomas code, indeed, it is working correctly! The magic is done by LocalDateTime until method correcting chrono units. Negative values in the Thomas code appear if the first DateTime is later than the second. But this can be easily corrected, for example, as I did in the above code. Thanks again. – Hennadii Kolomoiets Jun 18 '15 at 20:39
  • Well, Thomas code and my library Time4J using the same algorithm for calculation of durations can produce negative signs but only for the whole duration. That is the crucial point! The sign relates to the whole duration and hence describes if the start is later than the end and is never related to single duration components. Mixed signs are not possible here and would prevent such an interpretation of start relative to end (counter example are Joda-Time-period or `java.time.Period` where users can enforce/produce such mixed signs due to different internal design). – Meno Hochschild Jun 19 '15 at 08:28
6

And the version of @Thomas in Groovy with takes the desired units in a list instead of hardcoding the values. This implementation (which can easily ported to Java - I made the function declaration explicit) makes Thomas approach more reuseable.

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<ChronoUnit> 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(', ')
}

At the time of this writing,the code above returns 47 Years, 8 Months, 9 Days, 22 Hours, 52 Minutes, 7 Seconds, 140 Millis. And, for @Gennady Kolomoets input, the code returns 23 Hours.

When you provide a list of units it must be sorted by size of the units (biggest first):

def listOfUnits = [ChronoUnit.WEEKS, ChronoUnit.DAYS, ChronoUnit.HOURS]
// returns 2495 Weeks, 3 Days, 8 Hours
ChrLipp
  • 15,526
  • 10
  • 75
  • 107
3

After more than five years I answer my question. I think that the problem with a negative duration can be solved by a simple correction:

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

Period period = Period.between(fromDateTime.toLocalDate(), toDateTime.toLocalDate());
Duration duration = Duration.between(fromDateTime.toLocalTime(), toDateTime.toLocalTime());

if (duration.isNegative()) {
    period = period.minusDays(1);
    duration = duration.plusDays(1);
}
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);
long time[] = {hours, minutes, secs};
System.out.println(period.getYears() + " years "
            + period.getMonths() + " months "
            + period.getDays() + " days "
            + time[0] + " hours "
            + time[1] + " minutes "
            + time[2] + " seconds.");

Note: The site https://www.epochconverter.com/date-difference now correctly calculates the time difference.

Thank you all for your discussion and suggestions.

  • 1
    Thanks for the answer. For the hours and smaller you can benefit from the `toHours`, `toMinutes`and `toSeconds` methods of `Duration`. Since Java 9 even more from `toMinutesPart()` and `toSecondsPart()`. And I don’t see thepoint in wrapping the hours, mins and secs into an array only to take them out again. Generally a good answer, though. – Ole V.V. May 02 '20 at 10:42
1

Joda-Time

Since many of the answers required API 26 support and my min API was 23, I solved it by below code :

import org.joda.time.Days

LocalDate startDate = Something
LocalDate endDate = Something
// The difference would be exclusive of both dates, 
// so in most of use cases we may need to increment it by 1
Days.daysBetween(startDate, endDate).days
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Kushal
  • 8,100
  • 9
  • 63
  • 82
  • 2
    The *Joda-Time* project is in maintenance-mode, its creator Stephen Colebourne having gone on to create the *java.time* classes defined in JSR 310. Most of the *java.time* functionality is back-ported to Java 6 & 7 in the *ThreeTen-Backport* project. Further adapted for early Android in the *ThreeTenABP* project. – Basil Bourque Sep 24 '19 at 15:24
0

For anyone trying to count the minutes between two LocalTime objects, keep in mind that the result will be minus 1.

For example,

long countMins = MINUTES.between( LocalTime.of(8,00),LocalTime.of(9,59) );
System.out.println("countMins = " + countMins );

will give the Output: countMins = 119 instead of 120

  • 1
    Thanks for the heads-up. This is because `between` measures from start time inclusive to end time *exclusive*. If you want 120 minutes, use 8:00 and 10:00. – Ole V.V. Sep 26 '22 at 07:01
  • 1
    Yes, exactly. Thank you for the reasoning friend. And In case that the values are given on run-time by the user and you can't control them, just add 1 after they provide their input e.g. `countMins+=1;` – excitedWithin Sep 27 '22 at 22:40
  • What is the exact import for MINUTES here? I cant seem to find it in LocalTime/LocalDateTime classes so not sure where its from – cowley05 Sep 29 '22 at 09:56
  • 1
    @cowley05 import static java.time.temporal.ChronoUnit.MINUTES; – excitedWithin Sep 30 '22 at 10:53
0

If you don't need years and months you can use DurationFormatUtils from Apache Commons Lang 3. It uses miliseconds as the duration. You can get the miliseconds from java.time Duration class.

LocalDateTime start = LocalDateTime.of(2023,10,1,7,14,3);
LocalDateTime end = LocalDateTime.of(2023,10,1,8,55,22);
Duration d = Duration.between(start, end);
DurationFormatUtils.formatDuration(d.toMillis(), "d 'days' H 'hours' m 'minutes' s 'seconds'", true);

 // "0 days 1 hour 41 minutes 19 seconds"

For other possiblities check out the JavaDoc.

Robert Niestroj
  • 15,299
  • 14
  • 76
  • 119