0

I have been using the following code to calculate the difference between two dates and I have been getting an unusual error:

if I pass the following dates:

d1 = 08/12/2017 d2 = 31/07/2022

it returns: 24055..

isn't the result supposed to be return as the number of months?

 public static int CalcDateDiff( java.util.Date  date1,  java.util.Date  date2) {
    if(date1 == null || date2 ==null )
    {
        if(date2 == null)
        {
              Calendar d1 = Calendar.getInstance();
                d1.setTime(date1);
                final Calendar d2 = Calendar.getInstance();
                int diff = (d2.get(Calendar.YEAR) - d1.get(Calendar.YEAR)) * 12 + d2.get(Calendar.MONTH) - d1.get(Calendar.MONTH);

                return diff;
        }
        else
            return -1;
    }
    else
    {

            Calendar d1 = Calendar.getInstance();
            d1.setTime(date1);
            final Calendar d2 = Calendar.getInstance();
            d2.setTime(date2);
            int diff = (d2.get(Calendar.YEAR) - d1.get(Calendar.YEAR)) * 12 + d2.get(Calendar.MONTH) - d1.get(Calendar.MONTH);

            return diff;
        }
}

Thanks in advance!

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
user2597012
  • 581
  • 4
  • 9
  • 28
  • 1
    There is no difference between `31/07/22` and `31/07/22`. – achAmháin Dec 08 '17 at 15:12
  • apologies, updated the value. – user2597012 Dec 08 '17 at 15:13
  • 2
    Please provide a [mcve], and give details about the time zone you're in. There's far too much that could be going on here to be able to help you without seeing more details. – Jon Skeet Dec 08 '17 at 15:14
  • 5
    (You'd get roughly this answer if one date was in 17AD and one was 2022AD, for example.) – Jon Skeet Dec 08 '17 at 15:15
  • Where do you get `date1` and `date2 ` from? – AhmadWabbi Dec 08 '17 at 15:15
  • they are the function parameters from the database. their type is java.util.Date. Also the values are exactly like they are mentioned above. – user2597012 Dec 08 '17 at 15:16
  • 2
    I ran it and got 55 as the output...https://ideone.com/j06to9 I think we need to see more code, or you do more debugging. – achAmháin Dec 08 '17 at 15:18
  • 2
    I get 55 too. - Year, month and day are maybe not the only fields. What do you get, if you print d1.getTimeInMillis() and d2.getTimeInMillis()? – mayamar Dec 08 '17 at 15:38
  • What version of Java are you using? –  Dec 08 '17 at 15:52
  • 3
    **Show how you are creating `date1` and `date2`** –  Dec 08 '17 at 15:53
  • they are the function parameters from the database. their type is java.util.Date. Also the values are exactly like they are mentioned above – user2597012 Dec 08 '17 at 16:01
  • @Artemis, I believe 8 Dec 2017 and 31 July 2022 were intended. The deprecated `Date` constructor understands it the other way around, so you are calculating the difference between Sat Aug 12 2017 and Sun Jul 07 2024. That’s 83 months alright. – Ole V.V. Dec 08 '17 at 16:52

2 Answers2

1

Best Solution:

If you already have java.sql.Date instances then

final Date d1 = new java.sql.Date(2017 - 1900, 12, 8);
final Date d2 = new java.sql.Date(2022 - 1900, 07, 31);

I know that I am using a deprecated constructor in java.sql.Date, that is just for convenience to get what I need in the least amount of lines of code. Never use this constructor in production code!

The simplest most straightforward self documenting way to get what you want:

final long monthsBetween ChronoUnit.MONTHS.between(d1.toLocalDate(),d2.toLocalDate()) + 1;

Because with this method you do not have to twiddle with TimeZone information because it is all guaranteed to be correct for instances of LocalDate created this way.

For some reason only java.sql.Date only has .toLocalDate() which is good for you because that is what you get back from the database.

public LocalDate toLocalDate() Converts this Date object to a LocalDate The conversion creates a LocalDate that represents the same date value as this Date in local time zone

Returns: a LocalDate object representing the same date value Since: 1.8


Comments on corrections to your code:

The correct formula is:

(y2 - y1) * 12 + (m2 - m1) + 1

Also, your code is overly complex and using very old classes.

Java 8 Solution and your Java 7 compatible corrected solution:

public class Q47717075
{
    /*
    https://stackoverflow.com/questions/1086396/java-date-month-difference
     */
    public static void main(@Nonnull final String[] args)
    {
        final Date d1 = new java.sql.Date(2017 - 1900, 12, 8);
        final Date d2 = new java.sql.Date(2022 - 1900, 07, 31);
        System.out.println(CalcDateDiff(d1, d2));
        /* Obtains an instance of Instant from a text string such as 2007-12-03T10:15:30.00Z. */
        System.out.println(intervalInMonths(LocalDate.of(2017,12,8), LocalDate.of(2022,7,31)));
        System.out.println(ChronoUnit.MONTHS.between(d1.toLocalDate(),d2.toLocalDate()) + 1);
    }


    /*
       Alternate Java 8 version
     */
    public static int intervalInMonths(@Nonnull final LocalDate i1, @Nonnull final LocalDate i2)
    {
        final Period p = Period.between(i1, i2);
        return (p.getYears() * 12) + p.getMonths() + 1;
    }

    /**
     * Your versin corrected
     */
    public static int CalcDateDiff(@Nonnull final Date date1, @Nonnull final Date date2)
    {
        final Calendar d1 = Calendar.getInstance();
        d1.setTime(date1);
        final Calendar d2 = Calendar.getInstance();
        d2.setTime(date2);

        return ((d2.get(YEAR) - d1.get(YEAR)) * 12 + (d2.get(MONTH) - d1.get(MONTH)) + 1);
    }
}

The Correct Output is:

56
56
56
  • To continue the discussion from the other answer: I took August 1 and December 1 in America/Montevideo time zone. They have granularity of day and are in the same time zone. Calling your corrected `CalcDateDiff()` from a JVM in America/Santiago time zone yields 6 months. From a JVM in America/Moncton time zone only 4 months. Time zone matters. See my test code [run live on ideone](https://www.ideone.com/4v6rem). – Ole V.V. Dec 10 '17 at 19:57
  • I have said this over and over and over and over from the beginning on the other answer! As long as the timezones are **the same for both instances** the answers will be correct, you do not get it, you do not read for comprehension, just stop. –  Dec 11 '17 at 08:40
  • Exactly what do you mean by the time zone being the same for both instances (when a `Date` doesn’t contain a time zone)? Is the time zone for the two instances in my test code the same, or is it different, and in the latter case, what is the difference? – Ole V.V. Dec 11 '17 at 09:10
0

tl;dr

ChronoUnit.MONTHS.between(                                           // Use enum method to calculate elapsed time.
    date1.toInstant().atZone( ZoneId.of( "Africa/Casablanca " ) ) ,  // Convert from legacy `Date` class to modern `java.time.Instant` class. Adjust from UTC to a specific time zone. 
    date2.toInstant().atZone( ZoneId.of( "Africa/Casablanca " ) ) 
)                                                                    // Return a number of months elapsed. 

Details

The Answer by Roberson is good. Here's an alternative.

Your use of the troublesomeDate class is now outmoded. That legacy class is replaced by Instant in the java.time package. Both represent a moment, a point on the timeline in UTC. The modern class has a resolution of nanoseconds rather than milliseconds.

Convert using new methods added to the old classes.

Instant instant = myUtilDate.toInstant() ;

To determine a number of months, we need dates. Determining a date requires a time zone. For any given moment the date varies around the globe by zone.

ZoneId z = ZoneId.of( "Pacific/Auckland " ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

The ChronoUnit enum offers the between method for calculating elapsed time.

long months = ChronoUnit.MONTHS.between( zdt1 , zdt2 ) ;
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • the timezone information is just added complexity that is irrelevant to getting months between dates. TimeZone information is only relevant when dealing with intervals smaller than a day. `LocalDate` suffices in all cases with intervals larger than `Hour`. –  Dec 08 '17 at 17:00
  • @OleV.V. - TimeZone is irrelevant if you are working with calculations that do not have resolution below that of days as long as both have the same timezone, which in the case of `java.util.Date` is guaranteed. This answer is just a bunch of noise about timezone information that is completely and utterly irrelevant to the calculation at hand. You get the same exact output when you use `LocalDate` as my answer shows, which avoids all this noise. –  Dec 10 '17 at 02:20
  • @JarrodRoberson, you can believe that if you prefer, but it’s not true. If one time zone has summer time (DST) and another one hasn’t, or the crossover dates are not the same, it may still make a difference which one you pick (or which one you let your JVM pick for you). We’re in the corner cases where bugs are not easily discovered. Also, if your method only works with `Date`s at midnight in some time zone, you should at least state that. – Ole V.V. Dec 10 '17 at 06:52
  • @OleV.V. - what part of ***as long as they both have the same timezone*** do you not comprehend? My method does not matter what the times are of either instance ***as long as they both have the same timezone*** you will get the same number of **months** because a *month* is more than any of the time components. It is simple by default `java.util.Date` and thus `java.sql.Date` and `LocalDate` all have the **EXACT SAME TIMEZONE** because you can not set it, it defaults to the local timezone. So *your ignorance is not a measure of my knowledge*, this is simple logic based on the documentation. –  Dec 10 '17 at 06:56