在PHP中计算两个日期之间的小时数

129

如何计算两个日期之间的小时差?

例如:

day1=2006-04-12 12:30:00
day2=2006-04-14 11:30:00

在这种情况下,结果应该是47小时。


1
我的初始反应是,使用strftime()将两个值都转换为时间戳,并通过3600分割差异,但这样总是有效吗?该死的夏令时! - Pekka
1
@Pekka:我猜不总是有效...看看我的答案。在那里,我发布了一个解决方案,考虑到时区、闰年、闰秒和夏令时 :) - Fidi
@Pekka,如果你使用strtotime()函数,只要使用默认时区或明确指定时区偏移量,它就会始终正常工作。没有理由诅咒夏令时。 - Walter Tross
17个回答

252

较新版本的PHP提供了一些称为DateTimeDateIntervalDateTimeZoneDatePeriod的新类。这些类的好处在于,它们考虑了不同的时区、闰年、闰秒、夏令时等等。而且,使用它们非常容易。下面是如何使用这些对象来实现你想要的功能:

// Create two new DateTime-objects...
$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2006-04-14T11:30:00');

// The diff-methods returns a new DateInterval-object...
$diff = $date2->diff($date1);

// Call the format method on the DateInterval-object
echo $diff->format('%a Day and %h hours');

返回的DateInterval对象不仅提供了format方法,还提供了其他方法。如果您只希望以小时为单位获取结果,可以像下面这样操作:

$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2006-04-14T11:30:00');

$diff = $date2->diff($date1);

$hours = $diff->h;
$hours = $hours + ($diff->days*24);

echo $hours;

以下是文档链接:

这些类还提供了一种面向过程/函数式的操作日期的方式。因此,请参阅概述: http://php.net/manual/book.datetime.php


7
如果有人遇到了和我一样的问题,其中$diff->d等于0(因为我试图计算相隔两个月的日期之间的小时数):运行var_dump($diff)显示了另一个参数:["days"]=>int(61),所以最终我使用了$hours = $diff->days * 24;,结果接近于假设2个30天的月份总计有1440小时的“平均”时间,所以这比得到0的结果要好得多。(猜测我的PHP版本可能有点旧...) - semmelbroesel
3
我理解,世界上很多地方的一年中会有一个23小时的日子和一个25小时的日子。 - Walter Tross
5
@Amal Murali,所以你决定奖励这个错误的答案?你试过用这个答案计算1月1日中午到6月1日中午之间在任何有夏令时的时区内的小时数吗?你会得到一个偶数结果,而真正的结果是奇数。 - Walter Tross
DateInterval::format() 的问题在于当我们想要特定单位的结果时。例如:24小时的差异将显示为 0 小时和 1 天。因此,如果天数不为空,我们需要注意并进行另一次计算。 - GusDeCooL
1
不太清楚为什么DateDiff不能给我们总小时数。感谢您的回答和指导,之前很困惑为什么我的差异少了一天的小时数,现在有所了解了! - Antony
显示剩余2条评论

90
$t1 = strtotime( '2006-04-14 11:30:00' );
$t2 = strtotime( '2006-04-12 12:30:00' );
$diff = $t1 - $t2;
$hours = $diff / ( 60 * 60 );

7
为什么不用 $diff / 3600 - Alex G
10
@AlexG 这只是一种风格问题。输出结果相同,但程序员通常在涉及到乘法运算时使用乘号。 - User
建议您使用以下代码:round(($t1 - $22) / 3600); 使用round函数获取正确的小时数。 - Shiv Singh

20

为使用UTCGMT时区提供另一种 DatePeriod 方法。

计算小时数 https://3v4l.org/Mu3HD

$start = new \DateTime('2006-04-12T12:30:00');
$end = new \DateTime('2006-04-14T11:30:00');

//determine what interval should be used - can change to weeks, months, etc
$interval = new \DateInterval('PT1H');

//create periods every hour between the two dates
$periods = new \DatePeriod($start, $interval, $end);

//count the number of objects within the periods
$hours = iterator_count($periods);
echo $hours . ' hours'; 

//difference between Unix Epoch
$diff = $end->getTimestamp() - $start->getTimestamp();
$hours = $diff / ( 60 * 60 );
echo $hours . ' hours (60 * 60)';

//difference between days
$diff = $end->diff($start);
$hours = $diff->h + ($diff->days * 24);
echo $hours . ' hours (days * 24)';

结果

47 hours (iterator_count)
47 hours (60 * 60)
47 hours (days * 24)

考虑夏令时的小时计数 https://3v4l.org/QBQUB

请注意,DatePeriod 在夏令时排除一小时,但在夏令时结束时不会添加另一个小时。因此,其使用取决于您所需的结果和日期范围。

请参阅当前的错误报告

//set timezone to UTC to disregard daylight savings
date_default_timezone_set('America/New_York');

$interval = new \DateInterval('PT1H');

//DST starts Apr. 2nd 02:00 and moves to 03:00
$start = new \DateTime('2006-04-01T12:00:00');  
$end = new \DateTime('2006-04-02T12:00:00');

$periods = new \DatePeriod($start, $interval, $end);
$hours = iterator_count($periods);
echo $hours . ' hours';

//DST ends Oct. 29th 02:00 and moves to 01:00
$start = new \DateTime('2006-10-28T12:00:00');
$end = new \DateTime('2006-10-29T12:00:00'); 

$periods = new \DatePeriod($start, $interval, $end);
$hours = iterator_count($periods);
echo $hours . ' hours';

结果

#2006-04-01 12:00 EST to 2006-04-02 12:00 EDT
23 hours (iterator_count)
//23 hours (60 * 60)
//24 hours (days * 24)

#2006-10-28 12:00 EDT to 2006-10-29 12:00 EST
24 hours (iterator_count)
//25 hours (60 * 60)
//24 hours (days * 24)

#2006-01-01 12:00 EST to 2007-01-01 12:00 EST
8759 hours (iterator_count)
//8760 hours (60 * 60)
//8760 hours (days * 24)

//------

#2006-04-01 12:00 UTC to 2006-04-02 12:00 UTC
24 hours (iterator_count)
//24 hours (60 * 60)
//24 hours (days * 24)

#2006-10-28 12:00 UTC to 2006-10-29 12:00 UTC
24 hours (iterator_count)
//24 hours (60 * 60)
//24 hours (days * 24)

#2006-01-01 12:00 UTC to 2007-01-01 12:00 UTC
8760 hours (iterator_count)
//8760 hours (60 * 60)
//8760 hours (days * 24)

1
对于像我一样在看到DateInterval构造函数参数时感到困惑的人,其格式是ISO 8601 Duration - TheKarateKid
另外需要注意的是,DateInterval 不接受 ISO 8601 规范中的小数值。因此,在 PHP 中 P1.2Y 不是一个有效的持续时间。 - Will B.
注意:iterator_count仅返回正数结果。如果第一个日期大于第二个日期,则差异结果将为0。 - SubjectDelta
1
@SubjectDelta,问题与iterator_count无关,而是由于DatePeriod无法在开始日期在结束日期之后的未来生成日期。请参见:https://3v4l.org/Ypsp1。要使用负日期,您需要指定一个负间隔,例如`DateInterval::createFromDateString('-1 hour');`,并且开始日期必须在过去或者结束日期之前。 - Will B.
明白了。那么还有一个问题,13:00:0013:00:01之间有多少小时?3v4l.org/oXYng - SubjectDelta
1
@SubjectDelta 这是 DatePeriod 的另一个微妙之处,因为默认情况下它将包括指定时间段之间的开始日期,除非它们小于或等于开始日期。实际上,您告诉 PHP 在两个日期之间创建一个持续1小时的时间段,在1秒钟内。您需要从日期对象中删除分钟和秒,因为它们在计算中不相关,使用 DateTime::setTime(date->format('H'), 0)。https://3v4l.org/K7uss 这样,如果您超出了1秒的范围,就不会创建另一个日期。 - Will B.

18

你的答案是:

round((strtotime($day2) - strtotime($day1))/(60*60))

这行代码计算两个日期之间的小时数。它使用PHP中的strtotime函数将日期转换为时间戳,然后将两个时间戳相减并除以3600(即60 * 60)来计算小时数。最后,round函数将结果四舍五入为最接近的整数。

6
如果两个时间点之间相差2小时30分钟,你的答案会是3小时。我认为最好使用向下取整,这样就会得到2小时。不过这真的取决于具体情况。 - Kapitein Witbaard

17

获取两个日期(datetimes)之间的正确小时数,即使跨越夏令时更改,最简单的方法是使用Unix时间戳之差。Unix时间戳是自1970-01-01T00:00:00 UTC以来经过的秒数,忽略闰秒(这没问题,因为您可能不需要这种精度,并且因为考虑闰秒相当困难)。

将带有可选时区信息的日期时间字符串转换为Unix时间戳的最灵活方法是构造一个DateTime对象(可选地在构造函数中作为第二个参数使用DateTimeZone),然后调用其getTimestamp方法。

$str1 = '2006-04-12 12:30:00'; 
$str2 = '2006-04-14 11:30:00';
$tz1 = new DateTimeZone('Pacific/Apia');
$tz2 = $tz1;
$d1 = new DateTime($str1, $tz1); // tz is optional,
$d2 = new DateTime($str2, $tz2); // and ignored if str contains tz offset
$delta_h = ($d2->getTimestamp() - $d1->getTimestamp()) / 3600;
if ($rounded_result) {
   $delta_h = round ($delta_h);
} else if ($truncated_result) {
   $delta_h = intval($delta_h);
}
echo "Δh: $delta_h\n";

1
手册的注释中可以看出,为了兼容纪元前的日期,使用format("U")getTimestamp()更可取。 - Arth
1
@Arth,我不知道这是什么情况,但在我的PHP 5.5.9版本中已经不再适用了。getTimestamp()现在返回与format(“U”)完全相同的值。前者是一个整数,而后者是一个字符串(效率稍低)。 - Walter Tross
很酷,也许在早期版本中是真的。是的,整数会更干净,所以如果我能确定的话,我更喜欢使用getTimestamp() - Arth

4
//Calculate number of hours between pass and now
$dayinpass = "2013-06-23 05:09:12";
$today = time();
$dayinpass= strtotime($dayinpass);
echo round(abs($today-$dayinpass)/60/60);

4
<?
     $day1 = "2014-01-26 11:30:00";
     $day1 = strtotime($day1);
     $day2 = "2014-01-26 12:30:00";
     $day2 = strtotime($day2);

   $diffHours = round(($day2 - $day1) / 3600);

   echo $diffHours;

?>

这也是2010年答案的副本。 - Daniel W.

2

很不幸,FaileN提供的解决方案并不能像Walter Tross所说的那样工作...因为一天可能不是24小时!

我喜欢尽可能使用PHP对象,为了更灵活性,我想出了以下函数:

/**
 * @param DateTimeInterface $a
 * @param DateTimeInterface $b
 * @param bool              $absolute Should the interval be forced to be positive?
 * @param string            $cap The greatest time unit to allow
 *
 * @return DateInterval The difference as a time only interval
 */
function time_diff(DateTimeInterface $a, DateTimeInterface $b, $absolute=false, $cap='H'){

  // Get unix timestamps, note getTimeStamp() is limited
  $b_raw = intval($b->format("U"));
  $a_raw = intval($a->format("U"));

  // Initial Interval properties
  $h = 0;
  $m = 0;
  $invert = 0;

  // Is interval negative?
  if(!$absolute && $b_raw<$a_raw){
    $invert = 1;
  }

  // Working diff, reduced as larger time units are calculated
  $working = abs($b_raw-$a_raw);

  // If capped at hours, calc and remove hours, cap at minutes
  if($cap == 'H') {
    $h = intval($working/3600);
    $working -= $h * 3600;
    $cap = 'M';
  }

  // If capped at minutes, calc and remove minutes
  if($cap == 'M') {
    $m = intval($working/60);
    $working -= $m * 60;
  }

  // Seconds remain
  $s = $working;

  // Build interval and invert if necessary
  $interval = new DateInterval('PT'.$h.'H'.$m.'M'.$s.'S');
  $interval->invert=$invert;

  return $interval;
}

date_diff() 这样的函数会创建一个 DateTimeInterval 对象,但最大的时间单位是小时,而不是年。可以像平常一样进行格式化。

$interval = time_diff($date_a, $date_b);
echo $interval->format('%r%H'); // For hours (with sign)

注意:我使用了format('U')而不是getTimestamp(),因为在手册中有相关的注释。另外请注意,64位系统才支持后纪元和负纪元日期!


2

Carbon 也是一个不错的选择。

来自他们的网站:

一个简单的 PHP API 扩展,用于 DateTime。http://carbon.nesbot.com/

示例:

use Carbon\Carbon;

//...

$day1 = Carbon::createFromFormat('Y-m-d H:i:s', '2006-04-12 12:30:00');
$day2 = Carbon::createFromFormat('Y-m-d H:i:s', '2006-04-14 11:30:00');

echo $day1->diffInHours($day2); // 47

//...

Carbon扩展了DateTime类以继承方法,包括diff()。它添加了很好的语法糖,如diffInHoursdiffInMintutesdiffInSeconds等等。

2
$day1 = "2006-04-12 12:30:00"
$day1 = strtotime($day1);
$day2 = "2006-04-14 11:30:00"
$day2 = strtotime($day2);

$diffHours = round(($day2 - $day1) / 3600);

我猜strtotime()函数接受这种日期格式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接