摒弃 `strtotime` + 手工除以 86400/3600 的旧写法。DateTime 原生支持时区、闰年、夏令时、微秒,代码更健壮、可读性强。
<?php
/**
* 计算两个时间点的差值
* @param string|DateTime $start 开始时间
* @param string|DateTime $end 结束时间 (默认现在)
* @param string $timezone 时区 (默认 PRC)
* @return array ['days'=>, 'hours'=>, 'minutes'=>, 'seconds'=>, 'total_seconds'=>, 'human'=>]
*/
function timeDiff($start, $end = 'now', string $timezone = 'PRC'): array
{
$tz = new DateTimeZone($timezone);
$dtStart = $start instanceof DateTime ? $start : new DateTime($start, $tz);
$dtEnd = $end instanceof DateTime ? $end : new DateTime($end, $tz);
if ($dtStart > $dtEnd) {
// 交换并标记负数
[$dtStart, $dtEnd] = [$dtEnd, $dtStart];
$negative = true;
} else {
$negative = false;
}
/** @var DateInterval $diff */
$diff = $dtStart->diff($dtEnd);
$totalSeconds = $dtEnd->getTimestamp() - $dtStart->getTimestamp();
$result = [
'years' => $diff->y,
'months' => $diff->m,
'days' => $diff->d,
'hours' => $diff->h,
'minutes' => $diff->i,
'seconds' => $diff->s,
'total_days' => (int)$diff->format('%a'), // 总天数 (含年月折算)
'total_seconds' => $totalSeconds,
'negative' => $negative,
'human' => $diff->format('%y年 %m月 %d天 %h小时 %i分 %s秒'),
];
return $result;
}
// 1. 字符串时间
$diff = timeDiff('2023-01-01 00:00:00', '2023-12-31 23:59:59');
print_r($diff);
/*
Array
(
[years] => 0
[months] => 11
[days] => 30
[hours] => 23
[minutes] => 59
[seconds] => 59
[total_days] => 364
[total_seconds] => 31535999
[negative] =>
[human] => 0年 11月 30天 23小时 59分 59秒
)
*/
// 2. 仅想要总天数/总小时/总分钟
$start = new DateTime('2023-06-01 10:00:00');
$end = new DateTime('2023-06-03 14:30:00');
$diff = $start->diff($end);
echo "总天数: " . $diff->format('%a') . "\n"; // 2
echo "总小时: " . ($diff->days * 24 + $diff->h) . "\n"; // 52
echo "总分钟: " . (($diff->days * 24 + $diff->h) * 60 + $diff->i) . "\n"; // 3150
// 3. 倒计时场景:距离目标时间还剩多久
$target = new DateTime('2024-01-01 00:00:00');
$now = new DateTime();
$diff = $now->diff($target);
if ($now < $target) {
echo "距离新年: {$diff->format('%a天 %h小时 %i分 %s秒')}";
}
// 不推荐:手工计算易错、不支持时区、无闰秒处理
$start = strtotime('2023-01-01 00:00:00');
$end = strtotime('2023-12-31 23:59:59');
$diff = $end - $start;
$days = intval($diff / 86400);
$hours = intval(($diff % 86400) / 3600);
$minutes = intval(($diff % 3600) / 60);
$seconds = $diff % 60;
| 陷阱 | 原因 | 正确做法 |
|---|---|---|
$diff->days 与 $diff->format('%a') 不同 | days 仅为日期部分差值,不含年月折算 | 总天数用 format('%a') 或 $diff->days + $diff->m*30 + $diff->y*365 (近似) |
| 跨夏令时边界计算错误 | strtotime 使用系统时区,可能跳变 1 小时 | 显式指定 new DateTimeZone('Asia/Shanghai') (无夏令时) |
| 微秒丢失 | DateTime::diff 仅精确到秒 | 用 $dtEnd->getTimestamp() - $dtStart->getTimestamp() 获取总秒数,或用 microtime(true) |
| 负数差值 | 开始时间 > 结束时间 | 先比较大小再 diff,或检查 $diff->invert 属性 |
function secondsBetween(string $start, string $end): int
{
// 直接用时间戳差值,最快、最简单
return strtotime($end) - strtotime($start);
}
// 或 DateTime (可指定时区)
function secondsBetweenDt(DateTime $start, DateTime $end): int
{
return $end->getTimestamp() - $start->getTimestamp();
}
永远使用 DateTime/diff 处理时间差。它封装了复杂的日历逻辑,支持时区、格式化输出、倒计时、年龄计算等所有场景。手工除以 86400 的代码已属技术债,重构时请全部替换。