55

I'm trying to subtract n months from a date as follows:

maturity <- as.Date("2012/12/31")

m <- as.POSIXlt(maturity)

m$mon <- m$mon - 6

but the resulting date is 01-Jul-2012, and not 30-Jun-2012, as I should expect. Is there any short way to get such result?

Thanks in advance

Chase
  • 65,190
  • 17
  • 140
  • 160
user648905
  • 551
  • 1
  • 4
  • 3
  • 1
    Possible duplicate of [Add a month to a Date](http://stackoverflow.com/questions/14169620/add-a-month-to-a-date) – Barker Jan 05 '17 at 00:08
  • 2
    @Barker This old question of 2011 already has an excellent answer summarizing several possible approaches. So, I don't see reason to declare this as a duplicate of a Q of 2013. Perhaps, it should be the other way around? – Uwe Jan 05 '17 at 07:25
  • @UweBlock The other question is also many years old and also has many excellent answers. Also the answers on the other question deal with edge cases this one does not. My reason for marking as duplicate was this question was the one I found most easily and it didn't fix my problem, it was only after searching much harder that I found the other question that did have the answer I needed ([specifically the second one](http://stackoverflow.com/a/14182326/2943311)). Meta says the [less complete question should be the duplicate even if is newer](http://meta.stackoverflow.com/a/252017/2943311). – Barker Jan 05 '17 at 14:59
  • @Barker [G. Grothendiek's answer](http://stackoverflow.com/a/5226089/3817004) did already include `lubridate` and has been updated. – Uwe Jan 05 '17 at 15:32
  • @UweBlock ok, would you like me to mark the other question as duplicate then? How do I remove the flag from this question? – Barker Jan 05 '17 at 19:06

4 Answers4

112

1) seq.Date. Note that June has only 30 days so it cannot give June 31st thus instead it gives July 1st.

seq(as.Date("2012/12/31"), length = 2, by = "-6 months")[2]
## [1] "2012-07-01"

If we knew it was at month end we could do this:

seq(as.Date(cut(as.Date("2012/12/31"), "month")), length=2, by="-5 month")[2]-1
## "2012-06-30"

2) yearmon. Also if we knew it was month end then we could use the "yearmon" class of the zoo package like this:

library(zoo)
as.Date(as.yearmon(as.Date("2012/12/31")) -.5, frac = 1)
## [1] "2012-06-30"

This converts the date to "yearmon" subtracts 6 months (.5 of a year) and then converts it back to "Date" using frac=1 which means the end of the month (frac=0 would mean the beginning of the month). This also has the advantage over the previous solution that it is vectorized automatically, i.e. as.Date(...) could have been a vector of dates.

Note that if "Date" class is only being used as a way of representing months then we can get rid of it altogether and directly use "yearmon" since that models what we want in the first place:

as.yearmon("2012-12") - .5
## [1] "Jun 2012"

3) mondate. A third solution is the mondate package which has the advantage here that it returns the end of the month 6 months ago without having to know that we are month end:

library(mondate)
mondate("2011/12/31") - 6
## mondate: timeunits="months"
## [1] 2011/06/30

This is also vectorized.

4) lubridate. This lubridate answer has been changed in line with changes in the package:

library(lubridate)
as.Date("2012/12/31") %m-% months(6)
## [1] "2012-06-30"

lubridate is also vectorized.

5) sqldf/SQLite

library(sqldf)
sqldf("select date('2012-12-31', '-6 months') as date")
##         date
## 1 2012-07-01

or if we knew we were at month end:

sqldf("select date('2012-12-31', '+1 day', '-6 months', '-1 day') as date")
##         date
## 1 2012-06-30
jay.sf
  • 46,523
  • 6
  • 46
  • 87
G. Grothendieck
  • 233,926
  • 16
  • 195
  • 321
  • Hi, thanks for your reply, but the problem is a little bit more tricky. I need to calculate a date that is, for example, 6 months prior another one (i.e. 30-Jun, should the input date be 31-Dec). – user648905 Mar 12 '11 at 08:41
  • I do not know if the starting date is a month end or not. What I would like to get is a date with the same day of the starting one, should this be a valid one (i.e. 15-Mar, should the input date be 15-Sep), or, otherwise, with the last day of the month (i.e. 28-Feb, should the input date be 31-Aug). In my question, I referred to an end of month date, as for other dates the formula I used worked fine. Should I have to define a function for doing this? Thanks – user648905 Mar 12 '11 at 08:59
  • See the `mondate` solution in the response. – G. Grothendieck Mar 12 '11 at 13:01
  • 3
    Strange. If I use x=2 or 4 for `as.Date("2012/12/31") - months(x)` I got the expected results. With x = 1,3,5,6 I got NA. Any idea why this happens? – giordano Mar 31 '15 at 13:40
  • 3
    @giordano its because some months don't have 31 days. Try `as.Date("2012/12/20") - months(1:12)` for example. – David Arenburg Sep 21 '15 at 10:31
10

you can use lubridate package for this

library(lubridate)
maturity <- maturity %m-% months(6)

there is no reason for changing the day field.

you can set your day field back to the last day in that month by

day(maturity) <- days_in_month(maturity)
desertnaut
  • 52,940
  • 19
  • 125
  • 157
2

lubridate works correctly with such calculations:

library(lubridate)
as.Date("2000-01-01") - days(1)    # 1999-12-31
as.Date("2000-03-31") - months(1)  # 2000-02-29

but sometimes fails:

as.Date("2000-02-29") - years(1)   # NA, should be 1999-02-28
J. Doe
  • 51
  • 2
0

Technically you cannot add/subtract 1 month to all dates (although you can add/subtract 30 days to all dates, but I suppose, that's not something you want). I think this is what you are looking for

> lubridate::ceiling_date(as.Date("2020-01-31"), unit = "month")
[1] "2020-02-01"
> lubridate::floor_date(as.Date("2020-01-31"), unit = "month")
[1] "2020-01-01"
ishonest
  • 363
  • 2
  • 8