Get date difference in years (floating point)
Asked Answered
E

8

55

I want to correct source activity based on the difference between reference and measurement date and source half life (measured in years). Say I have

ref_date <- as.Date('06/01/08',format='%d/%m/%y')

and a column in my data.frame with the same date format, e.g.,

today <- as.Date(Sys.Date(), format='%d/%m/%y')

I can find the number of years between these dates using the lubridate package

year(today)-year(ref_date)
[1] 5

Is there a function I can use to get a floating point answer today - ref_date = 5.2y, for example?

Eveevection answered 22/3, 2013 at 11:34 Comment(0)
H
73

Yes, of course, use difftime() with an as numeric:

R> as.numeric(difftime(as.Date("2003-04-05"), as.Date("2001-01-01"), 
+                      unit="weeks"))/52.25
[1] 2.2529
R> 

Note that we do have to switch to weeks scaled by 52.25 as there is a bit of ambiguity there in terms of counting years---a February 29 comes around every 4 years but not every 100th etc.

So you have to define that. difftime() handles all time units up to weeks. Months cannot be done for the same reason of the non-constant 'numerator'.

Hominid answered 22/3, 2013 at 11:36 Comment(4)
Thanks. I assume I can divide by 365.25 and omit `unit=weeks"Eveevection
No, you need to -- just play with it to see. The unit auto-adjusts, try it with deltas of less than one, or even POSIXct objects (eg from Sys.time()).Hominid
And the weeks are always the same number in an year?Scorpius
52.25*7=365.75, is this OK? Wiki says that the gregorian calender has 365.2425 days... 365.2425/7=52.1775, How to do it right?Scorpius
O
55

The lubridate package contains a built-in function, time_length, which can help perform this task.

time_length(difftime(as.Date("2003-04-05"), as.Date("2001-01-01")), "years")
[1] 2.257534

time_length(difftime(as.Date("2017-03-01"), as.Date("2012-03-01")),"years")
[1] 5.00274

Documentation for the lubridate package can be found here.

Overstrung answered 15/2, 2018 at 15:6 Comment(1)
Do note @RenMa's answer below, as using difftime() can indeed give incorrect results. I happened to notice this when looking at the difference between 1948-12-07 and today (2018-11-29), which is 70.02466 via difftime() and 69.97808 via interval(). start <- as.Date("1948-12-07"); end <- as.Date("2018-11-29"); difftime(end, start) %>% time_length(unit = "years"); interval(start, end) %>% time_length(unit = "years")Wideangle
U
24

Inspired by Bryan F, time_length() would work better if using interval object

time_length(interval(as.Date("2003-04-05"), as.Date("2001-01-01")), "years")
[1] -2.257534
time_length(difftime(as.Date("2017-03-01"), as.Date("2012-03-01")),"years")
[1] 5.00274
time_length(interval(as.Date("2017-03-01"), as.Date("2012-03-01")),"years")
[1] -5

You can see if you use interval() to get the time difference and then pass it to time_length(), time_length() would take into account the fact that not all months and years have the same number of days, e.g., the leap year.

Urbas answered 24/8, 2018 at 13:42 Comment(2)
Why do we get a negative sign after the time length calculation?Slipover
interval(start_date, end_date) basically uses end_date minus start_date. So if your start_date occurs after the end_date, the interval will be negative.Urbas
M
6

Not an exact answer to your question, but the answer from Dirk Eddelbuettel in some situations can produce small errors.

Please, consider the following example:

as.numeric(difftime(as.Date("2012-03-01"), as.Date("2017-03-01"), unit="weeks"))/52.25
[1] -4.992481

The correct answer here should be at least 5 years.

The following function (using lubridate package) will calculate a number of full years between two dates:

# Function to calculate an exact full number of years between two dates
year.diff <- function(firstDate, secondDate) {
  yearsdiff <- year(secondDate) - year(firstDate)
  monthsdiff <- month(secondDate) - month(firstDate)
  daysdiff <- day(secondDate) - day(firstDate)

  if ((monthsdiff < 0) | (monthsdiff == 0 & daysdiff < 0)) {
    yearsdiff <- yearsdiff - 1
  }

  yearsdiff
}

You can modify it to calculate a fractional part depending on how you define the number of days in the last (not finished) year.

Margalit answered 30/10, 2017 at 13:39 Comment(0)
D
1

You can use the function AnnivDates() of the package BondValuation:

R> library('BondValuation')
R> DateIndexes <- unlist(
+   suppressWarnings(
+     AnnivDates("2001-01-01", "2003-04-05", CpY=1)$DateVectors[2]
+     )
+   )
R> names(DateIndexes) <- NULL
R> DateIndexes[length(DateIndexes)] - DateIndexes[1]
[1] 2.257534

Click here for documentation of the package BondValuation.

Dandiprat answered 10/4, 2019 at 12:43 Comment(0)
W
1

To get the date difference in years (floating point) you can convert the dates to decimal numbers of Year and calculate then their difference.

#Example Dates
x <- as.Date(c("2001-01-01", "2003-04-05"))

#Convert Date to decimal year:
date2DYear <- function(x) {
  as.numeric(format(x,"%Y")) + #Get Year an add
    (as.numeric(format(x,"%j")) - 0.5) / #Day of the year divided by
    as.numeric(format(as.Date(paste0(format(x,"%Y"), "-12-31")),"%j")) #days of the year
}
diff(date2DYear(x)) #Get the difference in years
#[1] 2.257534

I subtract 0.5 from the day of the year as it is not known if you are at the beginning or the end of the day and %j starts with 1.

I think the difference between 2012-03-01 and 2017-03-01 need not to be 5 Years, as 2012 has 366 days and 2017 365 and 2012-03-01 is on the 61 day of the year and 2017-03-01 on the 60.

x <- as.Date(c("2012-03-01", "2017-03-01"))
diff(date2DYear(x))
#[1] 4.997713

Note that using time_length and interval from lubridate need not come to the same result when you make a cumulative time difference.

library(lubridate)

x <- as.Date(c("2012-01-01", "2012-03-01", "2012-12-31"))
time_length(interval(x[1], x[3]), "years")
#[1] 0.9972678
time_length(interval(x[1], x[2]), "years") +
 time_length(interval(x[2], x[3]), "years")
#[1] 0.9995509 #!
diff(date2DYear(x[c(1,3)]))
#[1] 0.9972678
diff(date2DYear(x[c(1,2)])) + diff(date2DYear(x[c(2,3)]))
#[1] 0.9972678

x <- as.Date(c("2013-01-01", "2013-03-01", "2013-12-31"))
time_length(interval(x[1], x[3]), "years")
#[1] 0.9972603
time_length(interval(x[1], x[2]), "years") +
 time_length(interval(x[2], x[3]), "years")
#[1] 0.9972603
diff(date2DYear(x[c(1,3)]))
#[1] 0.9972603
diff(date2DYear(x[c(1,2)])) + diff(date2DYear(x[c(2,3)]))
#[1] 0.9972603
Waverly answered 9/9, 2020 at 16:4 Comment(0)
D
0

Here's a solution that only uses R base, although it only works with scalar values:

year.difference <- function (x, y) {
    x <- as.Date(x)
    y <- as.Date(y)
    if (x == y) {
        0
    } else if (x > y) {
        -year.difference(y, x)
    } else {
        k <- length(seq(x, y - 1, by = "years"))
        z <- seq(x, length.out = k + 1, by = "years")[c(k, k + 1)]
        k - 1 + (as.numeric(y - z[1]) / as.numeric(z[2] - z[1]))
    }
}

It correctly accounts for the extra days in leap years:

> year.difference("2024-02-01", "2024-02-02")
[1] 0.00273224
> year.difference("2025-02-01", "2025-02-02")
[1] 0.002739726
Dutra answered 19/2 at 18:0 Comment(0)
G
-1

Since you are already using lubridate package, you can obtain number of years in floating point using a simple trick:

find number of seconds in one year:

seconds_in_a_year <- as.integer((seconds(ymd("2010-01-01")) - seconds(ymd("2009-01-01"))))

now obtain number of seconds between the 2 dates you desire

seconds_between_dates <- as.integer(seconds(date1) - seconds(date2))

your final answer for number of years in floating points will be

years_between_dates <- seconds_between_dates / seconds_in_a_year 
Gripping answered 30/7, 2015 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.