如何使用PHP找到两个日期之间的天数?
当前回答
TL;DR不使用UNIX时间戳。不要利用时间()。如果你这样做了,要做好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()或进行隐式整数转换的解决方案将比预期少报告一天。
那么,“Y-m-d H:i:s是多少天前的?”会得到错误的答案。
第二个错误是把一天等同于86400秒。这几乎总是正确的——它经常发生,以至于人们忽略了它没有发生的时候。但当夏时制开始实施时,两个连续午夜之间的秒距肯定不会达到每年至少两次。跨DST边界比较两个日期将产生错误的答案。
因此,即使您使用强制所有日期时间戳到固定时间的“hack”,比如午夜(当您只指定日-月-年而不指定小时-分-秒时,这也会被各种语言和框架隐式完成;在数据库(如MySQL)中的DATE类型也是如此,这是广泛使用的公式
FLOOR((unix_timestamp(DATE2) - unix_timestamp(DATE1)) / 86400)
or
floor((time() - strtotime($somedate)) / 86400)
will return, say, 17 when DATE1 and DATE2 are in the same DST segment of the year; but even if the hour:minute:second part is identical, the argument might be 17.042, and worse still, 16.958 when they are in different DST segments and the time zone is DST-aware. The use of floor() or any implicit truncation to integer will then convert what should have been a 17 to a 16. In other circumstances, expressions like "$days > 17" will return true for 17.042, even if this will look as if the elapsed day count is 18.
And things grow even uglier since such code is not portable across platforms, because some of them may apply leap seconds and some might not. On those platforms that do, the difference between two dates will not be 86400 but 86401, or maybe 86399. So code that worked in May and actually passed all tests will break next June when 12.99999 days are considered 12 days instead of 13. Two dates that worked in 2015 will not work in 2017 -- the same dates, and neither year is a leap year. And between 2018-03-01 and 2017-03-01, on those platforms that care, 366 days will have passed instead of 365, making 2018 a leap year (which it is not).
因此,如果您真的想使用UNIX时间戳:
use round() function wisely, not floor(). as an alternative, do not calculate differences between D1-M1-YYY1 and D2-M2-YYY2. Those dates will be really considered as D1-M1-YYY1 00:00:00 and D2-M2-YYY2 00:00:00. Rather, convert between D1-M1-YYY1 22:30:00 and D2-M2-YYY2 04:30:00. You will always get a remainder of about twenty hours. This may become twenty-one hours or nineteen, and maybe eighteen hours, fifty-nine minutes thirty-six seconds. No matter. It is a large margin which will stay there and stay positive for the foreseeable future. Now you can truncate it with floor() in safety.
然而,正确的解决方案是,避免神奇常数,四舍五入的拼凑和维护债务,是
use a time library (Datetime, Carbon, whatever); don't roll your own write comprehensive test cases using really evil date choices - across DST boundaries, across leap years, across leap seconds, and so on, as well as commonplace dates. Ideally (calls to datetime are fast!) generate four whole years' (and one day) worth of dates by assembling them from strings, sequentially, and ensure that the difference between the first day and the day being tested increases steadily by one. This will ensure that if anything changes in the low-level routines and leap seconds fixes try to wreak havoc, at least you will know. run those tests regularly together with the rest of the test suite. They're a matter of milliseconds, and may save you literally hours of head scratching.
无论你的解决方案是什么,都要进行测试!
下面的函数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”代码来节省几微秒,这个计算最多需要30秒。这是一个非常明显的优化,以至于他甚至没有记录它,优化通过了集成测试,不知怎么地潜伏在代码中几个月,都没有被发现。
This happened in a program that calculates the wages for several top-selling salesmen, the least of which has a frightful lot more clout than a whole humble five-people programmer team taken together. On March 28th, 2015, the summer time zone engaged, the bug struck -- and some of those guys got shortchanged one whole day of fat commissions. To make things worse, most of them did not work on Sundays and, being near the end of the month, used that day to catch up with their invoicing. They were definitely not amused.
更糟糕的是,他们失去了(已经非常少的)信心,他们认为这个程序不是为了偷偷地欺骗他们而设计的,并且假装——并且获得了——一个完整的、详细的代码审查,用外行的术语运行和注释测试用例(加上接下来几周的许多红地毯式的接待)。
What can I say: on the plus side, we got rid of a lot of technical debt, and were able to rewrite and refactor several pieces of a spaghetti mess that hearkened back to a COBOL infestation in the swinging '90s. The program undoubtedly runs better now, and there's a lot more debugging information to quickly zero in when anything looks fishy. I estimate that just this last one thing will save perhaps one or two man-days per month for the foreseeable future, so the disaster will have a silver, or even golden, lining.
不利的一面是,整个风波让该公司损失了约20万欧元——加上面子,毫无疑问还加上了一些议价能力(因此,还有更多的钱)。
负责“优化”的人在2014年12月换了工作,远早于灾难发生之前,但仍有传言要起诉他索赔。高层认为这是“最后一个人的错”,这让他们不太满意——这看起来像是我们为澄清此事而设的圈套,最终,在那一年剩下的时间里,我们一直不受欢迎,那年夏天结束时,团队中有一人辞职了。
一百次中有九十九次,“86400黑客”会完美地工作。(例如,在PHP中,strtotime()将忽略夏令时,并报告在10月最后一个星期六的午夜到下一个星期一的午夜之间,正好2 * 24 * 60 * 60秒已经过去了,即使这显然不是真的……负负得正)。
女士们,先生们,这只是一个例子。就像使用安全气囊和安全带一样,你可能永远不会真正需要DateTime或Carbon的复杂性(和易用性)。但有一天,你可能会(或你必须证明你想过这件事的那一天)会像小偷一样在夜里出现(可能是在10月的某个周日凌晨2点)。做好准备。
其他回答
TL;DR不使用UNIX时间戳。不要利用时间()。如果你这样做了,要做好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()或进行隐式整数转换的解决方案将比预期少报告一天。
那么,“Y-m-d H:i:s是多少天前的?”会得到错误的答案。
第二个错误是把一天等同于86400秒。这几乎总是正确的——它经常发生,以至于人们忽略了它没有发生的时候。但当夏时制开始实施时,两个连续午夜之间的秒距肯定不会达到每年至少两次。跨DST边界比较两个日期将产生错误的答案。
因此,即使您使用强制所有日期时间戳到固定时间的“hack”,比如午夜(当您只指定日-月-年而不指定小时-分-秒时,这也会被各种语言和框架隐式完成;在数据库(如MySQL)中的DATE类型也是如此,这是广泛使用的公式
FLOOR((unix_timestamp(DATE2) - unix_timestamp(DATE1)) / 86400)
or
floor((time() - strtotime($somedate)) / 86400)
will return, say, 17 when DATE1 and DATE2 are in the same DST segment of the year; but even if the hour:minute:second part is identical, the argument might be 17.042, and worse still, 16.958 when they are in different DST segments and the time zone is DST-aware. The use of floor() or any implicit truncation to integer will then convert what should have been a 17 to a 16. In other circumstances, expressions like "$days > 17" will return true for 17.042, even if this will look as if the elapsed day count is 18.
And things grow even uglier since such code is not portable across platforms, because some of them may apply leap seconds and some might not. On those platforms that do, the difference between two dates will not be 86400 but 86401, or maybe 86399. So code that worked in May and actually passed all tests will break next June when 12.99999 days are considered 12 days instead of 13. Two dates that worked in 2015 will not work in 2017 -- the same dates, and neither year is a leap year. And between 2018-03-01 and 2017-03-01, on those platforms that care, 366 days will have passed instead of 365, making 2018 a leap year (which it is not).
因此,如果您真的想使用UNIX时间戳:
use round() function wisely, not floor(). as an alternative, do not calculate differences between D1-M1-YYY1 and D2-M2-YYY2. Those dates will be really considered as D1-M1-YYY1 00:00:00 and D2-M2-YYY2 00:00:00. Rather, convert between D1-M1-YYY1 22:30:00 and D2-M2-YYY2 04:30:00. You will always get a remainder of about twenty hours. This may become twenty-one hours or nineteen, and maybe eighteen hours, fifty-nine minutes thirty-six seconds. No matter. It is a large margin which will stay there and stay positive for the foreseeable future. Now you can truncate it with floor() in safety.
然而,正确的解决方案是,避免神奇常数,四舍五入的拼凑和维护债务,是
use a time library (Datetime, Carbon, whatever); don't roll your own write comprehensive test cases using really evil date choices - across DST boundaries, across leap years, across leap seconds, and so on, as well as commonplace dates. Ideally (calls to datetime are fast!) generate four whole years' (and one day) worth of dates by assembling them from strings, sequentially, and ensure that the difference between the first day and the day being tested increases steadily by one. This will ensure that if anything changes in the low-level routines and leap seconds fixes try to wreak havoc, at least you will know. run those tests regularly together with the rest of the test suite. They're a matter of milliseconds, and may save you literally hours of head scratching.
无论你的解决方案是什么,都要进行测试!
下面的函数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”代码来节省几微秒,这个计算最多需要30秒。这是一个非常明显的优化,以至于他甚至没有记录它,优化通过了集成测试,不知怎么地潜伏在代码中几个月,都没有被发现。
This happened in a program that calculates the wages for several top-selling salesmen, the least of which has a frightful lot more clout than a whole humble five-people programmer team taken together. On March 28th, 2015, the summer time zone engaged, the bug struck -- and some of those guys got shortchanged one whole day of fat commissions. To make things worse, most of them did not work on Sundays and, being near the end of the month, used that day to catch up with their invoicing. They were definitely not amused.
更糟糕的是,他们失去了(已经非常少的)信心,他们认为这个程序不是为了偷偷地欺骗他们而设计的,并且假装——并且获得了——一个完整的、详细的代码审查,用外行的术语运行和注释测试用例(加上接下来几周的许多红地毯式的接待)。
What can I say: on the plus side, we got rid of a lot of technical debt, and were able to rewrite and refactor several pieces of a spaghetti mess that hearkened back to a COBOL infestation in the swinging '90s. The program undoubtedly runs better now, and there's a lot more debugging information to quickly zero in when anything looks fishy. I estimate that just this last one thing will save perhaps one or two man-days per month for the foreseeable future, so the disaster will have a silver, or even golden, lining.
不利的一面是,整个风波让该公司损失了约20万欧元——加上面子,毫无疑问还加上了一些议价能力(因此,还有更多的钱)。
负责“优化”的人在2014年12月换了工作,远早于灾难发生之前,但仍有传言要起诉他索赔。高层认为这是“最后一个人的错”,这让他们不太满意——这看起来像是我们为澄清此事而设的圈套,最终,在那一年剩下的时间里,我们一直不受欢迎,那年夏天结束时,团队中有一人辞职了。
一百次中有九十九次,“86400黑客”会完美地工作。(例如,在PHP中,strtotime()将忽略夏令时,并报告在10月最后一个星期六的午夜到下一个星期一的午夜之间,正好2 * 24 * 60 * 60秒已经过去了,即使这显然不是真的……负负得正)。
女士们,先生们,这只是一个例子。就像使用安全气囊和安全带一样,你可能永远不会真正需要DateTime或Carbon的复杂性(和易用性)。但有一天,你可能会(或你必须证明你想过这件事的那一天)会像小偷一样在夜里出现(可能是在10月的某个周日凌晨2点)。做好准备。
尝试使用碳
$d1 = \Carbon\Carbon::now()->subDays(92);
$d2 = \Carbon\Carbon::now()->subDays(10);
$days_btw = $d1->diffInDays($d2);
你也可以用
\Carbon\Carbon::parse('')
使用给定的时间戳字符串创建一个Carbon date对象。
使用这个:)
$days = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24);
print $days;
现在起作用了
这段代码为我工作,并用PHP 8版本测试:
function numberOfDays($startDate, $endDate)
{
//1) converting dates to timestamps
$startSeconds = strtotime($startDate);
$endSeconds = strtotime($endDate);
//2) Calculating the difference in timestamps
$diffSeconds = $startSeconds - $endSeconds;
//3) converting timestamps to days
$days=round($diffSeconds / 86400);
/* note :
1 day = 24 hours
24 * 60 * 60 = 86400 seconds
*/
//4) printing the number of days
printf("Difference between two dates: ". abs($days) . " Days ");
return abs($days);
}
function howManyDays($startDate,$endDate) {
$date1 = strtotime($startDate." 0:00:00");
$date2 = strtotime($endDate." 23:59:59");
$res = (int)(($date2-$date1)/86400);
return $res;
}
推荐文章
- PHP中接口的意义是什么?
- 致命错误:未找到类“SoapClient”
- 我目前使用的是哪个版本的CodeIgniter ?
- 合并两个PHP对象的最佳方法是什么?
- 如何使HTTP请求在PHP和不等待响应
- 新DateTime()与默认值(DateTime)
- 发送附件与PHP邮件()?
- 如何获得当前的路线在Symfony 2?
- 用PHP删除字符串的前4个字符
- mysql_connect():[2002]没有这样的文件或目录(试图通过unix:///tmp/mysql.sock连接)在
- 一个函数的多个返回值
- 如何保存时区解析日期/时间字符串与strptime()?
- 在PHP中使用foreach循环时查找数组的最后一个元素
- 检查数组是否为空
- PHP DOMDocument loadHTML没有正确编码UTF-8