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
  • 65,555
  • 14
  • 143
  • 159
Michael Clinton
  • 635
  • 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

3 Answers3

70

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
  • 65,555
  • 14
  • 143
  • 159
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.

2

tidyverse has added the clock package in addition to the lubridate package that has nice functionality for this:

library(clock)

x <- as.Date("2014/12/31")

# The previous valid moment in time
add_months(x, 6, invalid = "previous")
[1] "2015-06-30"

# The next valid moment in time
add_months(x, 2, invalid = "next")
[1] "2015-03-01"

# Overflow the days. There were 28 days in February, 2015, but we
# specified 31. So this overflows 3 days past day 28.
add_months(x, 2, invalid = "overflow")
[1] "2015-03-03"

The invalid argument makes explicit what to do in this case. You can also specify invalid to be NA or if you leave off this argument you could get an error.


For the second use case:

adding [6 months] to 2/28/2014 should result in 8/31/2014

You can use calendar_end, which needs a calendar vector:

year_month_day(2014, 02, 28) |> 
  add_months(6) |>
  calendar_end("month")

<year_month_day<day>[1]>
[1] "2014-08-31"

Alternatively, you could sequence the dates and select out the dates with the appropriate number of months added (or subtracted):

dates <- date_build(2014, 1:12, 31, invalid = "previous")

dates[c(2,8)]
# [1] "2014-02-28" "2014-08-31"
LMc
  • 12,577
  • 3
  • 31
  • 43