26

I am looking to add and subtract six months (bond time) reliably with lubridate.

For example, adding six months to 12/31/2014 should result in 6/30/2015, and adding to 2/28/2014 should result in 8/31/2014

The issue with as.Date("2014-12-31") + months(6), is that it yields an NA. Alternatively, the second result is 8/28/2014 because it doesn't just add 6 months to the month and then know where the day should end up dependent upon the month.

Is there any way to quickly correct this? At the moment, I am building a function to basically use a switch and consider each month, but this is very long and I am having problems with it as well.

Thanks!

Henrik
  • 61,039
  • 13
  • 131
  • 152
Michael Clinton
  • 625
  • 1
  • 6
  • 12
  • Not really clear on the behavior you want - what should adding 6 months to 2/27/2014 give? 8/30/2014, or 8/27/2014? – Ken Williams May 24 '18 at 22:14

2 Answers2

69

The lubridate function %m+% may be useful here:

Add and subtract months to a date without exceeding the last day of the new month

as.Date("2014-12-31") %m+% months(6)
# [1] "2015-06-30"

To also handle the second case, you will need to round up to nearest month using ceiling_date, and subtract one day using days.

ceiling_date(as.Date("2014-02-28") %m+% months(6), unit = "month") - days(1)
# [1] "2014-08-31"
Henrik
  • 61,039
  • 13
  • 131
  • 152
  • 1
    It seems that this also works without `%m+%` operator as in `Sys.Date() - months(1)`. – Konrad Feb 19 '20 at 15:36
  • @Konrad It's unclear what "this" is. You use `%m+%` and `%m-%` to _prevent rollover_ (which for most dates isn't an issue). But, compare e.g. `as.Date("2020-03-30") - months(1)` and `as.Date("2020-03-30") %m-% months(1)`; `as.Date("2020-01-30") + months(1)` and `as.Date("2020-01-30") %m+% months(1)` – Henrik Feb 20 '20 at 12:16
2

I just coded this out quickly, but I think it should work. I'm not sure if it's the most elegant solution, however.

# up = 1, down = -1
six.mo.mover<-function(date,up.or.down) {
  last.day <- month(date) != month(as.Date(date)+1) 
  if(last.day) {
    adj.date <- as.Date(date) - day(as.Date(date)-1) + up.or.down*months(6)
    adj.mo <- month(adj.date)
    if (adj.mo == 2) {
      dy <- 28 + leap_year(year(adj.date))
    }
    else {
      dy <- 31-(adj.mo-1)%%7%%2
    }
    adj.date + days(dy-1)
  } 
  else {
    as.Date(date)+up.or.down*months(6)
  }
}

NB: not debugged, so check it yourself and let me know.