1. Introduction

When we’re working with date ranges, there are times that we want to generate a list that includes all the dates within that range. In this tutorial, we’ll explore different ways to generate a list of dates between a given range.

2. Problem Statement

We’re given a start date and an end date. The program should generate a list of dates between them, including the start and end dates. Moreover, we’ll use java.time.LocalDate to define the start and end dates.

We should be aware that we’re solely using the features provided by the standard library and not any third-party libraries. For better understanding, we’ll stick to the same start and end dates throughout the entire tutorial:

val startDate = LocalDate.parse("2023-10-28")
val endDate = LocalDate.parse("2023-11-03")

We should generate this output:

List(2023-10-28, 2023-10-29, 2023-10-30, 2023-10-31, 2023-11-01, 2023-11-02, 2023-11-03)

3. Approaches for Generating Date Ranges

In this section, we’ll go over several approaches that will generate the date ranges.

3.1. Using Range and Days.between()

We can first identify the number of days between the start and end dates. We can then use a range to generate the list of dates by using the plusDays() method from LocalDate:

val noOfDays = ChronoUnit.DAYS.between(startDate, endDate) + 1
val dates = (0 until noOfDays.toInt).map(startDate.plusDays(_)).toList
assert(dates == expectedDates)

Notably, between() calculates the number of days excluding the end date. As a result, we increment it by 1 to include that as well.

3.2. Using Range and LocalDate.ofEpochDay()

We can use the epochDays() method to get the epoch day count and create a range using that. We can then use the ofEpochDay() method to convert the epoch day count back to LocalDate:

val datesEpoch = startDate.toEpochDay.to(endDate.toEpochDay).map(LocalDate.ofEpochDay).toList
assert(datesEpoch == expectedDates)

In this case, we don’t need to increment the count, unlike in the previous section.

3.3. Using Recursion

We can also use tail recursion to generate the list of dates between a range:

def recursiveDateList(startDate: LocalDate, endDate: LocalDate): List[LocalDate] = {
  @tailrec
  def findNextDate(currentDate: LocalDate, accDates: List[LocalDate]): List[LocalDate] = {
    if (currentDate.isAfter(endDate)) {
      accDates
    } else {
      findNextDate(currentDate.plusDays(1), accDates :+ currentDate)
    }
  }
  findNextDate(startDate, Nil)
}

We can run this method and verify if the same results are generated:

val recDateList = recursiveDateList(startDate, endDate)
assert(recDateList == expectedDates)

3.4. Using Range and foldLeft()

Another way to generate the list of dates is using the foldLeft() method on the range:

val noOfDays = ChronoUnit.DAYS.between(startDate, endDate) + 1
val foldedDateList = (0 until noOfDays.toInt).foldLeft(List.empty[LocalDate]) {
  (acc, incr) =>
    acc :+ startDate.plusDays(incr)
}
assert(foldedDateList == expectedDates)

This is very similar to the earlier technique. However, it uses foldLeft() instead of map() to increment the dates.

3.5. Using Iterator.iterate()

Yet another way of generating a list of dates is using the Iterator.iterate() method. The iterate() method takes a starting value and a function that applies to the initial value to generate the next value.

Let’s see how we can utilize it to generate a list of dates:

val iteratorDates = Iterator
  .iterate(startDate)(_.plusDays(1))
  .takeWhile(!_.isAfter(endDate))
  .toList
assert(iteratorDates == expectedDates)

Here, we apply plusDays() to generate the next value of the iterator. Additionally, we use the takeWhile() condition to exit the iterator and get the list of dates.

3.6. Using tabulate()

We can also use the tabulate() method from List to generate the list of dates:

val noOfDays = ChronoUnit.DAYS.between(startDate, endDate) + 1
val datesTabulated = List.tabulate(noOfDays.toInt)(startDate.plusDays(_))
assert(datesTabulated == expectedDates)

The tabulate() method takes the dimensions of the list and a function as parameters. Additionally, each value of the dimension list is applied to the function, which is passed as the second parameter. Consequently, we can utilize this to generate a very concise method to generate the list of dates.

4. Conclusion

In this article, we discussed many different ways to generate a list of dates between the given range. It’s important to note that these options represent a subset, and other methods may also exist to achieve similar outcomes.

As always, the sample code used in this article is available over on GitHub.

Comments are closed on this article!