63

I have a date in R, e.g.:

dt = as.Date('2010/03/17')

I would like to subtract 2 years from this date, without worrying about leap years and such issues, getting as.Date('2008-03-17').

How would I do that?

gt6989b
  • 4,125
  • 8
  • 46
  • 64

8 Answers8

102

With lubridate

library(lubridate)
ymd("2010/03/17") - years(2)
hadley
  • 102,019
  • 32
  • 183
  • 245
  • 5
    This doesn't work as expected for leap years: `ymd("2016/02/29") - years(2)` returns NA. Using `%m-% months(24)` returns `"2014-02-28"`. And to be extra sure, `%m-% months(48)` returns `"2012-02-29"`. – user2105469 Feb 09 '18 at 00:12
  • 3
    This *is* the expected behavior from lubridate. The documentation for lubricate explicitly states this: Consider a simple operation, January 31st + one month. Should the answer be: * February 31st (which doesn’t exist) * March 4th (31 days after January 31), or * February 28th (assuming its not a leap year) A basic property of arithmetic is that a + b - b = a. Only solution 1 obeys this property, but it is an invalid date... if adding or subtracting a month or a year creates an invalid date, lubridate will return an NA. – Patrick Conwell May 01 '18 at 15:38
58

The easiest thing to do is to convert it into POSIXlt and subtract 2 from the years slot.

> d <- as.POSIXlt(as.Date('2010/03/17'))
> d$year <- d$year-2
> as.Date(d)
[1] "2008-03-17"

See this related question: How to subtract days in R?.

Community
  • 1
  • 1
Shane
  • 98,550
  • 35
  • 224
  • 217
  • 1
    rcs's answer below is preferable -- we do have `difftime` operator for it. – Dirk Eddelbuettel Jul 24 '10 at 14:29
  • 3
    With difftime, I don't think you can do years, just days or weeks. – gt6989b Sep 01 '10 at 12:43
  • 3
    Be careful in case of Feb-29, because the resulting object will probably have wday/mon/mday slots not correct ! Try : `d=as.POSIXlt('2016-02-29',tz='GMT');d$year=d$year - 1` and check the values of `d$wday,d$mon,d$mday` – digEmAll Feb 19 '18 at 18:20
31

You could use seq:

R> dt = as.Date('2010/03/17')
R> seq(dt, length=2, by="-2 years")[2]
[1] "2008-03-17"
rcs
  • 67,191
  • 22
  • 172
  • 153
  • 4
    there is no way to apply this to a list of dates, though, unless I'm missing a simple extension – MichaelChirico Feb 02 '15 at 00:23
  • seq.Date is also possible. seq.Date(as.Date('2010/03/17'),length.out=2,by='-1 year')[2] – Jerry T Dec 22 '19 at 17:04
  • @JerryT `seq.Date` is the dispatched S3 method when using `seq` – rcs Dec 23 '19 at 10:56
  • If you don't mind, I find the choice of "2" confusing it terms of seeing this solution. For others "struggling" like me, i think the following is a lot clearer: `x <- 2` `seq(Sys.Date(), length=2, by=paste0("-", x, " years"))[2]`. You can change x for other intervals (3, 4, etc), that's the variable - the other "2"s are "fixed... I believe it would then be easy to apply this list, as requested by @MichaelChirico – tchevrier Nov 17 '20 at 08:00
12

If leap days are to be taken into account then I'd recommend using this lubridate function to subtract months, as other methods will return either March 1st or NA:

> library(lubridate)
> dt %m-% months(12*2)
[1] "2008-03-17"

# Try with leap day
> leapdt <- as.Date('2016/02/29')
> leapdt %m-% months(12*2)
[1] "2014-02-28"
Hugo Silva
  • 121
  • 1
  • 2
  • 1
    Whether you get Feb. 28th or March 1st is a matter of convention. `NA` is obviously unacceptable, I agree. Thanks for adding info. – gt6989b Sep 26 '16 at 16:33
3

Same answer than the one by rcs but with the possibility to operate it on a vector (to answer to MichaelChirico, I can't comment I don't have enough rep):

R> unlist(lapply(c("2015-12-01", "2016-12-01"), 
      function(x) { return(as.character(seq(as.Date(x), length=2, by="-1 years")[2])) }))
 [1] "2014-12-01" "2015-12-01"
2

This way seems to do the job as well

dt = as.Date("2010/03/17")
dt-365*2
[1] "2008-03-17"

as.Date("2008/02/29")-365*2
## [1] "2006-03-01"
DJJ
  • 2,481
  • 2
  • 28
  • 53
1
cur_date <- str_split(as.character(Sys.Date()), pattern = "-")
cur_yr <- cur_date[[1]][1]
cur_month <- cur_date[[1]][2]
cur_day <- cur_date[[1]][3]
new_year <- as.integer(year) - 2
new_date <- paste(new_year, cur_month, cur_day, sep="-")
tommmm
  • 182
  • 1
  • 4
1

Using Base R, you can simply use the following without installing any package.

1) Transform your character string to Date format, specifying the input format in the second argument, so R can correctly interpret your date format.

dt = as.Date('2010/03/17',"%Y/%m/%d")

NOTE: If you look now at your enviroment tab you will see dt as variable with the following value "2010-03-17" (Year-month-date separated by "-" not by "/")

2) specify how many years to substract

years_substract=2

3) Use paste() combined with format () to only keep Month and Day and Just substract 2 year from your original date. Format() function will just keep the specific part of your date accordingly with format second argument.

dt_substract_2years<-
as.Date(paste(as.numeric(format(dt,"%Y"))-years_substract,format(dt,"%m"),format(dt,"%d"),sep = "-"))

NOTE1: We used paste() function to concatenate date components and specify separator as "-" (sep = "-")as is the R separator for dates by default.

NOTE2: We also used as.numeric() function to transform year from character to numeric

  • Thanks for answering. How is this essentially different than @tommmm's answer? (agree has more comments to explain what's going on, +1) – gt6989b Aug 18 '22 at 17:01