1

When using java.time in Scala I experienced a strange behavior. I want to calculate the number of months between two dates like this:

import java.time._

Period.between(LocalDate.parse("2015-03-31"), LocalDate.parse("2015-04-30")) 
// java.time.Period = P30D
// I would expect java.time.Period = P1M

Period.between(LocalDate.parse("2015-03-31"), LocalDate.parse("2015-05-01"))
// java.time.Period = P1M1D

Is this a bug or do I have got it all wrong?

org.joda.time works as I would expect it:

import org.joda.time.DateTime
import org.joda.time.Months

Months.monthsBetween( new DateTime().withDate(2015, 3, 31), new DateTime().withDate(2015, 4, 30))
//org.joda.time.Months = P1M

When adding months to a java.time.LocalDate it works fine:

java.time.LocalDate.parse("2015-03-31").plusMonths(1)
// java.time.LocalDate = 2015-04-30
cperriard
  • 13
  • 4
  • 1
    Does it break a clearly stated specification? – Marko Topolnik Sep 14 '16 at 11:27
  • @MarkoTopolnik: I don't know if it does but I would expect that a month can have different numbers of days. – cperriard Sep 14 '16 at 11:33
  • If it breaks specification, it's a bug; if it doesn't, then it only broke your (reasonable) expectation. – Marko Topolnik Sep 14 '16 at 11:35
  • I tried with 04-30 to 05-31, then it said 1M1D.. Makes no sense to me.. I have limited experience with the java time API, but from what I've seen, JodaTime is better. A shame really.. – Tobb Sep 14 '16 at 11:36

3 Answers3

5

This is not a bug, and it is behaving like expected (see also JDK-8152384 and JDK-8037392, which were closed as "Not An Issue"). Joda Time and the Java Time API have different behaviour regarding this. Quoting Stephen Colebourne from the previous bug report:

The OP appears to want a rule where the days are calculated based on the original month length, not the one that results once the month-year difference is applied. The OP is not wrong, its just that its not how we choose to make the calculation in java.time.

Indeed, from Period.between:

The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign. [...] A month is considered to be complete if the end day-of-month is greater than or equal to the start day-of-month.

Between the 31st of March, and the 30th of April, no complete month has elapsed. As such, you have a period containing the number of days between the two dates, which is 30. To have the complete month of April elapsed, you need to add one day to the end date, and make it the 1st of June.

Joda has a different way of calculating the month period. From Months.monthsBetween:

This method calculates by adding months to the start date until the result is past the end date. As such, a period from the end of a "long" month to the end of a "short" month is counted as a whole month.

Joda explicitly takes the variable number of days in a month into account when calculating the number of months between the two dates. Java Time doesn't.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thanks for the links. The fact that the results for Period(startDate, endDate) and Period(endDate, startDate) could differ so much is counter intuitive and a bit scary to be honest :| – Denis Rosca Sep 14 '16 at 12:15
2

I agree that it is a bit unexpected, but it is the correct result if you take into account the javadoc. From the javadoc

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.

The difference comes from what a "complete month" means. In this case 1st April to 1st May (exclusive) is considered a complete month while 31st March to 30th April (exclusive) is not.

Denis Rosca
  • 3,409
  • 19
  • 38
1

I believe the Period.between is returning P30D in the first example because the second parameter is exclusive. This is according to https://docs.oracle.com/javase/8/docs/api/java/time/Period.html#between-java.time.LocalDate-java.time.LocalDate-

public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)

kdrakon
  • 172
  • 1
  • 8
  • I am aware of this. But why is `Period.between(LocalDate.parse("2015-04-30"), LocalDate.parse("2015-05-30"))` returning P1M? – cperriard Sep 14 '16 at 11:44
  • @cperriard because that is one month apart. Why would you expect a period which is a day shorter to be the same? – Peter Lawrey Sep 14 '16 at 11:49
  • @PeterLawrey because the month of April only has 30 days and because `java.time.LocalDate.parse("2015-03-31").plusMonths(1)` returns 2015-04-30. – cperriard Sep 14 '16 at 11:53
  • @cperriard it only returns 2015-04-30 as there is not 31 days so it rounds down, now subtract 1 month and you will get 2015-03-30 not 31. – Peter Lawrey Sep 14 '16 at 12:00
  • @cperriad it looks like some of the confusion comes from `plusMonths`. See the algorithm described here https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#plusMonths-long- The other part of the confusion comes from the definition of a `Period` and ISO-8601 and what constitutes a 'month' (see here http://stackoverflow.com/questions/26265751/what-is-the-value-of-the-iso-8601-duration-p1m-in-seconds) – kdrakon Sep 14 '16 at 12:03