MySQL中日期之间的月差

Posted

技术标签:

【中文标题】MySQL中日期之间的月差【英文标题】:The difference in months between dates in MySQL 【发布时间】:2010-09-22 06:50:26 【问题描述】:

我正在计算两个日期时间字段之间的月数。

有没有比获取 unix 时间戳和除以 2 592 000(秒)并在 mysql 中四舍五入更好的方法?

【问题讨论】:

【参考方案1】:

任何给定两个日期之间的月差:

我很惊讶这还没有被提及:

看看 MySQL 中的 TIMESTAMPDIFF() 函数。

这允许你做的是传入两个 TIMESTAMPDATETIME 值(甚至是 DATE,因为 MySQL 将自动转换)以及你想要作为差异基础的时间单位。

可以在第一个参数中指定MONTH为单位:

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-04')
-- Outputs: 0

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-05')
-- Outputs: 1

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-15')
-- Outputs: 1

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-12-16')
-- Outputs: 7

它基本上获取从参数列表中的第一个日期开始经过的月数。此解决方案会自动补偿每个月 (28,30,31) 的不同天数,并考虑闰年 - 您不必担心任何这些问题。


精确的月差:

如果您想在经过的月数中引入小数精度,这会稍微复杂一些,但您可以这样做:

SELECT 
  TIMESTAMPDIFF(MONTH, startdate, enddate) +
  DATEDIFF(
    enddate,
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate)
    MONTH
  ) /
  DATEDIFF(
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate) + 1
    MONTH,
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate)
    MONTH
  )

startdateenddate 是您的日期参数,无论是来自表中的两个日期列还是来自脚本的输入参数:

例子:

With startdate = '2012-05-05' AND enddate = '2012-05-27':
-- Outputs: 0.7097

With startdate = '2012-05-05' AND enddate = '2012-06-13':
-- Outputs: 1.2667

With startdate = '2012-02-27' AND enddate = '2012-06-02':
-- Outputs: 3.1935

【讨论】:

这是正确答案,对所选答案的PERIODDIFF 评论的所有赞成票都非常误导 谢谢,就我而言,我需要包含日期,因此我将所有“enddate”替换为“date_add(enddate,interval 1 day)”。然后 2014-03-01 到 2014-05-31 将给出 3.00 而不是 2.97 非常感谢......特别感谢“精确的月差:”。你是mysql之星 同意。这是正确的答案。正是我想要的!谢谢。 如果我手动解释您的示例 startdate='2012-05-05' 和 enddate='2012-06-13' 那么您将看到 5 月 31 天中的 27 天和 30 天中的 12 天6 月的天数,所以它应该输出 0,87097 + 0.4 = 1,27097,而你的输出是 1.2667。另请参阅我在此页面上的答案以获得精确的精度计算。【参考方案2】:

PERIOD_DIFF 计算两个日期之间的月份。

例如,计算 now() 和 your_table 中时间列之间的差异:

select period_diff(date_format(now(), '%Y%m'), date_format(time, '%Y%m')) as months from your_table;

【讨论】:

知道当它可能是 11 个月零 1 天,也就是 12 个月时它会做什么,但如果你改为 30 天月,那么它仍然是 11? 11 个月和 1 天将在上面的示例中返回 11 个月。 PERIOD_DIFF 没有 30 天和 31 天的月份。【参考方案3】:

我也使用PERIOD_DIFF。要获取日期的年份和月份,我使用函数EXTRACT:

  SELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM time)) AS months FROM your_table;

【讨论】:

难以置信!与DATE_FORMAT 方法相比,它使用的时间减少了 50%,谢谢! +1 这里是unix时间戳SELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time, "%Y%m%d"))) AS months FROM your_table; 这是不正确的,PERIOD_DIFF 不采用日值,因此如果月数少于整数,您将计算四舍五入到最近月份的月数。您应该编辑您的答案以明确这一点。 是的,详细说明使用此方法的警告:它给出了时间段从当前月份跨越的日历月数。例如,如果时差为 5 天,但两个日期在同一月份,则结果将为 0 个月。如果时差是 5 天,但从 9 月跨越到 10 月,则为 1 个月。 非常适合我!只计算月份,这就是我需要的。【参考方案4】:

DATEDIFF 函数可以为您提供两个日期之间的天数。哪个更准确,因为...你如何定义一个月? (28、29、30 还是 31 天?)

【讨论】:

PERIODDIFF可以准确计算月数。 “更准确”在日期方面具有令人难以置信的主观性。例如,如果您所在的企业有每月周期,需要知道两个日期之间的月数可能比天数更重要,因为您在上面列出的原因。一个月有多长?如果我需要一个月的计数,我不能简单地除以 30。 这不是正确答案;您应该在下面标记Max's 答案正确。 如果您想要整月数,正确计算闰年和一个月中不同的天数,Max 的答案是不正确的。 PERIODDIFF 只需要一个 YYYYMM 值,因此除非您自己计算通过 YYYYMM 值之前的天数,否则您计算的只是月数,如果小于整数,则四舍五入到最接近的月份个月。请参阅下面 Zane 的回答。 为什么PERIODDIFF 评论有这么多赞? Perioddiff 不采用日期值,因此如果月份数少于整数,则您将计算四舍五入到最接近月份的月份数【参考方案5】:

正如这里的许多答案所示,“正确”答案完全取决于您的需要。就我而言,我需要四舍五入到最接近的整数

考虑以下示例: 1 月 1 日 -> 1 月 31 日:整整 0 个月,将近 1 个月。 1 月 1 日 -> 2 月 1 日?整整 1 个月,正好是 1 个月。

要获得整(完整)个月的数量,请使用:

SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-01-31');  => 0
SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-02-01');  => 1

要获得以月为单位的四舍五入持续时间,您可以使用:

SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1
SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1

这精确到 +/- 5 天,范围超过 1000 年。 Zane 的回答显然更准确,但我不喜欢它太冗长。

【讨论】:

我不同意... SELECT TIMESTAMPDIFF(MONTH, '2018-01-31', '2018-02-04');相隔 0 个月... SELECT TIMESTAMPDIFF(MONTH, '2018-01-31', '2018-03-01');给你 1... 如果 5 天是 0,那么 29 天是 1? @Scott,一月到三月之间有一整月(称为二月)。 TIMESTAMPDIFF 仅给出日期之间的完整月数。如果您希望任何一个月大小的时间段等于一个月,请使用我给出的四舍五入版本。【参考方案6】:

我更喜欢这种方式,因为每个人第一眼都会明白:

SELECT
    12 * (YEAR(to) - YEAR(from)) + (MONTH(to) - MONTH(from)) AS months
FROM
    tab;

【讨论】:

Stanislav - 如果您想按月数了解数据,此方法完全正确。我有一些按月(1 月/2 月/3 月等)显示销售额的报告,但是有太多数据(按季度/年/过去几年的图表等),我无法按任何分组。我需要提取原始数据并循环遍历它 - 当它是 1/31 时,许多计算将“下个月”放到 3 月,而我们人类知道它是 2 月。如果它是 2/4,一些计算表明1/31 是“本月”,但 1/28 是“上个月”【参考方案7】:

来自 MySQL 手册:

PERIOD_DIFF(P1,P2)

返回周期 P1 和 P2 之间的月数。 P1 和 P2 的格式应为 YYMM 或 YYYYMM。请注意,句点参数 P1 和 P2 不是日期值。

mysql> SELECT PERIOD_DIFF(200802,200703); -> 11

所以有可能做这样的事情:

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;

其中 d1 和 d2 是日期表达式。

我必须使用 if() 语句来确保月份是一个两位数,例如 02 而不是 2。

【讨论】:

【参考方案8】:

有没有更好的方法? 是的。不要使用 MySQL 时间戳。除了它们占用 36 个字节之外,它们使用起来一点也不方便。 对于所有日期/时间值,我建议从午夜开始使用 Julian Date 和 Seconds。 这些可以组合起来形成一个 UnixDateTime。如果将其存储在 DWORD(无符号 4 字节整数)中,则可以将一直到 2106 的日期存储为自 epoc,01/01/1970 以来的秒数 DWORD max val = 4,294,967,295 - 一个 DWORD 可以保存 136 年的秒数

在进行日期计算时,朱利安日期非常适合使用 UNIXDateTime 值在进行日期/时间计算时非常适合使用 两者都不好看,所以当我需要一个不会做太多计算的列时,我会使用时间戳,但我想要一目了然的指示。

用一门好的语言可以很快地转换为 Julian 并返回。使用指针,我将它降低到大约 900 个 Clks(这当然也是从 STRING 到 INTEGER 的转换)

当您进入使用日期/时间信息的严肃应用程序(例如金融市场)时,儒略日期是事实上的。

【讨论】:

您从哪里获得有关它们占用 36 个字节的事实的信息? MySQL 手动声明 TIMESTAMP 列的存储要求是 4 字节。 dev.mysql.com/doc/refman/5.1/en/storage-requirements.html 现在就开始规划 Y2106 漏洞吧! ;-)【参考方案9】:

查询将是这样的:

select period_diff(date_format(now(),"%Y%m"),date_format(created,"%Y%m")) from customers where..

提供自在客户记录上创建日期戳以来的多个日历月,让 MySQL 在内部进行月份选择。

【讨论】:

【参考方案10】:
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

【讨论】:

【参考方案11】:

执行此代码,它将创建一个函数 datedeifference,它会为您提供日期格式 yyyy-mm-dd 的差异。

DELIMITER $$

CREATE FUNCTION datedifference(date1 DATE, date2 DATE) RETURNS DATE
NO SQL

BEGIN
    DECLARE dif DATE;
    IF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < 0    THEN
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(DATE_SUB(date1, INTERVAL 1 MONTH)), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    ELSEIF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < DAY(LAST_DAY(DATE_SUB(date1, INTERVAL 1 MONTH))) THEN
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    ELSE
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    END IF;

RETURN dif;
END $$
DELIMITER;

【讨论】:

您认为您可以清理一下您的代码并对其进行注释以使其更易于理解吗?谢谢【参考方案12】:

这取决于您希望如何定义月数。回答这个问题:“月份有什么区别:2008 年 2 月 15 日 - 2009 年 3 月 12 日”。它是由明确的 # 天数定义的,这取决于闰年 - 它是几月,还是上个月的同一天 = 1 个月。

天数计算

2 月 15 日 -> 29(闰年)= 14 2008 年 3 月 1 日 + 365 = 2009 年 3 月 1 日。 3 月 1 日 -> 3 月 12 日 = 12 天。 14 + 365 + 12 = 391 天。 总计 = 391 天 /(每月平均天数 = 30)= 13.03333

月份的计算

2008 年 2 月 15 日 - 2009 年 2 月 15 日 = 12 2 月 15 日 -> 3 月 12 日 = 不到 1 个月 总计 = 12 个月,如果 2 月 15 日 - 3 月 12 日被视为“过去一个月”,则为 13 个月

【讨论】:

【参考方案13】:
SELECT * 
FROM emp_salaryrevise_view 
WHERE curr_year Between '2008' AND '2009' 
    AND MNTH Between '12' AND '1'

【讨论】:

【参考方案14】:

我需要精确的月差。尽管 Zane Bien 的解决方案方向正确,但他的第二个和第三个示例给出了不准确的结果。二月的一天除以二月的天数不等于五月的一天除以五月的天数。所以第二个例子应该输出 ((31-5+1)/31 + 13/30 = ) 1.3043 和第三个例子 ((29-27+1)/29 + 2/30 + 3 = ) 3.1701。

我最终得到了以下查询:

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

【讨论】:

【参考方案15】:

PERIOD_DIFF() 函数

其中一种方法是 MySQL PERIOD_DIFF() 返回两个周期之间的差异。期间应采用相同的格式,即 YYYYMM 或 YYMM。请注意,句点不是日期值。

代码:

SELECT PERIOD_DIFF(200905,200811);

【讨论】:

【参考方案16】:

您可以通过这种方式获得年、月和日:

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

【讨论】:

【参考方案17】:

你也可以试试这个:

select MONTH(NOW())-MONTH(table_date) as 'Total Month Difference' from table_name;

select MONTH(Newer_date)-MONTH(Older_date) as 'Total Month Difference' from table_Name;

【讨论】:

【参考方案18】:

给出开始日期为 ins_frm 和结束日期为 ins_to 的简单答案

SELECT convert(TIMESTAMPDIFF(year, ins_frm, ins_to),UNSIGNED) as yrs,
       mod(TIMESTAMPDIFF(MONTH, ins_frm, ins_to),12) mnths
FROM table_name

享受:)))

【讨论】:

【参考方案19】:

试试这个

SELECT YEAR(end_date)*12 + MONTH(end_date) - (YEAR(start_date)*12 + MONTH(start_date))

【讨论】:

欢迎来到 Stack Overflow!请务必检查问题的日期以及现有答案是否具有相同的解决方案。【参考方案20】:

虽然这是一个老话题,但它显示在谷歌的顶部,我没有看到与 Mysql 相关的新问题来计算月差。我需要一个非常精确的计算,包括月份的分数。

这是为了计算订阅费,例如每月8欧元。然后 2 月的 1 天与其他月份相比确实有不同的价格。所以需要计算月份的分数,这里分数的精度是基于秒的。

它的作用是在@from和@to日期之间计算时将计算分成3部分:

    @from 和@from 日历月结束之间的日历月的分数 @from 和 @to 之间的整个日历月数 日历月开始与@to 之间的日历月分数

例如从“2021-09-29 12:00:00”到“2021-11-07 00:00:00”:

    2021年9月末的1.5天。9月确实有30天 天,因此分数为 0.05 个月 (1.5/30)。 2021 年 10 月整整一个月,所以整整一个月 2021 年 11 月开始的 6 天。11 月确实有 30 天,所以阵营是 0.2 个月 (6/30)。

所以结果是 1.25 个月。

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;

完全相同的计算,但现在基于不使用 mysql 变量的人的字段,并且更容易接管您自己的字段:

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

对于速度优化,我使用了unix_timestamp,它应该执行得很快,因为它能够使用数学计算。 unix_timestamp 以秒为单位返回一个数字。 86400 是一天中的秒数。

【讨论】:

【参考方案21】:

这个查询对我有用:)

SELECT * FROM tbl_purchase_receipt
WHERE purchase_date BETWEEN '2008-09-09' AND '2009-09-09'

它只需要两个日期并检索它们之间的值。

【讨论】:

完全错误的答案,他要数月

以上是关于MySQL中日期之间的月差的主要内容,如果未能解决你的问题,请参考以下文章

MySQL中日期之间的月份差异

获取日期之间的月份列表

计算蜂巢中两个日期之间的月份

在JAVA中获取日期和今天之间的月数[重复]

Pandas - 两个日期之间的月数

r 中每年两个日期之间的月数