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