Joda Time and Java8 Time difference
Asked Answered
P

1

29

I'm looking for a solution to calculate the months between two date. I think joda or java8 time can do it. But when I compare them I found something really weird.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import org.joda.time.Months;

public class DateMain {
    public static void main(String[] args) throws ParseException {

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    Date d1 = simpleDateFormat.parse("2017-01-28 00:00:00.000");
    Date d2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 1");
    System.out.println("joda time api result: " + monthsBetweenJoda(d1, d2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(d1, d2) + " month");

    Date dd1 = simpleDateFormat.parse("2017-01-29 00:00:00.000");
    Date dd2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 2");
    System.out.println("joda time api result: " + monthsBetweenJoda(dd1, dd2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(dd1, dd2) + " month");
}

public static int monthsBetweenJoda(Date fromDate, Date toDate) {
    if (fromDate == null || toDate == null) {
        throw new IllegalArgumentException();
    }
    org.joda.time.LocalDateTime fromLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(fromDate);
    org.joda.time.LocalDateTime toLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(toDate);
    Months months = Months.monthsBetween(fromLocalDateTime, toLocalDateTime);
    return months.getMonths();
}

public static long monthsBetweenJava8(Date fromDate, Date toDate) {
    if (fromDate == null || toDate == null) {
        throw new IllegalArgumentException();
    }
    LocalDateTime ldt1 = fromDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    LocalDateTime ldt2 = toDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    return ChronoUnit.MONTHS.between(ldt1, ldt2);
}

}

the output is below:

Test Cast 1
joda time api result: 1 month
java8 time api result: 1 month
Test Cast 2
joda time api result: 1 month
java8 time api result: 0 month

I feel very confused with the test case 2, and which one is reasonable?

Sorry, it's my first question here. Completed code attached.

Perni answered 15/9, 2017 at 5:47 Comment(8)
Joda looks wrong to me. Its documentation says the number of whole months, but your test is inconsistent with that.Photokinesis
but the result of joda looks more reasonable, when plus 1 month to 2017-01-29 or 2017-01-30 , it always return 2017-02-28, so months between 2017-01-29 and 2017-02-28 should be 1 rignt?Perni
@JoeC: I suspect it's taking the largest number such that fromDate.plusMonths(x) <= toDate, which is reasonable when adding 1 month to January 29th still gives February 28th. Basically calendar arithmetic is weird.Liquor
Does this occur on other months. It could be that joda time it looking at the length of the current month (feb) with 28d and saying the it's still 1 month.Ever
Are you using java.sql.Date or java.util.Date? Please document this fact, and show how you generate your Date objects being passed as the pair of arguments. And show how your generated your output strings. Your output does not match that of the toString method on either java.sql.Date or java.util.Date. Provide us with a MCVE. Your code as shown is not complete, so we cannot replicate your scenario.Silicium
Shouldn't the joda result be more correct? There is no 29th in February 2017 - so there should be exactly 1 month between 29th January 2017 until 28th February 2017? Do we have a bug in the JDK? LocalDate.of(2017, 1, 29).plusMonths(1L) results in java.time.LocalDate = 2017-02-28 but getting the month interval between them results in 0. Could be a bugCorruption
Hm. The JavaDocs ofr ChonoUnit.between don't explicitly mention how the method deals with units of variable length (like DAYS, MONTHS or YEARS or almost all of them actually).Chiron
My implementation ends up invoking LocalDateTime.until which (for non-time-based units) in turn invokes LocalDate.until. However, it may reduce the end date by one day if end.time.isBefore(time) - maybe this is the case in your test (although it doesn't look like it from your output)?Chiron
W
39

Joda-Time and Java 8 java.time.* have different algorithms for determining the number of months between.

java.time.* only declares a month has passed once the day-of-month is greater (or in the next month). From 2017-01-29 to 2017-02-28 it is clear that the second day-of-month (28) is less than the first (29) so a month has not yet been completed.

Joda-Time declares a month has passed because adding 1 month to 2017-01-29 will yield 2017-02-28.

Both are plausible algorithms, but I believe the java.time.* algorithm to be more in tune with what people expect (which is why I chose a different algorithm when writing java.time.*).

Wuhu answered 15/9, 2017 at 9:49 Comment(1)
Makes me uneasy that this was the decision in Java 8 time. So every time we have a February 28, 1 month will only have elapsed by 1 March from January 29 - seems wrong to me, especially since adding a month to January 29 results in February 28. How do I deal with this so I can just tell myself - "let it go"?Corruption

© 2022 - 2024 — McMap. All rights reserved.