6

I want the number of months between 2 java.util.Date's, without the days in month counted. So I just want the year and month in the comparison.

Example:

 monthsBetween(new Date(2012,01,28), new Date(2012,02,01)) ---> 1

 monthsBetween(new Date(2012,02,27), new Date(2012,02,28)) ---> 0

 monthsBetween(new Date(2012,03,28), new Date(2012,07,01)) ---> 4

I have tried this (returns 0, expected 1), using Joda-time:

private static int monthsBetween(final Date fromDate, final Date toDate) {
    DateTime date1 = new DateTime().withDate(2012, 1, 20);
    DateTime date2 = new DateTime().withDate(2012, 2, 13);
    PeriodType monthDay = PeriodType.yearDayTime().withDaysRemoved();
    Period difference = new Period(date1, date2, monthDay);
    int months = difference.getMonths();
    
    return months;
 }

And also this (same results), using Joda-time:

private static int monthsBetween(final Date fromDate, final Date toDate) {
        return Months.monthsBetween(new DateTime(fromDate), new   DateTime(toDate).getMonths();
    }

How can I accomplish this?

Community
  • 1
  • 1
Filip
  • 327
  • 1
  • 8
  • 18
  • 2
    What time zone do you want to use to interpret the `Date` values? – Jon Skeet Nov 26 '12 at 14:16
  • I will use TimeZone="GMT+1", something else I need to think about in that case? – Filip Nov 26 '12 at 14:28
  • 2
    Always GMT+1? Fair enough if that's what you need - but it does sound a little odd. Basically, you just need to be aware that what date a particular instant in time falls into depends on time zone. (And calendar system, but that's less likely to be an issue.) – Jon Skeet Nov 26 '12 at 14:29
  • Yes, always GMT+1. Ok thanks for the information! – Filip Nov 26 '12 at 14:35
  • You probably noticed that new java.util.Date(int year, int month, int date) is deprecated? Are you aware that year is supposed to be the year minus 1900 and month = the month between 0-11? – Evgeniy Dorofeev Nov 26 '12 at 15:40
  • Good point! Yes, I noticed that. And yes, I'm aware of that, that part was just "pseudocode". But I shouldn't write code that is wrong or deprecated of course. – Filip Nov 26 '12 at 16:26

8 Answers8

10

You're asking for the number of whole months - which isn't the same as saying "ignore the day of month part".

To start with, I'd suggest using LocalDate instead of DateTime for the computations. Ideally, don't use java.util.Date at all, and take your input as LocalDate to start with (e.g. by parsing text straight to that, or wherever your data comes from.) Set the day of month to 1 in both dates, and then take the difference in months:

private static int monthsBetweenIgnoreDays(LocalDate start, LocalDate end) {
    start = start.withDayOfMonth(1);
    end = end.withDayOfMonth(1);
    return Months.monthsBetween(start, end).getMonths();
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    @BasilBourque: The OP didn't start off with `DateTime` values - they started with `Date` values. Changing those to be `LocalDate` means knowing which time zone to use, etc. It's not at all obvious that the OP really *needed* do start with `Date` objects though... – Jon Skeet Apr 27 '15 at 05:31
5

This version is JDK Calendar based:

public static void main(String[] args) throws Exception {
    SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
    Date d1 = f.parse("2012-01-01");
    Date d2 = f.parse("2012-02-02");
    int n = differenceInMonths(d1, d2);
    System.out.println(n);
}

private static int differenceInMonths(Date d1, Date d2) {
    Calendar c1 = Calendar.getInstance();
    c1.setTime(d1);
    Calendar c2 = Calendar.getInstance();
    c2.setTime(d2);
    int diff = 0;
    if (c2.after(c1)) {
        while (c2.after(c1)) {
            c1.add(Calendar.MONTH, 1);
            if (c2.after(c1)) {
                diff++;
            }
        }
    } else if (c2.before(c1)) {
        while (c2.before(c1)) {
            c1.add(Calendar.MONTH, -1);
            if (c1.before(c2)) {
                diff--;
            }
        }
    }
    return diff;
}
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • I noticed these two dates return 0: `2011-01-01 00:00:00 CST --- 2011-02-01 00:00:00 CST`. I think the two if's before `diff++` and `diff--` should include `|| c2.equals(c1)` don't you think? – elysch Jun 10 '15 at 21:38
  • Or, maybe the milliseconds are the problem... No, since it returns 1 just adding the "or equals" – elysch Jun 10 '15 at 21:40
  • This is a linear algorithm. If the input is 2 very far dates, it can take a bit time. At least consider binary search. – android developer Nov 05 '17 at 10:31
3

If you have the years and months in an int value:

months = (year2-year1)*12 + month2 - month1;

Being 12 months in a year.

mclopez
  • 31
  • 1
  • Using an api like `Calendar.add()` would be better – Alexander Mar 30 '15 at 16:29
  • Sorry, but I can't figure out how to solve the problem (count months between two dates) using `Calendar.add()`. Some example, please? Also I make the assumption _if you have int values_ Also, in my opinion, one simple line of code is better than importing a class you don't need for anything else (this only applies if there's no more Calendar objects in the project). – mclopez Apr 09 '15 at 10:56
2

java.time

The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.

Solution using java.time, the modern API: Since you do not need time and timezone to consider in your requirement, LocalDate fits best to your requirement.

Demo:

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

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(monthsBetween(LocalDate.of(2012, 1, 28), LocalDate.of(2012, 2, 1)));
        System.out.println(monthsBetween(LocalDate.of(2012, 2, 27), LocalDate.of(2012, 2, 28)));
        System.out.println(monthsBetween(LocalDate.of(2012, 3, 28), LocalDate.of(2012, 7, 1)));
    }

    static int monthsBetween(final LocalDate fromDate, final LocalDate toDate) {
        return Math.toIntExact(
                    ChronoUnit.MONTHS.between(
                            fromDate.with(TemporalAdjusters.firstDayOfMonth()),
                            toDate.with(TemporalAdjusters.firstDayOfMonth())
                    )
                );
    }
}

Output:

1
0
4

ONLINE DEMO

In case you need to use java.util.Date:

For any reason, if you need to do it for the objects of java.util.Date, you can convert the objects of java.util.Date to Instant using Date#toInstant which can be converted to other java.time types as required.

Demo:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        // Test
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
        sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        
        System.out.println(monthsBetween(sdf.parse("2012-01-28"), sdf.parse("2012-02-01")));
        System.out.println(monthsBetween(sdf.parse("2012-02-27"), sdf.parse("2012-02-28")));
        System.out.println(monthsBetween(sdf.parse("2012-03-28"), sdf.parse("2012-07-01")));
    }

    static int monthsBetween(final Date fromDate, final Date toDate) {
        ZonedDateTime zdtFrom = fromDate.toInstant().atZone(ZoneOffset.UTC);
        ZonedDateTime zdtTo = toDate.toInstant().atZone(ZoneOffset.UTC);
        
        return Math.toIntExact(
                    ChronoUnit.MONTHS.between(
                            zdtFrom.with(TemporalAdjusters.firstDayOfMonth()),
                            zdtTo.with(TemporalAdjusters.firstDayOfMonth())
                    )
                );
    }
}

Output:

1
0
4

ONLINE DEMO

Learn more about java.time, the modern Date-Time API* from Trail: Date Time.

Some important notes:

  1. The constructor, java.util.Date(int, int, int) is deprecated.
  2. java.util.Date and java.util.Calendar month is 0-based i.e. January is month#0. However, SimpleDateFormat considers January as month#1.
  3. An int prefixed with 0 is considered as an octal number which can support digit only in the range of 0-7. You did not encounter a compilation error merely by chance because you have used months only up to 07. Had you used 08 or 09, for example, you would have encountered a compilation error.

* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
1
/**
 * Gets number of months between two dates. 
 * <p>Months are calculated as following:</p>
 * <p>After calculating number of months from years and months from two dates,
 * if there are still any extra days, it will be considered as one more month.
 * For ex, Months between 2012-01-01 and 2013-02-06 will be 14 as 
 * Total Months = Months from year difference are 12 + Difference between months in dates is 1  
 * + one month since day 06 in enddate is greater than day 01 in startDate.
 * </p>
 * @param startDate
 * @param endDate
 * @return
 */
public static int getMonthsBetweenDates(Date startDate, Date endDate)
{
    if(startDate.getTime() > endDate.getTime())
    {
        Date temp = startDate;
        startDate = endDate;
        endDate = temp;
    }
    Calendar startCalendar = Calendar.getInstance(); 
    startCalendar.setTime(startDate);
    Calendar endCalendar = Calendar.getInstance();
    endCalendar.setTime(endDate);

    int yearDiff = endCalendar.get(Calendar.YEAR)- startCalendar.get(Calendar.YEAR);
    int monthsBetween = endCalendar.get(Calendar.MONTH)-startCalendar.get(Calendar.MONTH) +12*yearDiff;

    if(endCalendar.get(Calendar.DAY_OF_MONTH) >= startCalendar.get(Calendar.DAY_OF_MONTH))
        monthsBetween = monthsBetween + 1;
    return monthsBetween;

}
pavanlapr
  • 279
  • 1
  • 5
  • 15
1

I would just get the year and month fields of Calendar instances, convert the years to months and get differences.

private static int monthsBetween(final Date s1, final Date s2) {
    final Calendar d1 = Calendar.getInstance();
    d1.setTime(s1);
    final Calendar d2 = Calendar.getInstance();
    d2.setTime(s2);
    int diff = (d2.get(Calendar.YEAR) - d1.get(Calendar.YEAR)) * 12 + d2.get(Calendar.MONTH) - d1.get(Calendar.MONTH);
    return diff;
}
Leonardo Vidal
  • 553
  • 5
  • 7
0

It will work for leap year also

public static int getNumberOfMonths(Date fromDate, Date toDate) {
    int monthCount = 0;
    Calendar cal = Calendar.getInstance();
    cal.setTime(fromDate);
    int c1date = cal.get(Calendar.DATE);
    int c1month = cal.get(Calendar.MONTH);
    int c1year = cal.get(Calendar.YEAR);
    cal.setTime(toDate);
    int c2date = cal.get(Calendar.DATE);
    int c2month = cal.get(Calendar.MONTH);
    int c2year = cal.get(Calendar.YEAR);
    System.out.println(" c1date:"+c1date+" month:"+c1month+" year:"+c1year);
    System.out.println(" c2date:"+c2date+" month:"+c2month+" year:"+c2year);
    GregorianCalendar grCal = new GregorianCalendar();
    boolean isLeapYear1 = grCal.isLeapYear(c1year);
    boolean isLeapYear2 = grCal.isLeapYear(c2year);
    monthCount = ((c2year - c1year) * 12) + (c2month - c1month);
    if(isLeapYear2 && c2month == 1 && c2date == 29){
        monthCount = monthCount+ ((c1date == 28)?0:1);
    }else if(isLeapYear1 && c1month == 1 && c1date == 29){
        monthCount = monthCount+ ((c2date == 28)?0:1);
    }else{
        monthCount = monthCount+ ((c2date >= c1date)?0:1);
    }
    return monthCount;

}
pankaj
  • 587
  • 1
  • 7
  • 19
0

this will fetch you difference in months

(endCal.get(Calendar.YEAR)*12+endCal.get(Calendar.MONTH))-(startCal.get(Calendar.YEAR)*12+startCal.get(Calendar.MONTH))
Anoop Isaac
  • 932
  • 12
  • 15