日期/时间添加标准?

时间:2011-09-30 18:10:48

标签: php mysql oracle datetime standards

我正在寻找添加日期/时间的标准。我找不到任何东西。特别是我希望找到一个规范来定义当你将一个月添加到1月31日这样的日期时会发生什么。 2月28日(/ 29日)是正确答案吗? 3月1日? 3月2日?

我看到不同工具之间的实现不一致(在这种情况下是PHP和MySQL),而我正试图找到某种标准来为我的工作奠定基础。

不同的结果:

PHP

$end = strtotime("+1 month", 1314835200);
//1317513600   Sat, 01 Oct 2011 20:00:00 -0400

的MySQL

SELECT UNIX_TIMESTAMP(DATE_ADD(FROM_UNIXTIME(1314835200), INTERVAL 1 MONTH));
#1317427200    Fri, 30 Sep 2011 20:00:00 -0400

的Oracle

SELECT ADD_MONTHS('31-Aug-11', 1) FROM dual;
#30-SEP-11

(抱歉格式改变,我的oracle foo很弱)

爪哇

Calendar c = Calendar.getInstance();
c.clear();
c.set( 2011, Calendar.AUGUST, 31 );
c.add( Calendar.MONTH, 1 );
c.getTime()
#Fri Sep 30 00:00:00 EDT 2011

8 个答案:

答案 0 :(得分:11)

根据POSIX.1-2001标准,下个月(如在调用tm_mon之前递增mktime)是通过调整值直到它们适合来完成的。因此,例如,2001年1月31日的下个月是2001年3月3日。这是因为31的tm_mday对于1 {2月的tm_mon无效,所以它被标准化为tm_mon 2(3月)和tm_mday 3。

2000年1月31日的下一个月是2000年3月2日,因为2月份当年有29天。从1月开始的下个月,1 2038年不存在,具体取决于。

The great thing about standards is there are so many to chose from。检查SQL标准,我打赌你可以找到下个月的不同含义。我怀疑ISO 8601可能会给你另一个选择。重点是,有许多不同的行为,“下个月”的含义非常特定于域名。

编辑:我我发现SQL-92如何处理它,显然是从1月31日开始要求下个月出错。

链接:

答案 1 :(得分:2)

我相信事实上的标准是ISO 8601.不幸的是,有很多含糊之处,例如:

未定义日期算术

2001-03-30 + P1M = 2001-04-29 (Add 30 days)
2001-03-30 + P1M = 2001-04-30 (Add 1 mon.)

添加不是可交换的或关联的

2001-03-30 + P1D + P1M = 2001-04-30
2001-03-30 + P1M + P1D = 2001-05-01

减法不是加法的倒数。

小数部分的精度可能会有所不同。

完整规范可在http://www.iso.org/iso/catalogue_detail.htm?csnumber=26780

找到

我认为每种产品都试图坚持不可能实施的标准。模棱两可的部分是开放的解释,所以每个人都解释。这是打开我们Y2K错误的标准!

我自己,我赞成将日期/时间转换为基于1970的数字(UNIX时间戳)的实现,执行计算并转换回来。我相信这是Oracle / MySQL采用的方法。令我感到惊讶的是,这个问题没有得到更多的关注,因为在很多应用程序中它非常重要,有时也很重要。谢谢你的问题!

编辑:在进行更多阅读时,我发现了Joe Celko对不同日期/时间表示和标准化HERE的想法。

答案 2 :(得分:1)

查询:

SELECT
ADDDATE(DATE('2010-12-31'), INTERVAL 1 MONTH) 'Dec + Month',
ADDDATE(DATE('2011-01-31'), INTERVAL 1 MONTH) 'Jan + Month',
ADDDATE(DATE('2011-02-28'), INTERVAL 1 MONTH) 'Feb + Month',
ADDDATE(DATE('2011-03-31'), INTERVAL 1 MONTH) 'Mar + Month';

输出:

    Dec + Month  Jan + Month  Feb + Month   Mar + Month
    2011-01-31   2011-02-28   2011-03-28    2011-04-30

我的结论:

  1. 计算输入日期的月份天数。
  2. 将这么多天添加到输入日期。
  3. 检查结果日期中的日期是否超过了结果月份中的最大天数。
  4. 如果是,则将结果日期更改为结果月份的最大日期。
  5.   

    如果您添加MONTH,YEAR_MONTH或YEAR,并且结果日期的日期大于新月的最大日期,则将该日期调整为新月的最大天数

    source

    问题在于它没有提到月份实际上是输入日期的月份。

答案 3 :(得分:1)

Java中的

Joda-Time选择创建无效日期的先前有效日期。例如,2011-01-31 + P1M = 2011-02-28。我相信这是日期时间库中最广泛选择的默认选择,因此是事实上的标准。

ThreeTen/JSR-310为此提供了一种策略模式,有四种选择,请参阅code

更有趣的是2011-01-31 + P1M-1D的答案是什么的问题。如果你添加月份,然后解决无效日期,然后减去当天,你得到2011-02-27。但我认为大多数用户都期待2011-02-28,因为这个时期是一次性加入。了解ThreeTen如何处理此here

我曾考虑过在日期/时间计算或实际规范中编写通用最佳实践,但还没有真正的时间!

答案 4 :(得分:0)

该月的第一天+ 1个月应该等于下个月的第一天。在SQL Server上尝试这个

          SELECT CAST ('01/01/2012' AS DateTime), DATEADD (m, 1, '01/01/2012')
UNION ALL SELECT CAST ('02/01/2012' AS DateTime), DATEADD (m, 1, '02/01/2012')
UNION ALL SELECT CAST ('03/01/2012' AS DateTime), DATEADD (m, 1, '03/01/2012')
UNION ALL SELECT CAST ('04/01/2012' AS DateTime), DATEADD (m, 1, '04/01/2012')
UNION ALL SELECT CAST ('05/01/2012' AS DateTime), DATEADD (m, 1, '05/01/2012')

这导致

----------------------- -----------------------
2012-01-01              2012-02-01             
2012-02-01              2012-03-01             
2012-03-01              2012-04-01             
2012-04-01              2012-05-01             
2012-05-01              2012-06-01             

本月最后一天+ 1个月应该等于下个月的最后一天。这应该是下个月,当月,10个月下来等等。

          SELECT CAST ('01/31/2012' AS DateTime), DATEADD (m, 1, '01/31/2012')
UNION ALL SELECT CAST ('01/30/2012' AS DateTime), DATEADD (m, 1, '01/30/2012')
UNION ALL SELECT CAST ('01/29/2012' AS DateTime), DATEADD (m, 1, '01/29/2012')
UNION ALL SELECT CAST ('01/28/2012' AS DateTime), DATEADD (m, 1, '01/28/2012')
UNION ALL SELECT CAST ('01/27/2012' AS DateTime), DATEADD (m, 1, '01/27/2012')
UNION ALL SELECT CAST ('01/26/2012' AS DateTime), DATEADD (m, 1, '01/26/2012')

这导致

----------------------- -----------------------
2012-01-31              2012-02-29             
2012-01-30              2012-02-29             
2012-01-29              2012-02-29             
2012-01-28              2012-02-28             
2012-01-27              2012-02-27             
2012-01-26              2012-02-26             

了解31,30,29如何成为29号(2012年是闰年)。

P.S。我把时间部分(全部为零)取下来以使其更具可读性

答案 5 :(得分:0)

没有被广泛接受的标准。不同实现的原因是人们无法就标准应该达成什么达成一致。许多流行的软件系统给出了没有人会想到的答案。因此,始终需要文档来告诉用户您的系统将提供什么。但是,您可以根据您认为大多数人们期望的内容来选择一种方法。

我认为大街上的大多数人会同意:

  1. 您无法在特定天数内定义“月份”。所以...
  2. 当它是1月31日,有人说“我会从今天起一个月见到你”,他们指的是二月的最后一天。基本上,它会添加到月份,然后找到最接近今天的日期编号而不会超过它。
  3. 例外是会计,有时一个月意味着30天。

    编辑:我问过这里的一些人,“如果是3月31日,有人说我会在今天一个月见到你,你将在哪一天见到他们?”大多数人说,4月30日,但有一些人说4月28日,因为距离它还有四个星期。少数人正在解释工作时间表并思考“如果我们在这个工作日见面,我们将在同一个工作日再次见面”。基本上,如果他们在本月的最后一个星期四见面,并且他们将在一个月内见面,那么它将在那个月的最后一个星期四。

    所以,你去吧。 :\

答案 6 :(得分:0)

尝试使用mysql日期函数:

SELECT ADDDATE('2011-01-31',INTERVAL 1 MONTH)// 2011-02-28

闰年输入日期

SELECT ADDDATE('2012-01-31',INTERVAL 1 MONTH)// 2012-02-29

答案 7 :(得分:0)

在.NET框架中,System.DateTime.AddMonths的行为如下:

  

AddMonths方法计算得到的月份和年份   考虑到闰年和一个月的天数,那么   调整生成的DateTime对象的日期部分。如果   结果日期不是最后一个月的有效日期   使用结果月份的有效日期。例如,3月31日+ 1   月= 4月30日[而不是4月31日]。

我已经测试了它是如何工作的:

Console.WriteLine(new DateTime(2008,2,27).AddMonths(1));
Console.WriteLine(new DateTime(2008,2,28).AddMonths(1));
Console.WriteLine(new DateTime(2008,2,29).AddMonths(1));
Console.WriteLine(new DateTime(2011,2,27).AddMonths(1));
Console.WriteLine(new DateTime(2011,2,28).AddMonths(1));
Console.WriteLine(new DateTime(2008,1,30).AddMonths(1));
Console.WriteLine(new DateTime(2008,1,31).AddMonths(1));
Console.WriteLine(new DateTime(2011,1,30).AddMonths(1));
Console.WriteLine(new DateTime(2011,1,31).AddMonths(1));
/* output
3/27/2008 12:00:00 AM
3/28/2008 12:00:00 AM
3/29/2008 12:00:00 AM
3/27/2011 12:00:00 AM
3/28/2011 12:00:00 AM
2/29/2008 12:00:00 AM
2/29/2008 12:00:00 AM
2/28/2011 12:00:00 AM
2/28/2011 12:00:00 AM
    */