Iterate over dates range (the scala way)

后端 未结 7 1587
逝去的感伤
逝去的感伤 2021-02-04 10:26

Given a start and an end date I would like to iterate on it by day using a foreach, map or similar function. Something like

(DateTime.now to DateTime.now + 5.day         


        
相关标签:
7条回答
  • 2021-02-04 11:05

    Solution with java.time API using Scala

    Necessary import and initialization

    import java.time.temporal.ChronoUnit
    import java.time.temporal.ChronoField.EPOCH_DAY
    import java.time.{LocalDate, Period}
    
    val now = LocalDate.now
    val daysTill = 5
    

    Create List of LocalDate for sample duration

    (0 to daysTill)
      .map(days => now.plusDays(days))
      .foreach(println)
    

    Iterate over specific dates between start and end using toEpochDay or getLong(ChronoField.EPOCH_DAY)

    //Extract the duration
    val endDay = now.plusDays(daysTill)
    val startDay = now
    
    val duration = endDay.getLong(EPOCH_DAY) - startDay.getLong(EPOCH_DAY)
    
    /* This code does not give desired results as trudolf pointed
    val duration = Period
      .between(now, now.plusDays(daysTill))
      .get(ChronoUnit.DAYS)
    */
    
    //Create list for the duration
    (0 to duration)
      .map(days => now.plusDays(days))
      .foreach(println)
    
    0 讨论(0)
  • 2021-02-04 11:06

    you can use something like that:

     object Test extends App {
       private val startDate: DateTime = DateTime.now()
       private val endDate: DateTime = DateTime.now().plusDays(5)
       private val interval: Interval = new Interval(startDate, endDate)
       Stream.from(0,1)
             .takeWhile(index => interval.contains(startDate.plusDays(index)))
             .foreach(index => println(startDate.plusDays(index)))
     }
    
    0 讨论(0)
  • 2021-02-04 11:07

    For just iterating by day, I do:

    Iterator.iterate(start) { _ + 1.day }.takeWhile(_.isBefore(end))
    

    This has proven to be useful enough that I have a small helper object to provide an implicit and allow for a type transformation:

    object IntervalIterators {
      implicit class ImplicitIterator(val interval: Interval) extends AnyVal {
        def iterateBy(step: Period): Iterator[DateTime] = Iterator.iterate(interval.start) { _ + step }
            .takeWhile(_.isBefore(interval.end))
    
        def iterateBy[A](step: Period, transform: DateTime => A): Iterator[A] = iterateBy(step).map(transform)
    
        def iterateByDay: Iterator[LocalDate] = iterateBy(1.day, { _.toLocalDate })
    
        def iterateByHour: Iterator[DateTime] = iterateBy(1.hour)
      }
    }
    

    Sample usage:

    import IntervalIterators._
    
    (DateTime.now to 5.day.from(DateTime.now)).iterateByDay // Iterator[LocalDate]
    
    (30.minutes.ago to 1.hour.from(DateTime.now)).iterateBy(1.second)  // Iterator[DateTime], broken down by second
    
    0 讨论(0)
  • 2021-02-04 11:07

    This answer fixes the issue of mrsrinivas answer, that .get(ChronoUnits.DAYS) returns only the days part of the duration, and not the total number of days.

    Necessary import and initialization

    import java.time.temporal.ChronoUnit
    import java.time.{LocalDate, Period}
    

    Note how above answer would lead to wrong result (total number of days is 117)

    scala> Period.between(start, end)
    res6: java.time.Period = P3M26D
    
    scala> Period.between(start, end).get(ChronoUnit.DAYS)
    res7: Long = 26
    

    Iterate over specific dates between start and end

    val start = LocalDate.of(2018, 1, 5)
    val end   = LocalDate.of(2018, 5, 1)
    
    // Create List of `LocalDate` for the period between start and end date
    
    val dates: IndexedSeq[LocalDate] = (0L to (end.toEpochDay - start.toEpochDay))
      .map(days => start.plusDays(days))
    
    dates.foreach(println)
    
    0 讨论(0)
  • 2021-02-04 11:09

    In this case, the Scala way is the Java way:

    When running Scala on Java 9+, we can use java.time.LocalDate::datesUntil:

    import java.time.LocalDate
    import collection.JavaConverters._
    
    // val start = LocalDate.of(2019, 1, 29)
    // val end   = LocalDate.of(2018, 2,  2)
    start.datesUntil(end).iterator.asScala
    // Iterator[java.time.LocalDate] = <iterator> (2019-01-29, 2019-01-30, 2019-01-31, 2019-02-01)
    

    And if the last date is to be included:

    start.datesUntil(end.plusDays(1)).iterator.asScala
    // 2019-01-29, 2019-01-30, 2019-01-31, 2019-02-01, 2019-02-02
    
    0 讨论(0)
  • 2021-02-04 11:21
    import java.util.{Calendar, Date}
    import scala.annotation.tailrec
    
    /** Gets date list between two dates
      *
      * @param startDate  Start date
      * @param endDate    End date
      * @return           List of dates from startDate to endDate
      */
    def getDateRange(startDate: Date, endDate: Date): List[Date] = {
      @tailrec
      def addDate(acc: List[Date], startDate: Date, endDate: Date): List[Date] = {
        if (startDate.after(endDate)) acc
        else addDate(endDate :: acc, startDate, addDays(endDate, -1))
      }
    
      addDate(List(), startDate, endDate)
    }
    
    /** Adds a date offset to the given date
      *
      * @param date       ==> Date
      * @param amount     ==> Offset (can be negative)
      * @return           ==> New date
      */
    def addDays(date: Date, amount: Int): Date = {
      val cal = Calendar.getInstance()
      cal.setTime(date)
      cal.add(Calendar.DATE, amount)
      cal.getTime
    }
    
    0 讨论(0)
提交回复
热议问题