rlubridate

Generate date sequence by months


This is the result I want:

library(lubridate)

res <- structure(
  c(
    16160, 16251, 16343, 16435, 16525, 16616, 16708, 
    16800, 16891, 16982, 17074, 17166, 17256, 17347, 17439, 17531, 
    17621, 17712, 17804, 17896, 17986, 18077, 18169, 18261, 18352, 
    18443, 18535, 18627, 18717, 18808, 18900
  ), 
  class = "Date"
)

res
#>  [1] "2014-03-31" "2014-06-30" "2014-09-30" "2014-12-31" "2015-03-31"
#>  [6] "2015-06-30" "2015-09-30" "2015-12-31" "2016-03-31" "2016-06-30"
#> [11] "2016-09-30" "2016-12-31" "2017-03-31" "2017-06-30" "2017-09-30"
#> [16] "2017-12-31" "2018-03-31" "2018-06-30" "2018-09-30" "2018-12-31"
#> [21] "2019-03-31" "2019-06-30" "2019-09-30" "2019-12-31" "2020-03-31"
#> [26] "2020-06-30" "2020-09-30" "2020-12-31" "2021-03-31" "2021-06-30"
#> [31] "2021-09-30"

I have to generate it as a sequence though.

I have managed to get the result using a while loop:

dateA <- min(res)
dateB <- max(res)
by <- months(3)

# initialize a vector to contain all dates:
myres <- dateA

while (TRUE) {
  n <- length(myres)
  next_date <- ceiling_date(myres[n] %m+% by, unit = by) - 1
  
  # if next_date is less than dateB, add it to myres & continue looping:
  if (next_date < dateB) {
    myres[n + 1] <- next_date
    next
  }
  
  # if next_date is equal to or greater than dateB, add it and terminate loop:
  if (next_date >= dateB) {
    myres[n + 1] <- next_date
    break
  }
}

identical(res, myres)
#> [1] TRUE

I was hoping for something like:

x <- seq.Date(from = dateA, to = dateB, by = "3 months")
x
#>  [1] "2014-03-31" "2014-07-01" "2014-10-01" "2014-12-31" "2015-03-31"
#>  [6] "2015-07-01" "2015-10-01" "2015-12-31" "2016-03-31" "2016-07-01"
#> [11] "2016-10-01" "2016-12-31" "2017-03-31" "2017-07-01" "2017-10-01"
#> [16] "2017-12-31" "2018-03-31" "2018-07-01" "2018-10-01" "2018-12-31"
#> [21] "2019-03-31" "2019-07-01" "2019-10-01" "2019-12-31" "2020-03-31"
#> [26] "2020-07-01" "2020-10-01" "2020-12-31" "2021-03-31" "2021-07-01"

But that doesn't seem to work.

Is there a simpler way than the while loop? I'd appreciate any pointers in the right direction.


Solution

  • I think this is most elegantly solved by taking the day of the month component out of the equation while you are generating the sequence. With the clock package, you can generate a month precision sequence, then easily set the day to the last day of the month.

    library(clock)
    
    start <- as.Date("2014-03-31")
    end <- as.Date("2021-09-30")
    
    start <- calendar_narrow(as_year_month_day(start), "month")
    end <- calendar_narrow(as_year_month_day(end), "month")
    
    # Month precision dates
    start
    #> <year_month_day<month>[1]>
    #> [1] "2014-03"
    end
    #> <year_month_day<month>[1]>
    #> [1] "2021-09"
    
    seq(start, end, by = 3) |>
      set_day("last") |>
      as.Date()
    #>  [1] "2014-03-31" "2014-06-30" "2014-09-30" "2014-12-31" "2015-03-31"
    #>  [6] "2015-06-30" "2015-09-30" "2015-12-31" "2016-03-31" "2016-06-30"
    #> [11] "2016-09-30" "2016-12-31" "2017-03-31" "2017-06-30" "2017-09-30"
    #> [16] "2017-12-31" "2018-03-31" "2018-06-30" "2018-09-30" "2018-12-31"
    #> [21] "2019-03-31" "2019-06-30" "2019-09-30" "2019-12-31" "2020-03-31"
    #> [26] "2020-06-30" "2020-09-30" "2020-12-31" "2021-03-31" "2021-06-30"
    #> [31] "2021-09-30"
    

    Created on 2022-08-15 by the reprex package (v2.0.1)