3

Using Java 8 time I am simply trying to figure out the number of days represented in a time range. Consider the following:

LocalDate start = LocalDate.of(2016, Month.MARCH, 28);
LocalDate end = LocalDate.of(2016, Month.MARCH, 31);
Period period = Period.between(start, end);

The number of days in period is 3 which represents the number of days between the 2 dates, inclusive of start and exclusive of end. What I want is the number of days represented by the 2 dates which is actually 4 (March 28, March 29, March 30, March 31).

I know I can just add 1 to the number of days returned from Period.between() but I guess I was surprised that I couldn't find another call to return exactly what I want. Am I missing something or is adding 1 the only solution?

wxkevin
  • 1,634
  • 2
  • 25
  • 44

4 Answers4

5

tl;dr

Always define your spans of time by the Half-Open approach where:

  • Beginning is inclusive.
  • Ending is exclusive.

When you want the four days of March 28, March 29, March 30, March 31, make the beginning March 28 and the ending April 1. Run through those dates starting at the first (March 28th) while going up to, but not including, the last (April 1st).

2016-03-28/2016-04-01

Half-Open

Am I missing something

You may be missing an appreciation for the usefulness of the Half-Open approach in defining spans of time.

Generally, the best practice for defining spans of time is the Half-Open approach. In Half-Open, the beginning is inclusive while the ending is exclusive.

This approach solves the problem of dealing with fractional seconds. Intuitively, many programmers will try to find the last possible moment as the ending of a span of time. But that last moment involves an infinitely divisible last second. You might think, "Well, just go to three decimal place for milliseconds, 12:59.59.999 for the end of noon lunch break, as that is all the resolution I will ever need, and that is the resolution of the legacy java.util.Date class.”. But then you would fail to find matches in your database like Postgres that store date-time values with a resolution of microseconds, 12:59:59.999999. So you decide to use six decimal places of fraction, x.999999. But the start experiencing mismatches with date-time values in the java.time classes, and you learn the offer a resolution of nanoseconds for nine digits of fractional second, x.999999999. You can disembark this carousel of frustrating bugs by using the Half-Open approach where the ending runs up to, but does not include, the next whole second.

I believe you will find consistent use of the Half-Open approach throughout your date-time handling code (whether fractional seconds may be involved or not) will:

  • Make your code easier to read and comprehend.
  • Ease the cognitive load overall.
    Knowing all your spans of time carry the same definition eliminates ambiguity.
  • Reduce bugs.

Examples:

  • A noon lunch period starts at the moment the clock strikes noon (12:00:00) and runs up to, but does not include, the moment when the clock strikes one o’clock. That means 12:00:00 to 13:00:00.
  • A full day starts at the first moment of the day (not always 00:00:00, by the way) and runs up to, but does not include, the first moment of the following day.
  • A week starts on a Monday and runs up to, but does not include, the following Monday. That means seven days in Monday-Monday.
  • A month starts of the first of the month and runs up to, but not including, the first of the following month. So the month of March is March 1 to April 1.

enter image description here

LocalDate

Rather than one adding 1 to get a total of days, define your span of time as Half-Open: beginning-is-inclusive, ending-is-exclusive. If you are trying to represent the four dates of March 28, 29, 30, and 31, then I suggest you define a span of time from March 28 to April 1.

LocalDate start = LocalDate.of( 2016, Month.MARCH, 28 ) ; // inclusive
LocalDate stop = LocalDate.of( 2016, Month.APRIL, 1 ) ; // exclusive

Period

The java.time classes wisely use the Half-Open approach. So the Period.between method treats the ending as exclusive, as noted in the Question. I suggest you go-with-the-flow here rather than fight it. Search Stack Overflow for many more examples of how well this approach works.

Period p = Period.between( start , stop );

p.toString(): P4D

ChronoUnit

If you want a total number of days, such as 45 for a month and a half, use the ChronoUnit enum, an implementation of TemporalUnit. See this Question for discussion.

Again, the java.time classes use the Half-Open approach. So

long daysBetween = ChronoUnit.DAYS.between( start, stop );

4

Live code

See this example code run live at IdeOne.com.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • I have a big problem with the statement "So the month of March is March 1 to April 1." If I suggest that at my job then they would probably consider me crazy. April 1 is next month! While I agree that the current definition of days-calculation is fine (counting how many days to add to a given start) I would nevertheless still consider closed date intervals as the better choice and nearer to what most people think of date intervals intuitively. But question of OP is not related to intervals: day-difference and interval are two different questions/subjects. For time intervals, half-open is ok. – Meno Hochschild Jan 25 '17 at 04:42
  • @MenoHochschild I hear you, and your position is understandable. I would still argue that using half-open consistently eases the cognitive load. And specifically to the case of an entire date-only month, the half-open approach eliminates the issues of (a) how many days in a month (28, 29, 30, or 31) and (b) Leap Year with February. I'm not alone, as `ChronoUnit.DAYS.between` uses half-open in its calculations, as [seen in this code example](http://ideone.com/XOFYuj). And using half-open allows for easy math where adding a month to January 1 takes you to February 1, not Jan. 31. – Basil Bourque Jan 25 '17 at 05:01
2

There doesn't seem to be an inclusive end date method in either Period or LocalDate so it seems that the only thing to do is something like:

Period.between(start, end.plusDays(1))

or

start.until(end.plusDays(1))

(Period.between just calls LocalDate.until)

greg-449
  • 109,219
  • 232
  • 102
  • 145
  • 1
    Or `Period.between(start, end).plusDays(1)` or `start.until(end).plusDays(1)`. I love this API where you can use a dozen different ways to do the same… – Holger Jan 24 '17 at 17:19
1

I belive your day counting differs from java's. Period.between according to documentation: http://docs.oracle.com/javase/8/docs/api/java/time/Period.html#between-java.time.LocalDate-java.time.LocalDate- "The start date is included, but the end date is not." Having that in mind - yes, adding 1 is the only solution.

Kweldulf
  • 76
  • 1
  • 10
0

the signature of the method is

between(LocalDate startDateInclusive, LocalDate endDateExclusive) {

and the method is not oerloaded, you have no choice other than add 1 to the given result...

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97