1

I need to calculate the difference between two dates as a fraction which can be used for a subsequent BigDecimal comparison using compareTo(....).

The input dates are java.util.Date so i'm trying to convert these to joda.time.DateTime to get the numberOfDays between the two dates and convert the result to a BigDecimal for a later comparison.

If I run the following with a startServiceDate of 30/03/2019 and endServiceDate of 01/03/2021, I get a result of 1.

import java.util.Date;
import org.joda.time.DateTime;
import org.joda.time.Days;
        
private BigDecimal getNumberOfYearsService(Date startServiceDate, Date endServiceDate) {
    DateTime startServiceDateTime = new DateTime(startServiceDate);
    DateTime endServiceDateTime = new DateTime(endServiceDate);
    int numberOfDays = Days.daysBetween(startServiceDateTime, endServiceDateTime).getDays();
    return new BigDecimal(numberOfDays/365);
}

After updating the code to return new BigDecimal(numberOfDays/365).setScale(2,RoundingMode.HALF_UP);, i get a result of 1.00 but was expecting a result of 1.92 if the numberOfDays is 702

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Orby
  • 428
  • 1
  • 9
  • 24
  • 3
    Wondering why you're not using `java.time`…? – g00se Aug 25 '21 at 13:17
  • Is it reliable? My understanding is JodaTime is best API for getting the difference between two dates – Orby Aug 25 '21 at 13:19
  • [It is even recommended by the author of JodaTime](https://stackoverflow.com/questions/29750221/is-joda-time-deprecated-with-java-8-date-and-time-api-java-time), so I think it is the most reliable since Java 8. – deHaar Aug 25 '21 at 13:30
  • apologies, i should have mentioned i'm fixing a bug in a legacy application and need to use Java 1.6! – Orby Aug 25 '21 at 13:33
  • 1
    OK, that means you can use the [ThreeTen Backport](https://www.threeten.org/threetenbp/) to make functionality of `java.time` available in Java 6 and 7. – deHaar Aug 25 '21 at 13:35
  • OK thanks, can i not use JodaTime to achieve the same as this is available to me already. – Orby Aug 25 '21 at 13:38
  • 1
    I think you can, but JodaTime is a library, too, which is not up to date. – deHaar Aug 25 '21 at 13:38
  • ok thanks deHaar, i wasn't aware JodaTime isn't up to date, is it not supported anymore? The version in the application is 2.7 so its fairly old. – Orby Aug 25 '21 at 14:00
  • *JodaTime is best API for getting the difference between two dates* That was true up to 2014. java.time is developed by the same lead developer, Stephen Colebourne, so is regarded as the successor of Joda-Time. And is preferred for differences between dates and times. – Ole V.V. Aug 25 '21 at 14:22
  • From the [Joda-Time home page](https://www.joda.org/joda-time/): *Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to `java.time` (JSR-310).* – Ole V.V. Aug 25 '21 at 14:23
  • 2
    One hint: The result you get may be correct the way you are using JodaTime, but **not** the way you are using a `BigDecimal` because you don't `divide`, but in fact you create a `new BigDecimal(1)` due to `702 / 365` resulting in `1`. That's why you receive a `1.00` instead of a `1.92`. – deHaar Aug 25 '21 at 14:24
  • 1
    [*Joda-Time*](https://www.joda.org/joda-time/) is indeed maintained. [Frequent releases](https://www.joda.org/joda-time/changes-report.html) are made, mainly to update the nested [*tzdata*](https://en.wikipedia.org/wiki/Tz_database) tracking time zone rules. You can continue to use Joda-Time if you wish, and can even [purchase a support plan](https://tidelift.com/subscription/pkg/maven-joda-time-joda-time?utm_source=maven-joda-time-joda-time&utm_medium=referral&utm_campaign=enterprise). But to be future-ready with an API nearly identical to *java.time*, use *ThreeTen-Backport* in Java 6. – Basil Bourque Aug 25 '21 at 15:50
  • 1
    Thanks for the hint deHaar, i've updated my code to return `BigDecimal.valueOf(numberOfDays).divide(BigDecimal.valueOf(365), 2, RoundingMode.HALF_UP);` and it seems to have done the trick! – Orby Aug 25 '21 at 20:51

1 Answers1

1

BigDecimal.divide() and java.time through ThreeTen Backport

I recommend that you use java.time for your date and time work. My suggestion is:

private static final BigDecimal MILLISEONDS_PER_YEAR_AVERAGE
        = new BigDecimal(ChronoUnit.YEARS.getDuration().toMillis());

private static BigDecimal getNumberOfYearsService(Date startServiceDate, Date endServiceDate) {
    long millisBetween = ChronoUnit.MILLIS.between(startServiceDate.toInstant(),
                                                   endServiceDate.toInstant());
    return new BigDecimal(millisBetween)
            .divide(MILLISEONDS_PER_YEAR_AVERAGE, 7, RoundingMode.HALF_UP);
}

Let’s try it out with your example dates:

    ZoneId zone = ZoneId.of("America/Scoresbysund");
    Date start = Date.from(LocalDate.of(2019, Month.MARCH, 30).atStartOfDay(zone).toInstant());
    Date end = Date.from(LocalDate.of(2021, Month.MARCH, 1).atStartOfDay(zone).toInstant());
    BigDecimal yearsWithFraction = getNumberOfYearsService(start, end);
    System.out.println(yearsWithFraction);

Output is:

1.9220107

As you know, years haven’t got the same length. ChronoUnit.YEARS.getDuration() gives us an estimated year length (365 days 5 hours 49 minutes 12 seconds).

In my code I am taking into account that the old-fashioned Date class has millisecond precision. If you only want to take full days into account, you can probably modify the code accordingly. For precision in the calculation you will still want to convert the estimated length of a year if not into milliseconds, then seconds.

The middle argument to BigDecimal.divide(), 7, specifies that I want the result rounded to 7 decimal places. Specify any other number you want.

I am using java.time, the modern Java date and time API. If you prefer to use Joda-Time, you can probably write a very similar solution. If Joda-Time doesn’t provide an estimated year length, steal the amount I just gave and hardcode it into your code.

What went wrong in your code?

It has been said in the comments already: numberOfDays/365 in an integer division and gives an integer result. 702 divided by 365 yields 1 with a remainder of 337. The remainder is discarded. So you are converting the 1 to a BigDecimal with the value if 1. You can set its scale and get for example 1.00, but you cannot get the fraction back that you have discarded. Instead you need to perform the division in a type that supports fractions; float, double or BigDecimal.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Not only does this answer solve the problem correctly but also describes the solution in a simple way. Also, undoubtedly using `java.time`, the [modern Date-Time API](https://www.oracle.com/technical-resources/articles/java/jf14-Date-Time.html) must be the way forward. – Arvind Kumar Avinash Aug 25 '21 at 19:53