How to create date ranges in scala?

Hi how’s it going?

In python, I’m using this code to create date range bins, along with the number of bins per date range passed in through the ‘freq’ argument.

bin1 =pd.date_range(start='1/1/1900', end='12/31/1999', freq='7Y')
bin2=pd.date_range(start='1/1/2000', end='12/31/2020', freq='7M')

How would I do this in scala?

1 Like

There is nothing pretty built in. You can make something ad hoc. Quick sketch:

def dateRange(start: LocalDateTime, end: LocalDateTime, increment: LocalDateTime => LocalDateTime) =
  Iterator.unfold(start)(next => Option(increment(next)).filter(_.isBefore(end)))

Compiles in my mind, wrote it on my phone, ymmv

1 Like

You’ll probably want takeWhile instead of filter though. I think filter will go in an infinite loop. And when you don’t end the iteration inside the unfold you can use iterate instead.

Iterator.iterate(start)(increment).takeWhile(_.isBefore(end))
1 Like

I stumbled over this, too, but it filters the Option, not the Iterator. :slight_smile: For #unfold(), it’d have to produce an Option[(LocalDate, LocalDate)], though, e.g.

Iterator.unfold(start)(next => Some(increment(next)).filter(_.isBefore(end)).map(next -> _))

With this, the #iterate()/#takeWhile() variant certainly looks more concise.

2 Likes

You may like this answer, I went with a LazyList[_] since you probably don’t always want the whole thing in memory, I believe the above iterator solutions do the same. I also decorated it a bit so that you can use either LocalDate, LocalDateTime, or ZonedDateTime. I am using asInstanceOf, which I rarely use, and everyone should rarely use, but since I am casting to the same thing, I found no harm, correct me if I am mistaken.

Anyway, try this:

import java.time._
import java.time.temporal._
case class DateRange[T <: Temporal](from:T, to:T) {
      def every(i:Int, chronoUnit:ChronoUnit)(implicit ord:Ordering[T]):LazyList[T] =
           LazyList.iterate(from)(t => t.plus(i, chronoUnit).asInstanceOf[T]).takeWhile(ord.lteq(_, to))
}

To use it, try some of these samples. Keep in mind, lazy lists would require using take if you want some elements:

val result2 = DateRange(from = LocalDate.of(2010, 1, 10), to = LocalDate.of(2011, 3, 13)).every(3, ChronoUnit.DAYS)
val result3 = DateRange(from = LocalDateTime.of(2010, 1, 10, 12, 0, 1), to = LocalDateTime.of(2011, 3, 13, 13, 4, 50)).every(3, ChronoUnit.DAYS)
val result4 = DateRange(from = ZonedDateTime.of(2010, 1, 10, 12, 0, 1, 0, ZoneId.of("America/Los_Angeles")), to = ZonedDateTime.of(2011, 3, 13, 13, 4, 50, 0, ZoneId.of("America/Denver"))).every(1, ChronoUnit.YEARS)

println(result2.take(10).toList)
println(result3.take(10).toList)
println(result4.take(10).toList)

I hope you really enjoy Scala. :smile:

Spark sql has Sequence function which might solve your problem with less line of code , if your Scala application no need to be fast for business requirement you can use spark libraries inside Scala app and can use those functions by running spark on local mode.

Examples:

> SELECT sequence(1, 5);
 [1,2,3,4,5]
> SELECT sequence(5, 1);
 [5,4,3,2,1]
> SELECT sequence(to_date('2018-01-01'), to_date('2018-03-01'), interval 1 month);
 [2018-01-01,2018-02-01,2018-03-01]

Since: 2.4.0

In this specific case it should be fine, since the contract for #plus() states: “Returns an object of the same type as this object with the specified period added.” (And all existing Temporal implementations in java.time seem to document compliance through a covariant return type.)

If we were only given the API signatures for Temporal, there could be implementations that break this assumption (while otherwise complying with the contract, AFAICS), e.g.

class TemporalWrapper[T <: Temporal](val t: T) extends Temporal {
  override def plus(amountToAdd: Long, unit: TemporalUnit): Temporal =
    t.plus(amountToAdd, unit)
  // implement all other methods by delegating to t, as well
}
1 Like

Thanks so much for the replies guys