56

NOTE THIS IS NOT A DUPLICATE OF EITHER OF THE FOLLOWING


I have two dates:

  • Start date: "2016-08-31"
  • End date: "2016-11-30"

Its 91 days duration between the above two dates, I expected my code to return 3 months duration, but the below methods only returned 2 months. Does anyone have a better suggestion? Or do you guys think this is a bug in Java 8? 91 days the duration only return 2 months.

Thank you very much for the help.

Method 1:

Period diff = Period.between(LocalDate.parse("2016-08-31"),
    LocalDate.parse("2016-11-30"));

Method 2:

long daysBetween = ChronoUnit.MONTHS.between(LocalDate.parse("2016-08-31"),
    LocalDate.parse("2016-11-30"));

Method 3:

I tried to use Joda library instead of Java 8 APIs, it works. it loos will return 3, It looks like Java duration months calculation also used days value. But in my case, i cannot use the Joda at my project. So still looking for other solutions.

    LocalDate dateBefore= LocalDate.parse("2016-08-31");
    LocalDate dateAfter = LocalDate.parse("2016-11-30");
    int months = Months.monthsBetween(dateBefore, dateAfter).getMonths();
    System.out.println(months);
morten.c
  • 3,414
  • 5
  • 40
  • 45
SharpLu
  • 1,136
  • 2
  • 12
  • 28
  • Duplicate of https://stackoverflow.com/questions/1555262/calculating-the-difference-between-two-java-date-instances and https://stackoverflow.com/questions/18440083/calculate-months-between-two-dates-in-java?s=1|166.9893 – pattyd Feb 23 '18 at 14:35
  • Please read my question carefully, – SharpLu Feb 23 '18 at 14:36
  • That selected answer needs to be updated. – SedJ601 Feb 23 '18 at 14:37
  • The solution is already available on Stack Overflow, see the links I posted above for you. – pattyd Feb 23 '18 at 14:37
  • Are you sure that returning 2 is incorrect? If your index starts at zero, 2 would be the correct answer. – pattyd Feb 23 '18 at 14:38
  • 5
    @pattyd This is obviously not a duplicate. OP knows how to calculate it, as shown by the examples they posted. They want to know why they are getting an unexpected result. – Pablo Feb 23 '18 at 14:39
  • return 2 it might correct for the Java 8 API, but i expect to return 3 months . – SharpLu Feb 23 '18 at 14:40
  • Have you tried `LocalDate.minusMonths`? You could do something like `november.minusMonths(october.getMonthValue()).getMonthValue()` – Vince Feb 23 '18 at 14:40
  • If you changed the start date to `2016-08-30`, you get three months 3 months and 0 days. Using the values you have, you get 2 months and 30 days. – SedJ601 Feb 23 '18 at 14:44
  • The underlying problem is, that dates aren't arithmetic. In math, we expect, that if (a==b) then (a+1==b+1) and if (a==b), then (a-1==b-1). Now for your last days of the month, a+1 is Sep. 1st, b+1 is Dez. 1st, and therefore 3 months. But if we go 29 days back, we end at 1st Aug and 2nd Nov. It's inconsistent in the real world. – user unknown Feb 23 '18 at 14:47
  • Yes, It looks If two dates both are end at each month, then Java it only returns 2 month duration. – SharpLu Feb 23 '18 at 14:48
  • @SharpLu not always. It depends on the day number. For example, from 2016-08-31 to 2016-11-30 is 2 months, but from 2016-09-30 to 2016-12-31 is 3 months. – DodgyCodeException Feb 23 '18 at 14:57
  • Between method end dates month. To get correct value you need substract one month from any of the months. Use following code – Prashanth Debbadwar Feb 23 '18 at 15:02
  • long daysBetween = ChronoUnit.MONTHS.between(LocalDate.parse("2016-08-31").minusMonths(1), LocalDate.parse("2016-11-30")); – Prashanth Debbadwar Feb 23 '18 at 15:02
  • @PrashanthDebbadwar use minusMonths it works for all cases? because I have a lot of dates need to check the correct duration months. – SharpLu Feb 23 '18 at 15:09
  • @PrashanthDebbadwar for example if test 2015-01-01 to 2016-01-01 . in your logic it will not work. – SharpLu Feb 23 '18 at 15:11
  • 3
    @SharpLu you say you want to calculate the correct duration in months. But for the dates given in your question, 2 months **is** the correct duration! If you want people to provide an answer, you need to state why you believe that the correct duration should be 3 months when in fact the correct answer is 2. – DodgyCodeException Feb 23 '18 at 15:18
  • 2
    Come, man, this two date its duration 91 days. do you think 91 day= 2 months is correct? – SharpLu Feb 23 '18 at 15:22
  • 4
    You are not understanding the difference between the duration in month and in days. If you have a problem with that, use mathematics. `durationInDays / 30` since it seems you believe every month should have 30 days. For me, the 3rd month will be when then days will be the same or after, not before. If you really want to use the methods, set both days to `1`, you don't care about the days so this would return the correct result. – AxelH Feb 23 '18 at 15:38
  • Thanks for the information, but i used a dirty solution it works now. If we see the duration mon between 2016-08 and 2016-11 its 3 months duration. if we check days "2016-08-31" and "2016-11-30" Its 91 days, so i didnt understand why we got 2 duration in the API. – SharpLu Feb 23 '18 at 15:49
  • 3
    @SharpLu in answer to "Come, man, this two date its duration 91 days. do you think 91 day= 2 months is correct?": yes, I think 91 days = 2 months is correct when the months involved have 31+30+31 = 92 days. – DodgyCodeException Feb 23 '18 at 18:46
  • 2
    @SharpLu Throughout your Question and your Comments, you never explain *your* own algorithm for determining months. Please edit both your Question and its title to clarify. – Basil Bourque Feb 24 '18 at 01:24

10 Answers10

66

Since you don't care about the days in your case. You only want the number of month between two dates, use the documentation of the period to adapt the dates, it used the days as explain by Jacob. Simply set the days of both instance to the same value (the first day of the month)

Period diff = Period.between(
            LocalDate.parse("2016-08-31").withDayOfMonth(1),
            LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(diff); //P3M

Same with the other solution :

long monthsBetween = ChronoUnit.MONTHS.between(
        LocalDate.parse("2016-08-31").withDayOfMonth(1),
        LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(monthsBetween); //3

Edit from @Olivier Grégoire comment:

Instead of using a LocalDate and set the day to the first of the month, we can use YearMonth that doesn't use the unit of days.

long monthsBetween = ChronoUnit.MONTHS.between(
     YearMonth.from(LocalDate.parse("2016-08-31")), 
     YearMonth.from(LocalDate.parse("2016-11-30"))
)
System.out.println(monthsBetween); //3
AxelH
  • 14,325
  • 2
  • 25
  • 55
  • 1
    Thanks. Used. ChronoUnit.MONTHS.between(dateBefore.withDayOfMonth(1), dateAfter.withDayOfMonth(1)); – SharpLu Feb 23 '18 at 15:53
  • 1
    @SharpLu Seems legit, you don't really need the `Period` in your case, only the numeric value. – AxelH Feb 23 '18 at 15:54
  • 7
    You should compare `YearMonth` and not `LocalDate`: `ChronoUnit.MONTHS.between(YearMonth.from(fromDate), YearMonth.from(toDate))` – Olivier Grégoire Feb 20 '19 at 22:22
  • year month isn't in java 8. – Taugenichts Feb 26 '20 at 21:14
  • Yes it is @Taugenichts : [YearMonth](https://docs.oracle.com/javase/8/docs/api/java/time/YearMonth.html) – AxelH Feb 27 '20 at 06:54
  • I prefer the option with the `YearMonth` – you only have what you need, and nothing more: a year and a month. You don't get distracted by things you don't (want to) use. – MC Emperor Jul 09 '21 at 08:33
17

Since Java8:

ChronoUnit.MONTHS.between(startDate, endDate);
Zon
  • 18,610
  • 7
  • 91
  • 99
  • Incorrect answer. It doesn't work if you don't care about the number of days – kapandron Jan 29 '21 at 15:31
  • 1
    Can't get the idea of previous comment. Sorry. – Zon Feb 01 '21 at 13:35
  • Just run `ChronoUnit.MONTHS.between(LocalDate.parse("2021-01-31"), LocalDate.parse("2021-02-28"))` and see the result – kapandron Feb 02 '21 at 18:47
  • 1
    @Kapelichik, method works as expected, following its docs: `Returns: the amount of time between temporal1Inclusive and temporal2Exclusive in terms of this unit`. In your case `2021-02-28` day is not included, so no full months are between these dates. What gives you 1 month is `ChronoUnit.MONTHS.between(LocalDate.parse("2021-01-31"), LocalDate.parse("2021-03-01"));` – Zon Feb 10 '21 at 07:30
14

//Backward compatible with older Java

public static int monthsBetween(Date d1, Date d2){
    if(d2==null || d1==null){
        return -1;//Error
    }
    Calendar m_calendar=Calendar.getInstance();
    m_calendar.setTime(d1);
    int nMonth1=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
    m_calendar.setTime(d2);
    int nMonth2=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
    return java.lang.Math.abs(nMonth2-nMonth1);
}
Neuron
  • 5,141
  • 5
  • 38
  • 59
Yandi Ongkojoyo
  • 141
  • 1
  • 2
7

The documentation of Period#between states the following:

The start date is included, but the end date is not.

Furthermore:

A month is considered if the end day-of-month is greater than or equal to the start day-of-month.

Your end day-of-month 30 is not greater than or equal to your start day-of-month 31, so a third month is not considered.

Note the parameter names:

public static Period between​(LocalDate startDateInclusive, LocalDate endDateExclusive)

To return 3 months, you can increment the endDateExclusive by a single day.

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
2

In case you want stick to java.time.Period API

As per java.time.Period documentation

Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)

where

@param startDateInclusive  the start date, inclusive, not null
@param endDateExclusive  the end date, exclusive, not null

So it is better to adjust your implementation to make your end date inclusive and get your desired result

Period diff = Period.between(LocalDate.parse("2016-08-31"),
                LocalDate.parse("2016-11-30").plusDays(1));
System.out.println("Months : " + diff.getMonths());
//Output -> Months : 3
Raul Cacacho
  • 267
  • 1
  • 4
  • 15
abhi
  • 4,762
  • 4
  • 29
  • 49
2

You have to be careful, never use LocalDateTime to calculate months between two dates the result is weird and incorrect, always use LocalDate !

here's is some code to prove the above:

package stack.time;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class TestMonthsDateTime {
    public static void main(String[] args) {
        /**------------------Date Time----------------------------*/
        LocalDateTime t1 = LocalDateTime.now();
        LocalDateTime t2 = LocalDateTime.now().minusMonths(3);
        long dateTimeDiff = ChronoUnit.MONTHS.between(t2, t1);  
        System.out.println("diff dateTime : " + dateTimeDiff); // diff dateTime : 2
        /**-------------------------Date----------------------------*/
        LocalDate t3 = LocalDate.now();
        LocalDate t4 = LocalDate.now().minusMonths(3);
        long dateDiff = ChronoUnit.MONTHS.between(t4, t3);
        System.out.println("diff date : "  +  dateDiff); // diff date : 3
    }
}

My 2%

NashBird99
  • 193
  • 2
  • 12
2

This example checks to see if the second date is the end of that month. If it is the end of that month and if the first date of month is greater than the second month date it will know it will need to add 1

LocalDate date1 = LocalDate.parse("2016-08-31");
LocalDate date2 = LocalDate.parse("2016-11-30");
    
long monthsBetween = ChronoUnit.MONTHS.between(
            date1,
            date2);
    
if (date1.isBefore(date2) 
        && date2.getDayOfMonth() == date2.lengthOfMonth() 
        && date1.getDayOfMonth() > date2.getDayOfMonth()) {
    monthsBetween += 1;
}
Flips
  • 170
  • 1
  • 11
1

After the short investigation, still not totally fix my question, But I used a dirty solution to avoid return the incorrect duration. At least, we can get the reasonable duration months.

private static long durationMonths(LocalDate dateBefore, LocalDate dateAfter) {
        System.out.println(dateBefore+"   "+dateAfter);
        if (dateBefore.getDayOfMonth() > 28) {
            dateBefore = dateBefore.minusDays(5);
        } else if (dateAfter.getDayOfMonth() > 28) {
            dateAfter = dateAfter.minusDays(5);
        }
        return ChronoUnit.MONTHS.between(dateBefore, dateAfter);
    }
SharpLu
  • 1,136
  • 2
  • 12
  • 28
  • 1
    Why only 5 days if greater than 28 ? This is not defined in the question... so why not simply set the first day of the month ? What should be the result for `durationMonths(LocalDate.parse("2016-08-30"), LocalDate.parse("2016-11-01"))` ? – AxelH Feb 23 '18 at 15:49
0

The Java API response is mathematically accurate according to the calendar. But you need a similar mechanism, such as rounding decimals, to get the number of months between dates that matches the human perception of the approximate number of months between two dates.

Period period = Period.between(LocalDate.parse("2016-08-31"), LocalDate.parse("2016-11-30"));
long months = period.toTotalMonths();
if (period.getDays() >= 15) {
    months++;
}
DavidJezek
  • 101
  • 1
  • 4
0

Sorted out, seems to be tricky

 Period period = Period.between(startDate, endDate);
    if ((endDate.getMonth() == Month.FEBRUARY && period.getDays() >= Month.FEBRUARY.length(Year.isLeap(endDate.getYear())) &&
            endDate.isEqual(getLastDayofTheMonth(endDate))) ||
            (startDate.isEqual(getLastDayofTheMonth(startDate)) &&
                    endDate.isEqual(getLastDayofTheMonth(endDate)) &&
                    endDate.getDayOfMonth() == ODD_MONTH_DAYS)) {
        period = period.plusMonths(1);
    }

    return period.getYears() * 12 + period.getMonths();
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 23 '23 at 13:02