2

I see that java.time.Period with java.time.LocalDates works mostly as desired and I can use it to get the duration between two dates in years, months and days but I found an example that is not working (not as I expected it), like following:

val test1 = LocalDate.of(2023, 1, 31)
val test2 = LocalDate.of(2023, 1, 28)
val testToday = LocalDate.of(2023, 3, 9)

val period1 = Period.between(test1, testToday)
val period2 = Period.between(test2, testToday)

L.d { "test1 = $test1 | test2 = $test2 | today = $testToday" }
L.d { "period1 = $period1 | period2 = $period2" }

Output is following:

period1 = P1M9D | period2 = P1M9D
test1 = 2023-01-31 | test2 = 2023-01-28 | today = 2023-03-09

Problem

The solution above tells me following:

  • Duration between 2023-01-31 and 2023-03-09 => 1 month and 9 days (P1M9D)
  • Duration between 2023-01-28 and 2023-03-09 => 1 month and 9 days (P1M9D)

=> but this is wrong, the second one should show 3 days more as duration... Is there an explanation for this?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
prom85
  • 16,896
  • 17
  • 122
  • 242

1 Answers1

1

The "problem" is the way Period.between performs its calculation:

The start date is included, but the end date is not. The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign. The number of months is then split into years and months based on a 12 month year. A month is considered if the end day-of-month is greater than or equal to the start day-of-month. For example, from 2010-01-15 to 2011-03-18 is one year, two months and three days.

This results in 2023-01-28 until 2023-01-01 counting as 1 month and 1 day (P1M1D), but so will 2023-01-29, 30, and 31. This is because 2023-01-31 plus 1 month is 2023-02-31, which is invalid, so it uses 2023-02-28 (and same for 29 and 30, and of course 28). In other words, there is 1 full month, which ends on 2023-02-28, and then there are 9 more days until 2023-03-09 (exclusive).

This conforms to the behaviour documented for LocalDate.plusMonths:

This method adds the specified amount to the months field in three steps:

  1. Add the input months to the month-of-year field
  2. Check if the resulting date would be invalid
  3. Adjust the day-of-month to the last valid day if necessary

For example, 2007-03-31 plus one month would result in the invalid date 2007-04-31. Instead of returning an invalid result, the last valid day of the month, 2007-04-30, is selected instead.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 1
    thanks for the explanation. I was totally aware of exclusion of the end date and of how `LocalDate.plusMonths` works (which makes sense) but when calculation the `Period` between two dates I would consider another logic as correct (I still feel like the logic is in `Period` is incorrect) - please check the last paragraph of my question, I added what I'd consider a "natural" logic => can I get this result with `java.time` or do I need to write this logic myself? – prom85 Mar 09 '23 at 08:40
  • 1
    @prom85 I have rolled back that edit, because that is known as "moving the goalposts". In any case, I'm not aware of a "standard" way of doing this. – Mark Rotteveel Mar 09 '23 at 08:44
  • that's ok. It just did not fit into a comment... Thanks for answering it. – prom85 Mar 09 '23 at 08:46
  • 2
    The “correct” way to calculate months and days across months of uneven lengths and involving dates near the end of the month is a never-ending discussion. Each date library typically has its own way. I believe that no way exists that would be considered correct and adequate for all purposes and would satisfy everyone. – Ole V.V. Mar 11 '23 at 15:58
  • And it shouldn’t be too hard to hand code a method that calculates it in the way that you prefer and stuffs the result into a `Period`. – Ole V.V. Mar 11 '23 at 16:04