【问题标题】:Standards for Date/Time addition?添加日期/时间的标准?
【发布时间】:2011-11-28 16:27:29
【问题描述】:

我正在寻找添加日期/时间的标准。我一直找不到。特别是,我希望找到一个规范来定义当您将一个月添加到像 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

甲骨文

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

(对不起格式更改,我的 oracle foo 很弱)

Java

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

【问题讨论】:

  • 地狱基本上有两种:编码地狱和日期时间地狱。
  • 我认为一个准标准存在于任何一个古老的(Unix?)库中定义strtotime()+1 week的解析等等。可悲的是,这个基本功能的文档非常少。我想知道是否值得向 PHP 之外的更多标签打开这个问题? Jon Skeet 可能会添加一两件事,他has been dealing with Date/Time libraries in the past。事实上,我想我会 ping 他。
  • @Pekka IIRC PHP 为此使用了一次 gnutime(),但 Derick 已经重写了代码。
  • @Pekka 在这种情况下,我正在比较“+1 月”和 MySQL 的 DATE_ADD(timestamp, INTERVAL 1 MONTH),它们不同。

标签: php mysql oracle datetime standards


【解决方案1】:

根据 POSIX.1-2001 标准,下个月(如在调用 mktime 之前递增 tm_mon)通过调整值直到它们适合。因此,例如,从 2001 年 1 月 31 日开始的下个月是 2001 年 3 月 3 日。这是因为 31 的 tm_mday 与 1(2 月)的 tm_mon 无效,因此它被归一化为 tm_mon 的2(三月)和tm_mday,共 3 个。

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

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

编辑:我认为我已经发现 SQL-92 是如何处理它的,显然要求从 1 月 31 日开始的下个月是一个错误。

链接:

【讨论】:

  • +1 有趣。我认为这个问题的重点不是找到 one 标准,而是找出可能使用哪些标准,以及哪个库遵循哪个标准。你知道任何定义POSIX.1-2001行为的文档吗?
  • @derobert,很好的答案。你有链接吗?标准的数量与街上人们的答案一样多。肯定没有被广泛接受的标准,而且这个 POSIX 标准的答案出乎人们的意料。
  • @JonathanM:添加了一些链接。
  • @derobert,我希望你不介意我添加的链接。如果您愿意,请随意回滚。
  • 谢谢,看起来(即使在赏金之后)问题并未在记录了他们自己的解决方案的特定空间之外正式定义。感谢您收集了一些答案,感谢所有提交添加到链接列表的人。
【解决方案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

【讨论】:

  • 另一个(Markus Kuhn 的)关于 ISO 8601 标准的观点在这里()。 RFC 3339 和 RFC 2822,都在 twinsun.com/tz/tz-link.htm 上引用,也可能是相关的。我认为主要问题是您关心的是几个月,而不是几天或几年。天和年是天文定义的,而月份是人为的构造,取决于您打算遵循的日历。
  • 此评论中链接的第一个 URL 指出:“日和年都是构建时间的有用单位,因为影响我们生活的太阳在天空中的位置是由它们描述的。但是一年中的 12 个月源于某种晦涩的神秘起源,今天没有真正的目的,只是人们习惯于拥有它们(他们甚至不描述月球的当前位置)。”
【解决方案3】:

查询:

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';

输出:

12 月 + 1 月 + 2 月 + 3 月 + 月 2011-01-31 2011-02-28 2011-03-28 2011-04-30

我的结论:

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

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

source

这里的问题是它没有提到月份实际上是从输入日期开始的月份。

【讨论】:

  • +1 太棒了,我不知道 mySQL 手册如此深入地解释了它的日期逻辑。
【解决方案4】:

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

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

【讨论】:

  • 更新:JSR 310 已实现为 Java 8 及更高版本中内置的 java.time 类。见Tutorial
【解决方案5】:

当月的第一天 + 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 是如何变成 2 月 29 日的(2012 年是闰年)。

附言我去掉了时间部分(全为零)以使其更具可读性

【讨论】:

  • 我真的在寻找基于标准的答案,而不是意见池。
  • @preinheimer,它必须是一个意见池,因为如果你向街上的人提问,你会得到不同的答案。他们不受任何标准的约束。而且由于日期软件必须模拟现实生活中对时间单位的解释,因此该软件必须要么澄清期望,要么明确定义将如何解释这些短语。
  • 这个月的倒数第二天呢?从 2001 年 1 月 29 日起下个月是什么? “下个月”与“上个月”对称吗?
  • 可能存在多个标准机构,以将流行且逻辑正确的意见编入标准。其中一个已经这样做了。
  • 我投了反对票,因为我认为这是一个非常有趣的问题,虽然我同意这个观点,但这只是一种观点,而不是一套明确的规则来作为工作的基础,这就是 OP要求。
【解决方案6】:

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

我想街上的大多数人都会同意:

  1. 您不能按特定天数定义“月”。所以...
  2. 当 1 月 31 日有人说“我会在一个月后见到你”时,他们的意思是 2 月的最后一天。基本上是加到月份上,然后找到离今天最近的天数,不要超过它。

会计是个例外,有时一个月意味着 30 天。

编辑:我问过这里的一些人,“如果是 3 月 31 日,有人说我会在一个月后见到你,你打算在哪一天见到他们?”大多数人说是 4 月 30 日,但也有少数人说是 4 月 28 日,因为还有四个星期。少数人在解释工作日程,想着“如果我们在这个工作日见面,我们会在同一个工作日再次见面”。基本上,如果他们在每个月的最后一个星期四见面,并且他们将在一个月后见面,那么将在 那个月的最后一个星期四。

那么,你去吧。 :\

【讨论】:

  • 反对者,想发表评论吗?帮助我改进我的答案以删除您的反对票。
  • 嗯,这不是问题的真正答案,是吗? OP 要求以明确的标准作为其工作的基础。我理解他的方式并不意味着标准必须被普遍接受,只是有一些组定义的规则可以使用。您是否声称不存在这样的规则集?我认为这不太可能。
  • @Pekka,因为没有办法证明标准不存在(你不能证明是否定的),所以你真的应该删除反对票,直到你证明一个可用的、被广泛接受的标准确实存在存在。到那时,我会很高兴自己投反对票。 :)
  • @Jonathan M,问题不是关于“可用的、被广泛接受的标准”,而是关于是否有任何标准的问题。因此,您的答案......嗯......不是一个。
  • @Jonathan M,我不会假设任何关于 OP 对可用性的兴趣。如果 对可用性感兴趣,则 OP 有责任表达他对所提供标准的不满,并明确要求更易于访问的标准。回答提出的问题。
【解决方案7】:

试试mysql的日期功能:

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

输入闰年日期

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

【讨论】:

    【解决方案8】:

    在.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
        */
    

    【讨论】:

      猜你喜欢
      • 2018-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-26
      • 2013-02-09
      相关资源
      最近更新 更多