The DATEDIFF function can give you the number of days between two dates. Which is more accurate, since... how do you define a month? (28, 29, 30, or 31 days?)
This depends on how you want the # of months to be defined. Answer this questions: 'What is difference in months: Feb 15, 2008 - Mar 12, 2009'. Is it defined by clear cut # of days which depends on leap years- what month it is, or same day of previous month = 1 month.
A calculation for Days:
Feb 15 -> 29 (leap year) = 14
Mar 1, 2008 + 365 = Mar 1, 2009.
Mar 1 -> Mar 12 = 12 days.
14 + 365 + 12 = 391 days.
Total = 391 days / (avg days in month = 30) = 13.03333
A calculation of months:
Feb 15 2008 - Feb 15 2009 = 12
Feb 15 -> Mar 12 = less than 1 month
Total = 12 months, or 13 if feb 15 - mar 12 is considered 'the past month'
Returns the number of months between periods P1 and P2. P1 and P2 should be in the format YYMM or YYYYMM. Note that the period arguments P1 and P2 are not date values.
mysql> SELECT PERIOD_DIFF(200802,200703);
-> 11
So it may be possible to do something like this:
Select period_diff(concat(year(d1),if(month(d1)<10,'0',''),month(d1)), concat(year(d2),if(month(d2)<10,'0',''),month(d2))) as months from your_table;
Where d1 and d2 are the date expressions.
I had to use the if() statements to make sure that the months was a two digit number like 02 rather than 2.
Is there a better way?
yes. Do not use MySQL Timestamps. Apart from the fact that they occupy 36 Bytes, they are not at all convenient to work with.
I would reccomend using Julian Date and Seconds from midnight for all date/time values.
These can be combined to form a UnixDateTime. If this is stored in a DWORD (unsigned 4 Byte Integer) then dates all the way up to 2106 can be stored as seconds since epoc, 01/01/1970
DWORD max val = 4,294,967,295 - A DWORD can hold 136 years of Seconds
Julian Dates are very nice to work with when making date calculations
UNIXDateTime values are good to work with when making Date/Time calculations
Neither are good to look at, so I use the Timestamps when I need a column that I will not be doing much calculation with, but I want an at-a-glance indication.
Converting to Julian and back can be done very quickly in a good language. Using pointers I have it down to about 900 Clks (This is also a conversion from a STRING to an INTEGER of course)
When you get into serious applications that use Date/Time information like for example the financial markets, Julian dates are de-facto.
DROP FUNCTION IF EXISTS `calcula_edad` $$
CREATE DEFINER=`root`@`localhost` FUNCTION `calcula_edad`(pFecha1 date, pFecha2 date, pTipo char(1)) RETURNS int(11)
Begin
Declare vMeses int;
Declare vEdad int;
Set vMeses = period_diff( date_format( pFecha1, '%Y%m' ), date_format( pFecha2, '%Y%m' ) ) ;
/* Si el dia de la fecha1 es menor al dia de fecha2, restar 1 mes */
if day(pFecha1) < day(pFecha2) then
Set vMeses = VMeses - 1;
end if;
if pTipo='A' then
Set vEdad = vMeses div 12 ;
else
Set vEdad = vMeses ;
end if ;
Return vEdad;
End
select calcula_edad(curdate(),born_date,'M') -- for number of months between 2 dates
What this allows you to do is pass in two TIMESTAMP or DATETIME values (or even DATE as MySQL will auto-convert) as well as the unit of time you want to base your difference on.
You can specify MONTH as the unit in the first parameter:
It basically gets the number of months elapsed from the first date in the parameter list. This solution automatically compensates for the varying amount of days in each month (28,30,31) as well as taking into account leap years — you don't have to worry about any of that stuff.
Month-difference with precision:
It's a little more complicated if you want to introduce decimal precision in the number of months elapsed, but here is how you can do it:
SELECT
username
,date_of_birth
,DATE_FORMAT(CURDATE(), '%Y') - DATE_FORMAT(date_of_birth, '%Y') - (DATE_FORMAT(CURDATE(), '00-%m-%d') < DATE_FORMAT(date_of_birth, '00-%m-%d')) AS years
,PERIOD_DIFF( DATE_FORMAT(CURDATE(), '%Y%m') , DATE_FORMAT(date_of_birth, '%Y%m') ) AS months
,DATEDIFF(CURDATE(),date_of_birth) AS days
FROM users
I needed month-difference with precision. Although Zane Bien's solution is in the right direction, his second and third examples give inaccurate results. A day in February divided by the number of days in February is not equal to a day in May divided by the number of days in May. So the second example should output ((31-5+1)/31 + 13/30 = ) 1.3043 and the third example ((29-27+1)/29 + 2/30 + 3 = ) 3.1701.
I ended up with the following query:
SELECT
'2012-02-27' AS startdate,
'2012-06-02' AS enddate,
TIMESTAMPDIFF(DAY, (SELECT startdate), (SELECT enddate)) AS days,
IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), 0, (TIMESTAMPDIFF(DAY, (SELECT startdate), LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY)) / DAY(LAST_DAY((SELECT startdate)))) AS period1,
TIMESTAMPDIFF(MONTH, LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY, LAST_DAY((SELECT enddate))) AS period2,
IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), (SELECT days), DAY((SELECT enddate))) / DAY(LAST_DAY((SELECT enddate))) AS period3,
(SELECT period1) + (SELECT period2) + (SELECT period3) AS months
One of the way is MySQL PERIOD_DIFF() returns the difference between two periods. Periods should be in the same format i.e. YYYYMM or YYMM. It is to be noted that periods are not date values.
Although it's an old topic it shows on top in google and I don't see newer questions related to Mysql to calculate the difference in months. And I needed a very precise calculation including the fraction of the month.
This for the purpose to calculate a subscription fee e.g. 8 euro per month. Then 1 day in februari does have a different price compared to other months. So the fraction of months needs to be calculated and here the precision of the fraction is based on seconds.
What it does is to split the calculation into 3 parts when calculation between @from and @to dates:
fraction of the calendar month between @from and the end of the @from calendar month
number of whole calendar months between @from and @to
fraction of the calendar month between start of the calendar month and @to
E.g from '2021-09-29 12:00:00' to '2021-11-07 00:00:00':
The 1.5 days at the end of september 2021. September does have 30
days so the fraction is 0.05 month (1.5/30).
the whole month oktober 2021 so 1 full month
The 6 full days at the begin of november 2021. November does have 30 days so the faction is 0.2 month (6/30).
So the outcome is 1.25 month.
set @from = '2021-09-29 12:00:00';
set @to = '2021-11-07 00:00:00';
select
/* part 1 */ (unix_timestamp(last_day(@from)) + 86400 - unix_timestamp(@from)) / 86400 / day(last_day(@from))
/* part 2 */ + PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM @to), EXTRACT(YEAR_MONTH FROM @from)) - 1 +
/* part 3 */ 1 - (unix_timestamp(last_day(@to)) + 86400 - unix_timestamp(@to)) / 86400 / day(last_day(@to))
month_fraction;
Exactly the same calculation but now based on fields for people not using mysql variables and easier to take over your own fields:
select
/* part 1 */ (unix_timestamp(last_day(periodStart)) + 86400 - unix_timestamp(periodStart)) / 86400 / day(last_day(periodStart))
/* part 2 */ + PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM periodTill), EXTRACT(YEAR_MONTH FROM periodStart)) - 1 +
/* part 3 */ 1 - (unix_timestamp(last_day(periodTill)) + 86400 - unix_timestamp(periodTill)) / 86400 / day(last_day(periodTill))
month_fraction
from (select '2021-09-29 12:00:00' periodStart, '2021-11-07 00:00:00' periodTill) period
For speed optimization I've used unix_timestamp which should perform fast as it is able to use mathematic calculation. The unix_timestamp returns a number in seconds. The 86400 is the number of seconds in a day.