92

Is there a standard/common method/formula to calculate the number of months between two dates in R?

I am looking for something that is similar to MathWorks months function

jogo
  • 12,469
  • 11
  • 37
  • 42
knguyen
  • 2,974
  • 5
  • 25
  • 27

11 Answers11

72

I was about to say that's simple, but difftime() stops at weeks. How odd.

So one possible answer would be to hack something up:

# turn a date into a 'monthnumber' relative to an origin
R> monnb <- function(d) { lt <- as.POSIXlt(as.Date(d, origin="1900-01-01")); \
                          lt$year*12 + lt$mon } 
# compute a month difference as a difference between two monnb's
R> mondf <- function(d1, d2) { monnb(d2) - monnb(d1) }
# take it for a spin
R> mondf(as.Date("2008-01-01"), Sys.Date())
[1] 24
R> 

Seems about right. One could wrap this into some simple class structure. Or leave it as a hack :)

Edit: Also seems to work with your examples from the Mathworks:

R> mondf("2000-05-31", "2000-06-30")
[1] 1
R> mondf(c("2002-03-31", "2002-04-30", "2002-05-31"), "2002-06-30")
[1] 3 2 1
R> 

Adding the EndOfMonth flag is left as an exercise to the reader :)

Edit 2: Maybe difftime leaves it out as there is no reliable way to express fractional difference which would be consistent with the difftime behavior for other units.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • Thanks, Dirk. That's a clever trick. Speaking of units, I found out that this also works.. >as.numeric(as.Date('2009-10-1') - as.Date('2009-8-01'), units = 'weeks') > as.numeric(as.Date('2009-10-1') - as.Date('2009-8-01'), units = 'days') but it stops at 'weeks' as well. – knguyen Jan 04 '10 at 00:18
  • I think that is just clever overloading of the - operator to invoke the same `difftime()` function. "No Free Lunch" as the saying goes :) – Dirk Eddelbuettel Jan 04 '10 at 00:28
  • I found this very slow for large datasets. The answer by Manoel Galdino below is much faster. – anotherfred Apr 13 '18 at 10:41
  • 2
    @anotherfred And potentially false. Quick example are dates 2017-01-31 and 2018-03-01. Gives '13' here with his first answer (once we add the missing `as.numeric()`, and '12' with the second answer. My answer gives '14' as one would want. – Dirk Eddelbuettel Apr 13 '18 at 12:52
  • @DirkEddelbuettel sorry, should have clarified - I meant his first method is useful for cases like mine where I have a large dataset but all dates are the 1st of the month. – anotherfred Apr 16 '18 at 09:20
66

I think this is a closer answer to the question asked in terms of parity with MathWorks function

MathWorks months function

MyMonths = months(StartDate, EndDate, EndMonthFlag)

My R code

library(lubridate)
interval(mdy(10012015), today()) %/% months(1)

Output (as when the code was run in April 2016)

[1] 6

Lubridate [package] provides tools that make it easier to parse and manipulate dates. These tools are grouped below by common purpose. More information about each function can be found in its help documentation.

interval {lubridate} creates an Interval-class object with the specified start and end dates. If the start date occurs before the end date, the interval will be positive. Otherwise, it will be negative

today {lubridate} The current date

months {Base} Extract the month These are generic functions: the methods for the internal date-time classes are documented here.

%/% {base} indicates integer division AKA ( x %/% y ) (up to rounding error)

mtelesha
  • 2,079
  • 18
  • 16
57

A simple function...

elapsed_months <- function(end_date, start_date) {
    ed <- as.POSIXlt(end_date)
    sd <- as.POSIXlt(start_date)
    12 * (ed$year - sd$year) + (ed$mon - sd$mon)
}

Example...

>Sys.time()
[1] "2014-10-29 15:45:44 CDT"
>elapsed_months(Sys.time(), as.Date("2012-07-15"))
[1] 27
>elapsed_months("2002-06-30", c("2002-03-31", "2002-04-30", "2002-05-31"))
[1] 3 2 1

To me it makes sense to think about this problem as simply subtracting two dates, and since minuend − subtrahend = difference (wikipedia), I put the later date first in the parameter list.

Note that it works fine for dates preceeding 1900 despite those dates having internal representations of year as negative, thanks to the rules for subtracting negative numbers...

> elapsed_months("1791-01-10", "1776-07-01")
[1] 174
pbnelson
  • 1,649
  • 16
  • 14
49

There may be a simpler way. It's not a function but it is only one line.

length(seq(from=date1, to=date2, by='month')) - 1

e.g.

> length(seq(from=Sys.Date(), to=as.Date("2020-12-31"), by='month')) - 1

Produces:

[1] 69

This calculates the number of whole months between the two dates. Remove the -1 if you want to include the current month/ remainder that isn't a whole month.

Dominic
  • 624
  • 5
  • 11
  • 4
    This doesn't always give expected results. `length(seq(from=as.Date("2015-01-31"), to=as.Date("2015-03-31"), by='month')) = 3` Also, `length(seq(from=as.Date("2015-01-31"), to=as.Date("2015-04-30"), by='month')) = 3` – Octave1 Aug 18 '17 at 12:57
  • The code above now returns an error because Sys.Date()>"2020-12-31" by now. It still works if you change 'from' and 'to' arguments. – otwtm Feb 05 '21 at 10:46
16

There is a message just like yours in the R-Help mailing list (previously I mentioned a CRAN list).

Here the link. There are two suggested solutions:

  • There are an average of 365.25/12 days per month so the following expression gives the number of months between d1 and d2:
#test data 
d1 <- as.Date("01 March 1950", "%d %B %Y")    
d2 <- as.Date(c("01 April 1955", "01 July 1980"), "%d %B %Y")
# calculation 
round((d2 - d1)/(365.25/12))
  • Another possibility is to get the length of seq.Dates like this:
as.Date.numeric <- function(x) structure(floor(x+.001), class = "Date")
sapply(d2, function(d2) length(seq(d1, as.Date(d2), by = "month")))-1
Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
Manoel Galdino
  • 2,376
  • 6
  • 27
  • 40
5
library(lubridate)

case1: naive function

mos<-function (begin, end) {
      mos1<-as.period(interval(ymd(begin),ymd(end)))
      mos<-mos1@year*12+mos1@month
      mos
}

case2: if you need to consider only 'Month' regardless of 'Day'

mob<-function (begin, end) {
      begin<-paste(substr(begin,1,6),"01",sep="")
      end<-paste(substr(end,1,6),"01",sep="")
      mob1<-as.period(interval(ymd(begin),ymd(end)))
      mob<-mob1@year*12+mob1@month
      mob
}

Example :

mos(20150101,20150228) # 1
mos(20150131,20150228) # 0
# you can use "20150101" instead of 20150101

mob(20150131,20150228) # 1
mob(20150131,20150228) # 1
# you can use a format of "20150101", 20150101, 201501
hyunwoo jeong
  • 1,534
  • 1
  • 15
  • 14
  • interval(mdy(20150101), mdy(20150228)) %/% months(1) Just use the lubridate's native function interval. – mtelesha Sep 19 '17 at 17:35
  • thank you mtelesha. However, in my business, I need to calculate 'month count' of 2 days often. For example, months between 20150131 and 20150201 have to be 1. So, I used to make 'mob' user defined function. Do you know how to make this using lubridate? – hyunwoo jeong Sep 26 '17 at 06:08
  • I think you would can either use month(ymd) - month(ymd). Or you could make three columns one for year, month and day. – mtelesha Sep 26 '17 at 22:53
  • Sure, but in case of different year, it will not work. For example, month(ymd(20170101)) - month(ymd(20161231)) = -11 not 1 – hyunwoo jeong Oct 11 '17 at 00:39
5
library(lubridate)
date1 = "1 April 1977"
date2 = "7 July 2017"

date1 = dmy(date1)
date2 = dmy(date2)
number_of_months = (year(date1) - year(date2)) * 12 + month(date1) - month(date2)

Difference in months = 12 * difference in years + difference in months.

Following may need to be corrected using ifelse condition for the month subtractions

mrsrinivas
  • 34,112
  • 13
  • 125
  • 125
Ajay Ohri
  • 3,382
  • 3
  • 30
  • 60
  • maybe worth to note that this results in negative numbers - so either changing date1/date2 or use `abs(etc)` . I think this is a very easy and straight forward solution for getting a `guestimate` of months – tjebo Apr 04 '19 at 14:08
5

For me this is what worked:

library(lubridate)

Pagos$Datediff <- (interval((Pagos$Inicio_FechaAlta), (Pagos$Inicio_CobFecha)) %/% months(1))

The output is the number of months between two dates and stored in a column of the Pagos data frame.

Jeremy W
  • 1,889
  • 6
  • 29
  • 37
  • Thanks for your contribution but this doesn't' give any decimal places. Any advice? – Mohamed Rahouma Oct 20 '20 at 18:40
  • I am afraid this only gives you the number of months. but not with commas. My opinion is that instead of that, should calculate in term of days. I might be more precise for your case. – EDUARDO ROLDAN Nov 12 '20 at 13:54
3

You can use as.yearmon() function from zoo package. This function converts a Date type variable to a year-month type variable. You can subtract two year-month type variables and then multiple by 12 to get diff in months as follows:

12 * (as.yearmon(Date1) - as.yearmon(Date2))
Zheng Feng
  • 41
  • 4
  • 2
    Code only answers are normally discouraged. Could you add an explanation as to why this works or why it is better than the other solutions? – Jason Dec 09 '19 at 18:14
  • @Jason Just did! – Zheng Feng Dec 10 '19 at 19:51
  • This works, but `as.yearmon()` is part of the `zoo` package and is not part of R base. If you have zoo installed: `12 * (zoo::as.yearmon(Date1) - zoo::as.yearmon(Date2))` – Mark Egge Apr 28 '20 at 18:37
0

Date difference in months

$date1 = '2017-01-20';
$date2 = '2019-01-20';

$ts1 = strtotime($date1);
$ts2 = strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1);
Kamlesh
  • 5,233
  • 39
  • 50
0

Another short and convenient way is this:

day1 <- as.Date('1991/04/12')
day2 <- as.Date('2019/06/10')
round(as.numeric(day2 - day1)/30.42)

[1] 338

JAQuent
  • 1,137
  • 11
  • 25