【问题标题】:Why does php datetime diff depend on time zones?为什么 php datetime diff 依赖于时区?
【发布时间】:2017-03-14 06:14:22
【问题描述】:

见以下代码:

function printDiff($tz) {
    $d1 = new DateTime("2015-06-01", new DateTimeZone($tz));
    $d2 = new DateTime("2015-07-01", new DateTimeZone($tz));
    $diff = $d1->diff($d2);
    print($diff->format("Year: %Y Month: %M Day: %D"). PHP_EOL);
}
printDiff("UTC");
printDiff("Australia/Melbourne");

结果是:

Year: 00 Month: 01 Day: 00
Year: 00 Month: 00 Day: 30

问题:

  • 相邻两个月(6 月 1 日和 7 月 1 日)的同一天之间的差异怎么可能与 1 个月不同?
  • 如果给定日期之间没有 DST,没有闰年,没有什么特别的,为什么计算模式取决于我使用的时区?
  • 我在哪里可以找到关于如何计算差异的算法的确切描述?

【问题讨论】:

  • FWIW,像“月”这样定义模糊的单位中的两个日期之间的差异无论如何都是相当模棱两可的,所以我不希望有任何特别一致的结果。尽管如此,有趣的问题(和答案)。

标签: php date timezone diff intervals


【解决方案1】:

date 扩展以 GMT 格式存储时间值。 GMT 偏移量is stored separately。它应用于计算的后期阶段,以进行修正。

特别是,DateTime::diff 在内部调用 timelib_diff 函数,该函数计算两个日期之间的差异,应用 DST 校正,然后按此顺序规范化其内部结构。

当两个日期都在 UTC 时,函数会比较以下内容:

  • Timestamp=1433116800, year=2015, month=6, day=1, hour=0, minute=0, second=0
  • Timestamp=1435708800, year=2015, month=7, day=1, hour=0, minute=0, second=0

它会相应地减去年、月、日、小时、分钟和秒。差值正好是一个月。所以normalization之后内部结构没有修改。

当两个日期都在 Australia/Melbourne 时,该函数会比较以下内容:

  • Timestamp=1433080800, year=2015, month=5, day=31, hour=14, minute=0, second=0
  • Timestamp=1435672800, year=2015, month=6, day=30, hour=14, minute=0, second=0

两个日期都是通过减去 10 小时(没有 DST 校正的时区偏移)获得的。如我们所见,如果需要(特别是不需要),在减去时间值之后的 DST 校正is applied。归一化前的区别是:

0 years, 1 month, -1 day, 0 hours, 0 minutes, 0 seconds

由于我们在月份之间存在偏移,因此归一化函数将差异计算为

0 years, 0 months, 30 days, 0 hours, 0 minutes, 0 seconds

DateInterval::format 方法很简单。它只是将占位符替换为timelib_rel_time 结构的成员。这就是为什么我们在 UTC 获得 1 个月,在澳大利亚/墨尔本获得 30 天

a 格式字符

这看起来有点不可靠。但是,timelib_rel_time 结构中有一个days 成员,即always equals to the difference in days。该值可通过a 格式字符获得:

function printDiff($tz) {
  $d1 = new DateTime("2015-06-01", new DateTimeZone($tz));
  $d2 = new DateTime("2015-07-01", new DateTimeZone($tz));
  $diff = $d1->diff($d2);
  print($diff->format("Year: %Y Month: %M Day: %D days: %a"). PHP_EOL);
}

printDiff("UTC");
printDiff("Australia/Melbourne");

输出

Year: 00 Month: 01 Day: 00 days: 30
Year: 00 Month: 00 Day: 30 days: 30

附言

此答案中的值是在TIMELIB_DEBUG 宏的帮助下获得的。

【讨论】:

  • 谢谢你,鲁斯兰。它解释了很多事情,现在完全清楚了。我仍然不确定这是否是正确的(即直观预期的)行为,但它非常准确地解释了它。谢谢。
猜你喜欢
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-05
  • 1970-01-01
相关资源
最近更新 更多