Filip
Filip

Reputation: 347

Number of months between 2 java.util.Date, excluding day of month

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?

Upvotes: 6

Views: 38116

Answers (8)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78965

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.

Upvotes: 3

Anoop Isaac
Anoop Isaac

Reputation: 942

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

Upvotes: 0

pankaj
pankaj

Reputation: 587

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;

}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499770

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

Upvotes: 10

Leonardo Vidal
Leonardo Vidal

Reputation: 563

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

Upvotes: 1

mclopez
mclopez

Reputation: 31

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

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

Being 12 months in a year.

Upvotes: 3

pavanlapr
pavanlapr

Reputation: 279

/**
 * 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;

}

Upvotes: 1

Evgeniy Dorofeev
Evgeniy Dorofeev

Reputation: 135992

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

Upvotes: 5

Related Questions