javascaladatedatetime

Fractional month difference between 2 dates (Scala or Java)


I'm trying to find how many months are between 2 dates. My code is something like this right now

ChronoUnit.MONTHS.between(d1, d2)

The problem is that the result is a long. For example if the dates differ only in a few days I should get a result something like 0.34 instead of 0.

Also I need my code to account for the calendar, I cant assume each month has 31 days.

Diff between 1999-05-12 and 1999-08-24
Assuming all months have 31 days for simplicity

result = (19/31 + 31/31 + 31/31 + 24/31) = 2.793
According to the calendar we replace the 31s with the correct number of days for that specific year and month

Solution

  • Here is my solution:

    public static double monthsBetween(LocalDate start, LocalDate end) {
        if (start.isAfter(end)) throw new IllegalArgumentException("Start must be before end!");
    
        var lastDayOfStartMonth = start.with(TemporalAdjusters.lastDayOfMonth());
        var firstDayOfEndMonth = end.with(TemporalAdjusters.firstDayOfMonth());
        var startMonthLength = (double)start.lengthOfMonth();
        var endMonthLength = (double)end.lengthOfMonth();
        if (lastDayOfStartMonth.isAfter(firstDayOfEndMonth)) { // same month
            return ChronoUnit.DAYS.between(start, end) / startMonthLength;
        }
        long months = ChronoUnit.MONTHS.between(lastDayOfStartMonth, firstDayOfEndMonth);
        double startFraction = ChronoUnit.DAYS.between(start, lastDayOfStartMonth.plusDays(1)) / startMonthLength;
        double endFraction = ChronoUnit.DAYS.between(firstDayOfEndMonth, end) / endMonthLength;
        return months + startFraction + endFraction;
    }
    

    The idea is that you find the last day of start's month (lastDayOfStartMonth), and the first day of end's month (firstDayOfEndMonth) using temporal adjusters. These two dates are very important. The number you want is the sum of:

    Then there is the edge case of when both dates are within the same month, which is easy to handle.

    By using this definition, we preserve the useful property of:

    For all local dates a, b, c where a < b < c, monthsBetween(a,b) + monthsBetween(b,c) == monthsBetween(a, c).

    Also, the number of months between two start-of-months are always a whole number.

    Note that in the first calculation, you have to add one day to lastDayOfStartMonth, because ChronoUnit.between treats the upper bound as exclusive, but we actually want to count it as one day here.