TL;DR 不要 使用 UNIX 时间戳。 不要 Do not use time()
。如果你这样做, 请做好准备, 以防其 98.0825% 的可靠性让你失望。使用 DateTime(或 Carbon)。
正确 答案 是 Saksham Gupta 给出的答案(其他答案也正确):
$date1 = new DateTime('2010-07-06');
$date2 = new DateTime('2010-07-09');
$days = $date2->diff($date1)->format('%a');
或者以单行程序形式:
/**
* Number of days between two dates.
*
* @param date $dt1 First date
* @param date $dt2 Second date
* @return int
*/
function daysBetween($dt1, $dt2) {
return date_diff(
date_create($dt2),
date_create($dt1)
)->format('%a');
}
需要注意的是:“%a”似乎表示 绝对 天数。如果您希望将其作为有符号整数,即当第二个日期早于第一个日期时为负数,则需要使用“%r”前缀(即 format('%r%a')
)。
如果您确实必须使用 UNIX 时间戳, 请将时区设置为 GMT, 以避免 大多数 陷阱。
长答案:为什么除以 24*60*60(又名 86400)不安全
大多数使用 UNIX 时间戳(以及将其转换为天数的 86400)的答案都做出了两个假设,这两个假设结合在一起,可能会导致 错误的结果 和 细微错误 ,甚至在成功部署后的几天、几周或几个月后才会出现。这并不是说解决方案不起作用——它有效。今天。但它明天可能会停止工作。
”时,如果从现在到“昨天”所表示的时刻之间还不 一天 ,计算机可能会如实地回答 零 。
通常,将“天”转换为 UNIX 时间戳时,获得的是 该特定天的 午夜
因此,在 10 月 1 日午夜至 10 月 15 日午夜之间,已经过去了 15 天。但在 10 月 1 日 13:00 至 10 月 15 日 14:55 之间, 15 天减 5 分钟 ,并且大多数使用 floor()
或进行隐式整数转换的 都会报告比预期少一天 .
因此,\'Ymd H:i:s 是多少天前\'?将得出 错误答案 .
第二个错误是将一天等同于 86400 秒。这 几乎总是 正确的——这种情况经常发生,以至于忽略了不等的情况。但是,当夏令时开始发挥作用时,每年至少有两次连续午夜之间的秒数 肯定 不是 86400。跨 DST 边界比较两个日期将得出错误的答案。
因此,即使你使用“黑客”将所有日期时间戳强制为固定小时,比如午夜(当你只指定日-月-年而不是时-分-秒时,各种语言和框架也会隐式地执行此操作;MySQL 等数据库中的 DATE 类型也是如此),广泛使用的公式
FLOOR((unix_timestamp(DATE2) - unix_timestamp(DATE1)) / 86400)
或者
floor((time() - strtotime($somedate)) / 86400)
例如,当 DATE1 和 DATE2 处于一年中的同一 DST 段时,将返回 17;但即使时:分:秒部分相同,参数也可能为 17.042,更糟糕的是,当它们处于不同的 DST 段并且时区支持 DST 时,参数可能为 16.958。使用 floor() 或任何隐式截断为整数会将本应为 17 的值转换为 16。在其他情况下,像 \'$days > 17\' 这样的表达式将返回 true
17.042,即使这看起来好像已过去的天数是 18。
事情变得更加糟糕,因为这样的代码 不能 跨平台移植,因为 有些平台可能会应用闰秒,有些则不会 。在那些 支持 ,两个日期之间的差值不是 86400 而是 86401,或者可能是 86399。因此,5 月份运行正常且通过所有测试的代码将在明年 6 月失效,因为那时 12.99999 天被认为是 12 天而不是 13 天。2015 年运行正常的两个日期在 2017 年将无法运行 —— 日期 相同 ,而且都不是闰年。而在 2018-03-01 和 2017-03-01 之间,在那些关心闰年的平台上, 366 天而不是 365 天,这使得 2018 年成为闰年(但事实并非如此)。
因此,如果您确实想使用 UNIX 时间戳:
正确的 方案 是
无论您的解决方案是什么,请测试它!
函数 funcdiff
在现实世界场景中实现了其中一种解决方案(碰巧的是,它是最初被接受的解决方案 - 但后来一直未被接受)。
<?php
$tz = 'Europe/Rome';
$yearFrom = 1980;
$yearTo = 2020;
$verbose = false;
function funcdiff($date2, $date1) {
$now = strtotime($date2);
$your_date = strtotime($date1);
$datediff = $now - $your_date;
return floor($datediff / (60 * 60 * 24));
}
########################################
date_default_timezone_set($tz);
$failures = 0;
$tests = 0;
$dom = array ( 0, 31, 28, 31, 30,
31, 30, 31, 31,
30, 31, 30, 31 );
(array_sum($dom) === 365) || die("Thirty days hath September...");
$last = array();
for ($year = $yearFrom; $year < $yearTo; $year++) {
$dom[2] = 28;
// Apply leap year rules.
if ($year % 4 === 0) { $dom[2] = 29; }
if ($year % 100 === 0) { $dom[2] = 28; }
if ($year % 400 === 0) { $dom[2] = 29; }
for ($month = 1; $month <= 12; $month ++) {
for ($day = 1; $day <= $dom[$month]; $day++) {
$date = sprintf("%04d-%02d-%02d", $year, $month, $day);
if (count($last) === 7) {
$tests ++;
$diff = funcdiff($date, $test = array_shift($last));
if ((double)$diff !== (double)7) {
$failures ++;
if ($verbose) {
print "There seem to be {$diff} days between {$date} and {$test}\n";
}
}
}
$last[] = $date;
}
}
}
print "This function failed {$failures} of its {$tests} tests";
print " between {$yearFrom} and {$yearTo}.\n";
结果是,
This function failed 280 of its 14603 tests
恐怖故事:“节省时间”的代价
一切始于 2014 年末。一位聪明的程序员决定在最多需要三十秒的计算中节省几微秒,方法是在多个地方插入臭名昭著的 \'(MidnightOfDateB-MidnightOfDateA)/86400\' 代码。这是一项明显的优化,他甚至没有记录下来,而这项优化通过了集成测试,并以某种方式潜伏在代码中几个月,却无人注意。
这件事发生在一个计算几位顶级销售员工资的程序中,其中最不重要的一位的影响力比整个五人程序员团队的影响力加起来还要大得多。2015 年 3 月 28 日,夏令时区启动,漏洞出现—— 其中一些 人被少付了一整天的巨额佣金。更糟糕的是,他们中的大多数人周日不上班,而且快到月底了,他们利用这一天来赶上他们的发票。他们肯定 不高兴 。
更糟糕的是,他们失去了(已经很少了)对程序 设计 不是 获得了 ——一个完整、详细的代码审查,其中的测试用例以外行人能理解的语言进行运行和评论(加上接下来几周的大量红地毯待遇)。
我能说什么呢:积极的一面是,我们摆脱了大量技术债务,并且能够重写和重构一些乱七八糟的东西,这些乱七八糟的东西让人回想起 90 年代 COBOL 泛滥的时期。毫无疑问,现在程序运行得更好了,当任何可疑的东西出现时,有更多的调试信息可以快速定位。我估计,在可预见的未来,仅这最后一件事就可以每月节省一到两个人的时间,因此灾难 会有 一线希望,甚至是一线希望。
不利的一面是,整个风波让公司预先损失了大约 20 万欧元,另外还损失了面子,毫无疑问还损失了一些议价能力(因此,损失的钱也更多了)。
负责“优化”的人早在 2014 年 12 月就换了工作,远在灾难发生之前,但仍有传言称要起诉他并要求赔偿。而高层对此并不满意,认为这是“上一个人的错”——这看起来像是一场阴谋,让我们把这件事说得一清二楚,最终,我们在那一年的剩余时间里一直处于困境之中,团队中的一名成员在那个夏天末辞职了。
百分之九十九的情况下,“86400 hack”都能完美运行。(例如在 PHP 中, strtotime()
会忽略 DST,并报告在 10 月最后一个星期六的午夜和下周一的午夜之间恰好过去了 2 * 24 * 60 * 60 秒,即使这显然不是 真的 ……两个错误恰好可以凑成一个正确)。
女士们先生们,这就是 一个 没有实现的例子。与安全气囊和安全带一样,您可能永远 不需要 或 DateTime
的复杂性(和易用性) Carbon
。但有一天,您 可能 (或您必须 证明 您考虑过这一点)的那一天会像夜里的贼一样到来(可能是十月某个星期天的凌晨 2 点)。做好准备。