95

I have two dates let´s say 14.01.2013 and 26.03.2014.

I would like to get the difference between those two dates in terms of weeks(?), months(in the example 14), quarters(4) and years(1).

Do you know the best way to get this?

zx8754
  • 52,746
  • 12
  • 114
  • 209
ddg
  • 2,493
  • 2
  • 20
  • 23
  • 1
    For the weeks I found the following difftime(time1,time2,units="weeks"). This is unfortunately not working for months, quarters, years. – ddg Jan 22 '13 at 09:14

10 Answers10

86

what about this:

# get difference between dates `"01.12.2013"` and `"31.12.2013"`

# weeks
difftime(strptime("26.03.2014", format = "%d.%m.%Y"),
strptime("14.01.2013", format = "%d.%m.%Y"),units="weeks")
Time difference of 62.28571 weeks

# months
(as.yearmon(strptime("26.03.2014", format = "%d.%m.%Y"))-
as.yearmon(strptime("14.01.2013", format = "%d.%m.%Y")))*12
[1] 14

# quarters
(as.yearqtr(strptime("26.03.2014", format = "%d.%m.%Y"))-
as.yearqtr(strptime("14.01.2013", format = "%d.%m.%Y")))*4
[1] 4

# years
year(strptime("26.03.2014", format = "%d.%m.%Y"))-
year(strptime("14.01.2013", format = "%d.%m.%Y"))
[1] 1

as.yearmon() and as.yearqtr() are in package zoo. year() is in package lubridate. What do you think?

ddg
  • 2,493
  • 2
  • 20
  • 23
  • 16
    This answer requires caution... it will consider Dec 31 2013 to be 1 year different from the next day, Jan 1, 2014. Sometimes this is wanted, but often not. – Gregor Thomas Dec 08 '14 at 18:25
  • 1
    Extending Gregor's caveat: `year` will give difference in **calendar years only**, so if you need to know the difference in some fraction of a year, it will not be suitable. – Scransom Jun 03 '15 at 09:45
  • 'format' + defaults can make this easier to type: difftime(format("2014-03-26"), format("2013-01-14"), units = "weeks") Time difference of 62.28571 weeks – tim Feb 09 '16 at 10:17
61

All the existing answers are imperfect (IMO) and either make assumptions about the desired output or don't provide flexibility for the desired output.

Based on the examples from the OP, and the OP's stated expected answers, I think these are the answers you are looking for (plus some additional examples that make it easy to extrapolate).

(This only requires base R and doesn't require zoo or lubridate)

Convert to Datetime Objects

date_strings = c("14.01.2013", "26.03.2014")
datetimes = strptime(date_strings, format = "%d.%m.%Y") # convert to datetime objects

Difference in Days

You can use the diff in days to get some of our later answers

diff_in_days = difftime(datetimes[2], datetimes[1], units = "days") # days
diff_in_days
#Time difference of 435.9583 days

Difference in Weeks

Difference in weeks is a special case of units = "weeks" in difftime()

diff_in_weeks = difftime(datetimes[2], datetimes[1], units = "weeks") # weeks
diff_in_weeks
#Time difference of 62.27976 weeks

Note that this is the same as dividing our diff_in_days by 7 (7 days in a week)

as.double(diff_in_days)/7
#[1] 62.27976

Difference in Years

With similar logic, we can derive years from diff_in_days

diff_in_years = as.double(diff_in_days)/365 # absolute years
diff_in_years
#[1] 1.194406

You seem to be expecting the diff in years to be "1", so I assume you just want to count absolute calendar years or something, which you can easily do by using floor()

# get desired output, given your definition of 'years'
floor(diff_in_years)
#[1] 1

Difference in Quarters

# get desired output for quarters, given your definition of 'quarters'
floor(diff_in_years * 4)
#[1] 4

Difference in Months

Can calculate this as a conversion from diff_years

# months, defined as absolute calendar months (this might be what you want, given your question details)
months_diff = diff_in_years*12
floor(month_diff)
#[1] 14

I know this question is old, but given that I still had to solve this problem just now, I thought I would add my answers. Hope it helps.

rysqui
  • 2,779
  • 2
  • 19
  • 26
  • I think this does not work, when the `months_diff` is < 0. – timat Oct 23 '16 at 15:35
  • @timat can you give a specific example of two date strings for which this doesn't work for you? – rysqui Nov 02 '16 at 23:51
  • 1
    `date_strings = c("14.07.2014", "10.03.2015")` give `-4` instead 7 months according to the first definition.. – timat Nov 03 '16 at 08:07
  • @timat you're right! I'm not sure why when I wrote this I didn't just calculate months directly from the `diff_in_years`, e.g., in your example, the true answer is that almost 8 full months have past. You get the correct answer just by `diff_in_years*12 = 7.857534` I corrected my answer--thanks. – rysqui Jan 07 '17 at 09:40
  • 2
    Remember when you divide days by `365` to get years, it only applies to 3 out of 4 years, due to leap years. Dividing by `365.25` would be more precise, especially for calculating an age. – MS Berends Sep 15 '17 at 11:45
15

For weeks, you can use function difftime:

date1 <- strptime("14.01.2013", format="%d.%m.%Y")
date2 <- strptime("26.03.2014", format="%d.%m.%Y")
difftime(date2,date1,units="weeks")
Time difference of 62.28571 weeks

But difftime doesn't work with duration over weeks.
The following is a very suboptimal solution using cut.POSIXt for those durations but you can work around it:

seq1 <- seq(date1,date2, by="days")
nlevels(cut(seq1,"months"))
15
nlevels(cut(seq1,"quarters"))
5
nlevels(cut(seq1,"years"))
2

This is however the number of months, quarters or years spanned by your time interval and not the duration of your time interval expressed in months, quarters, years (since those do not have a constant duration). Considering the comment you made on @SvenHohenstein answer I would think you can use nlevels(cut(seq1,"months")) - 1 for what you're trying to achieve.

plannapus
  • 18,529
  • 4
  • 72
  • 94
14

I just wrote this for another question, then stumbled here.

library(lubridate)

#' Calculate age
#' 
#' By default, calculates the typical "age in years", with a
#' \code{floor} applied so that you are, e.g., 5 years old from
#' 5th birthday through the day before your 6th birthday. Set
#' \code{floor = FALSE} to return decimal ages, and change \code{units}
#' for units other than years.
#' @param dob date-of-birth, the day to start calculating age.
#' @param age.day the date on which age is to be calculated.
#' @param units unit to measure age in. Defaults to \code{"years"}. Passed to \link{\code{duration}}.
#' @param floor boolean for whether or not to floor the result. Defaults to \code{TRUE}.
#' @return Age in \code{units}. Will be an integer if \code{floor = TRUE}.
#' @examples
#' my.dob <- as.Date('1983-10-20')
#' age(my.dob)
#' age(my.dob, units = "minutes")
#' age(my.dob, floor = FALSE)
age <- function(dob, age.day = today(), units = "years", floor = TRUE) {
    calc.age = interval(dob, age.day) / duration(num = 1, units = units)
    if (floor) return(as.integer(floor(calc.age)))
    return(calc.age)
}

Usage examples:

my.dob <- as.Date('1983-10-20')

age(my.dob)
# [1] 31

age(my.dob, floor = FALSE)
# [1] 31.15616

age(my.dob, units = "minutes")
# [1] 16375680

age(seq(my.dob, length.out = 6, by = "years"))
# [1] 31 30 29 28 27 26
Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • 'new_interval' is deprecated; use 'interval' instead. Deprecated in version '1.5.0'. – Manoj Kumar May 16 '16 at 08:36
  • *I just wrote this for another question, then stumbled here.* Me too! Small advice: use curly brackets after an if statement: `if (floor) { ... }` and only use `return` when you're returning something halfway your function. Last line should just be `calc.age`. – MS Berends Sep 15 '17 at 11:49
  • 1
    @MSBerends Those are just style guidelines. I much prefer to explicitly `return` with my functions - I find it clearer. Of course, in your own code, use whatever style suits you. – Gregor Thomas Sep 17 '17 at 03:51
  • Very true. Regarding the function: it doesn’t work right in this case: `1950-01-17` and `2015-01-01`. It returns `65`, but this person would not turn 65 before 2015-01-17... Any idea why? – MS Berends Sep 17 '17 at 09:30
  • That's strange! I'll look into it more. The problem seems to be 2013, if you define `yy = seq.Date(from = as.Date("2010-01-01"), to = as.Date("2015-01-01"), by = "year")` and then try `age(dob = as.Date("1950-01-17"), age.day = yy)`, the result jumps over 62. And only if the DOB is between 1949 and 1952. Very strange... – Gregor Thomas Sep 19 '17 at 06:12
  • How coincidental then, that I just had a patient with such a birth day and test date! I'll see too if I can manage things here. – MS Berends Sep 20 '17 at 06:38
  • 1
    For future reference: This doesn't happen with the current version of lubridate 1.7.8 (https://lubridate.tidyverse.org/news/index.html). The reason was the use of duration in this function. Previoulsy, a year was considered 365, and not 365.25 days.`d <- as.integer(as.duration(interval(as.Date("1950-01-17"), as.Date("2015-01-01"))))`; `d/(86400*365) #> 65`; `d/(86400*365.25) #> 64.95551` ... – tjebo May 06 '20 at 08:01
  • (Having said that, your function is great, but I'd probably also include the option of using periods. Just to make it a bit more complex :) – tjebo May 06 '20 at 08:05
11

Here the still lacking lubridate answer (although Gregor's function is built on this package)

The lubridate timespan documentation is very helpful for understanding the difference between periods and duration. I also like the lubridate cheatsheet and this very useful thread

library(lubridate)

dates <- c(dmy('14.01.2013'), dmy('26.03.2014'))

span <- dates[1] %--% dates[2] #creating an interval object

#creating period objects 
as.period(span, unit = 'year') 
#> [1] "1y 2m 12d 0H 0M 0S"
as.period(span, unit = 'month')
#> [1] "14m 12d 0H 0M 0S"
as.period(span, unit = 'day')
#> [1] "436d 0H 0M 0S"

Periods do not accept weeks as units. But you can convert durations to weeks:

as.duration(span)/ dweeks(1)
#makes duration object (in seconds) and divides by duration of a week (in seconds)
#> [1] 62.28571

Created on 2019-11-04 by the reprex package (v0.3.0)

tjebo
  • 21,977
  • 7
  • 58
  • 94
6

Here's a solution:

dates <- c("14.01.2013", "26.03.2014")

# Date format:
dates2 <- strptime(dates, format = "%d.%m.%Y")

dif <- diff(as.numeric(dates2)) # difference in seconds

dif/(60 * 60 * 24 * 7) # weeks
[1] 62.28571
dif/(60 * 60 * 24 * 30) # months
[1] 14.53333
dif/(60 * 60 * 24 * 30 * 3) # quartes
[1] 4.844444
dif/(60 * 60 * 24 * 365) # years
[1] 1.194521
Sven Hohenstein
  • 80,497
  • 17
  • 145
  • 168
  • Thanks for that however your solution will not work in all cases. For example if you take dates <- c("01.12.2013", "31.12.2013"), you´ll get difference in months=1 while I would expect the difference to be 0 (both dates occur in Dec 13). – ddg Jan 22 '13 at 09:01
  • 3
    Although still nog accurate, I suggest using 365.242 for the amount of days in a year instead of 365. – CousinCocaine Apr 10 '14 at 17:22
3

This is a simple way to find out the difference in years with the lubridate package:

as.numeric(as.Date("14-03-2013", format = "%d-%m-%Y") %--% as.Date("23-03-2014", format = "%d-%m-%Y"), "years")

This returns 1.023956

You can use floor() if you don't want the decimals.

A. Suliman
  • 12,923
  • 5
  • 24
  • 37
  • This also works if you are calculating the interval between two date columns, or a date column and a fixed date. Just replace the as.Date(...) brackets with the variable names. – Pendragon Apr 27 '22 at 12:06
2

try this for a months solution

StartDate <- strptime("14 January 2013", "%d %B %Y") 
EventDates <- strptime(c("26 March 2014"), "%d %B %Y") 
difftime(EventDates, StartDate) 
jbaums
  • 27,115
  • 5
  • 79
  • 119
Rachel Gallen
  • 27,943
  • 21
  • 72
  • 81
  • Hi Rachel, thanky for that however this is not working. When I run strptime("14 January 2013", "%d %B %Y") I get NA. – ddg Jan 22 '13 at 09:16
  • Same here.. If I use this step, I get NAs – RHelp Feb 27 '14 at 09:48
  • This solution will work only for English locales. It's much safer to use `%m` and numeric months (e.g. 1 for January) instead of `%B`. – Frank Schmitt Sep 15 '15 at 10:44
2

A more "precise" calculation. That is, the number of week/month/quarter/year for a non-complete week/month/quarter/year is the fraction of calendar days in that week/month/quarter/year. For example, the number of months between 2016-02-22 and 2016-03-31 is 8/29 + 31/31 = 1.27586

explanation inline with code

#' Calculate precise number of periods between 2 dates
#' 
#' @details The number of week/month/quarter/year for a non-complete week/month/quarter/year 
#'     is the fraction of calendar days in that week/month/quarter/year. 
#'     For example, the number of months between 2016-02-22 and 2016-03-31 
#'     is 8/29 + 31/31 = 1.27586
#' 
#' @param startdate start Date of the interval
#' @param enddate end Date of the interval
#' @param period character. It must be one of 'day', 'week', 'month', 'quarter' and 'year'
#' 
#' @examples 
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "month"), 15/29 + 1)
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "quarter"), (15 + 31)/(31 + 29 + 31))
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "year"), (15 + 31)/366)
#' 
#' @return exact number of periods between
#' 
numPeriods <- function(startdate, enddate, period) {

    numdays <- as.numeric(enddate - startdate) + 1
    if (grepl("day", period, ignore.case=TRUE)) {
        return(numdays)

    } else if (grepl("week", period, ignore.case=TRUE)) {
        return(numdays / 7)
    }

    #create a sequence of dates between start and end dates
    effDaysinBins <- cut(seq(startdate, enddate, by="1 day"), period)

    #use the earliest start date of the previous bins and create a breaks of periodic dates with
    #user's period interval
    intervals <- seq(from=as.Date(min(levels(effDaysinBins)), "%Y-%m-%d"), 
        by=paste("1",period), 
        length.out=length(levels(effDaysinBins))+1)

    #create a sequence of dates between the earliest interval date and last date of the interval
    #that contains the enddate
    allDays <- seq(from=intervals[1],
        to=intervals[intervals > enddate][1] - 1,
        by="1 day")

    #bin all days in the whole period using previous breaks
    allDaysInBins <- cut(allDays, intervals)

    #calculate ratio of effective days to all days in whole period
    sum( tabulate(effDaysinBins) / tabulate(allDaysInBins) )
} #numPeriods

Please let me know if you find more boundary cases where the above solution does not work.

chinsoon12
  • 25,005
  • 4
  • 25
  • 35
1

Hoping I can add something new to the table that's simple, intuitive and efficient.

Using time_diff() from my package timeplyr, one can easily calculate the time difference between any 2 date/datetime vectors.

# remotes::install_github("NicChr/timeplyr")
library(timeplyr)
library(lubridate)

x <- dmy("14-01-2013")
y <- dmy("26-03-2014")

time_diff(x, y, "days")
#> [1] 436
time_diff(x, y, "hours")
#> [1] 10464
time_diff(x, y, "weeks")
#> [1] 62.28571
time_diff(x, y, "months")
#> [1] 14.3871
time_diff(x, y, "years")
#> [1] 1.194521

Created on 2023-04-10 with reprex v2.0.2

You can specify exotic units as well.

.extra_time_units
#>  [1] "fortnights" "quarters"   "semesters"  "olympiads"  "lustrums"  
#>  [6] "decades"    "indictions" "scores"     "centuries"  "milleniums"

time_diff(x, y, "fortnight")
#> [1] 31.14286
time_diff(x, y, "quarter")
#> [1] 4.788889
time_diff(x, y, "decade")
#> [1] 0.1193866

Choose between lubridate durations and periods.

time_diff(x, y, dmonths(1))
#> [1] 14.32444
time_diff(x, y, months(1))
#> [1] 14.3871

And even choose multi-units, as it's vectorised.

time_diff(x, y, time_by = list("days" = -10:10))
#>  [1]  -43.60000  -48.44444  -54.50000  -62.28571  -72.66667  -87.20000
#>  [7] -109.00000 -145.33333 -218.00000 -436.00000        Inf  436.00000
#> [13]  218.00000  145.33333  109.00000   87.20000   72.66667   62.28571
#> [19]   54.50000   48.44444   43.60000
NicChr
  • 858
  • 1
  • 9