1

If I calculate the difference between 2 LocalDate's in java.time using:

Period p = Period.between(testDate, today);

Then I get an output with the number of years, months, days like:

Days = 9
Months = 6
Years = 18

Does anyone know a clean way to represent that as a decimal type value (ie, above would be something around 18.5...)?

Luke
  • 1,218
  • 1
  • 24
  • 40
  • 2
    this might help you: https://stackoverflow.com/questions/1555262/calculating-the-difference-between-two-java-date-instances – Ryan Turnbull Oct 02 '17 at 20:58
  • 1
    The problem is that those units don't have a fixed length. A year can have 365 or 366 days, a month can have from 28 to 31 days, and even days don't always have 24 hours due to Daylight Saving changes (a `LocalDate` would ignore that, but anyway...) Any calculation you do will be an arbitrary approximation –  Oct 02 '17 at 21:41

3 Answers3

7

Please do not do this.

Representing the difference between two dates as a 'number of years' multiplier is problematic because the average length of a year between two dates is dependent on which dates you are comparing. It's easy to get this wrong, and it's much harder to come up with all the test cases necessary to prove you got it right.

Most programmers should never perform date/time calculations manually. You are virtually guaranteed to get it wrong. Seriously, there are so many ways things can go horribly wrong. Only a handful of programmers on the planet fully understand the many subtleties involved. The fact that you are asking this question proves that you are not one of them, and that's okay--neither am I. You, along with the vast majority of us, should rely on a solid Date/Time API like java.util.time.

If you really need a single numeric value, then the safest option I can think of is to use the number of days, because the LocalDate API can calculate that number for you:

long differenceInDays = testDate.until(today, ChronoUnit.DAYS)

Note that this difference is only valid for the two dates used to produce it. The round-trip conversion is straightforward:

LocalDate today = testDate.plus(differenceInDays, ChronoUnit.DAYS)

Do not attempt to manually convert a Period with year, month, and day components into a whole number of days. The correct answer depends on the dates involved, which is why we want to let the LocalDate API calculate it for us.


When precision isn't important

Based on your comments, precision isn't an issue for you, because you only want to display someone's age to the nearest quarter-year or so. You aren't trying to represent an exact difference in time; only an approximate one, with a rather large margin for error. You also don't need to be able to perform any round-trip calculations. This changes things considerably.

An approximation like @VGR's should be more than adequate for these purposes: the 'number of years' should be accurate to within 3 days (< 0.01 years) unless people start living hundreds of thousands of years, in which case you can switch to double ;).

@Oleg's approach also works quite well, and will give you a date difference in whole quarters, which you can divide by 4 to convert to years. This is probably the easiest solution to get right, as you won't need to round or truncate the result. This is, I think, the closest you will get to a direct solution from java.util.time. The Java Time API (and date/time APIs in general) are designed for correctness: they'll give you whole units, but they usually avoid giving you fractional approximations due to the inherent error involved in floating-point types (there are exceptions, like .NET's System.TimeSpan).

However, if your goal is to present someone's age for human users, and you want greater precision than whole years, I think 18 years, 9 months (or an abbreviated form like 18 yr, 9 mo) is a better choice than 18.75 years.

Mike Strobel
  • 25,075
  • 57
  • 69
  • I agree that representing an amount of time in-program in this way is very undesirable. However there's a requirement to display it in this manner (the age of the person is 18.25, 18.5, etc). So I was just looking for whether the Java Time API had an easy way of figuring this out. I didn't mention it in the question, but since it's not trivial with Java Time, I probably won't go with figuring out an exact decimal representation down to the day but rather checking number of months. – Luke Oct 02 '17 at 22:07
  • 2
    How much precision do you need? If you’re just displaying years to the nearest half or quarter year, and it’s just for display purposes (e.g., you don’t need to be able to do a round-trip), any of the “wrong” answers should be good enough. Even if someone is 120 years old, a naive calculation shouldn’t be off by more than ~1 month. But it might be more ‘friendly’ to display the age as “x years, y months”. – Mike Strobel Oct 02 '17 at 22:22
  • Yeah quarter year would suffice in my particular case. And yeah, most of the time I've come across this in the past it's been "X years, Y months". – Luke Oct 02 '17 at 22:45
  • 1
    Then @VGR’s answer should be plenty good enough (accurate to within ~3 days, regardless of the number of years, I think). Just round to the nearest `0.25f`. – Mike Strobel Oct 02 '17 at 22:48
  • 2
    @Luke Your Question would be much improved if you edited to include these comments’ info. – Basil Bourque Oct 02 '17 at 23:53
  • @Luke See the `YearQuarter` class in the ThreeTen-Extra project. – Basil Bourque Oct 03 '17 at 06:58
  • 1
    Addressed some of the comment discussion in my answer, but only to satisfy my own sense of completeness. You should keep @VGR's answer as 'accepted' if that's the solution you went with. – Mike Strobel Oct 03 '17 at 12:12
7

You mentioned in one of your comments that you need quarter year precision if you need the current quarter you can use IsoFields.QUARTER_YEARS:

double yearAndQuarter = testDate.until(today, IsoFields.QUARTER_YEARS) / 4.0;

This way you will actually use the time api, always get the correct result and @Mike won't have to loathe anything.

Oleg
  • 6,124
  • 2
  • 23
  • 40
4

I would avoid using Period, and instead just calculate the difference in days:

float years = testDate1.until(today, ChronoUnit.DAYS) / 365.2425f;
VGR
  • 40,506
  • 4
  • 48
  • 63
  • 2
    While I am loathe to encourage manual date/time-based calculations, I _think_ using days and a fixed divisor should be okay. However, there's an inherent error involved in using a floating point value. A more complete answer would show how to correct for this when converting back to whole days. – Mike Strobel Oct 02 '17 at 21:31