【问题标题】:Calculate number of hours between 2 dates in PHP计算PHP中2个日期之间的小时数
【发布时间】:2010-06-24 09:17:22
【问题描述】:

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

例如:

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

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

【问题讨论】:

  • 我最初的反应是,使用strftime() 将两个值转换为时间戳,然后将差值除以 3600,但这总是有效吗?该死的,夏令时!
  • @Pekka:不,我猜它并不总是有效......看看我的答案。在那里我发布了一个解决方案,考虑时区、闰年、闰秒和 dst :)
  • @Pekka,如果您使用strtotime(),只要您使用默认时区或明确指定时区偏移量,它将始终有效。没有理由诅咒 DST。

标签: php datetime


【解决方案1】:

较新的 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

【讨论】:

  • +1 干得好!这看起来很可靠,是一个很好的概述。请务必注意,由于 DST 规则不同,计算可能会因时区而异,因此最好始终定义时区而不依赖服务器设置。
  • 是的。使用此对象,您甚至可以在不同时区的日期之间进行计算。 $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
  • 如果有人遇到与我刚才在 $diff->d 等于 0 的情况下相同的问题(因为我正在尝试计算两个恰好相隔 2 个月的日期之间的小时数):运行 var_dump($diff) 显示我的另一个参数:["days"]=>int(61),所以我最终使用了$hours = $diff->days * 24;,它在 2 个 30 天的月份中接近 1440 小时的“平均值”,所以这看起来比 0 的结果要好得多。(猜测我的PHP版本有点老了……)
  • 我的意思是,在世界许多地方,一年有一天 23 小时和一天 25 小时。
  • @Amal Murali,所以你决定奖励这个答案,这是错误的吗?您是否尝试使用此答案计算在具有 DST(夏令时)的任何时区中,从 1 月 1 日中午到 6 月 1 日中午之间的小时数?你会得到一个偶数的结果,而真正的结果是奇数。
【解决方案2】:
$t1 = strtotime( '2006-04-14 11:30:00' );
$t2 = strtotime( '2006-04-12 12:30:00' );
$diff = $t1 - $t2;
$hours = $diff / ( 60 * 60 );

【讨论】:

  • 为什么不$diff / 3600
  • @AlexG 这只是一种风格。相同的输出,但程序员通常在涉及时间时使用乘法
  • 我建议你喜欢:round(($t1 - $22) / 3600);使用 round 获得正确的时间
【解决方案3】:

在使用 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 不包括 DST 的一小时,但不会在 DST 结束时再增加一小时。因此,它的使用取决于您想要的结果和日期范围。

查看当前bug report

//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)

【讨论】:

  • 对于任何像我一样在看到 DateInterval 构造函数参数时感到困惑的人,格式是 ISO 8601 Duration
  • 另一个注意事项是DateInterval 不接受 ISO 8601 规范中的小数值。所以P1.2Y 在 PHP 中不是有效的持续时间。
  • 注意:iterator_count 将只返回正面结果。如果第一个日期大于第二个日期,则差异结果将为 0。
  • @SubjectDelta 该问题与iterator_count 无关,这是由于DatePeriod 无法从结束日期开始生成日期。请参阅:3v4l.org/Ypsp1 使用负日期,您需要指定负间隔,DateInterval::createFromDateString('-1 hour'); 开始日期在过去的结束日期。
  • @SubjectDelta 这是DatePeriod 的另一个细微差别,因为默认情况下它将包括指定期间之间的开始日期,除非它们小于或等于开始日期。实际上,您是在告诉 php 在两个日期之间创建一个 1 小时的时间段,在 1 秒内。您需要使用DateTime::setTime(date->format('H'), 0) 从日期对象中删除分钟和秒,因为它们与计算无关。 3v4l.org/K7uss 这样,如果您超出范围 1 秒,则不会创建另一个日期。
【解决方案4】:

你的答案是:

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

【讨论】:

  • 如果中间有2小时30分钟呢?您的答案将在 3 小时内完成。我认为最好使用地板,这样会产生 2 小时。不过真的要视情况而定。
【解决方案5】:

获得两个日期(日期时间)之间正确小时数的最简单方法是使用 Unix 时间戳的差异,即使在夏令时更改时也是如此。 Unix 时间戳是自 1970-01-01T00:00:00 UTC 以来经过的秒数,忽略闰秒(这没关系,因为您可能不需要这种精度,而且很难考虑闰秒)。

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

$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";

【讨论】:

  • manual 中的评论看来,为了与纪元前的日期兼容,format("U")getTimestamp() 更可取
  • @Arth,我不知道这是什么时候发生的,但在我的 PHP 5.5.9 中不再是这样了。 getTimestamp() 现在返回与format("U") 完全相同的值。不过,前者是一个整数,而后者是一个字符串(这里效率较低)。
  • 酷,也许在早期版本中确实如此。是的,整数会更清晰,所以如果可以确定的话,我更喜欢getTimestamp()
【解决方案6】:
//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);

【讨论】:

    【解决方案7】:
    $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() 函数接受这种日期格式。

    【讨论】:

      【解决方案8】:
      <?
           $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年答题表的副本。
      【解决方案9】:

      不幸的是,FailN 提供的解决方案不能像 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)
      

      注意由于manual 中的评论,我使用了format('U') 而不是getTimestamp()。另请注意,后纪元和前负纪元日期需要 64 位!

      【讨论】:

        【解决方案10】:

        Carbon 也是不错的选择。

        来自他们的网站:

        DateTime 的简单 PHP API 扩展。 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

        【讨论】:

          【解决方案11】:

          此功能可帮助您计算两个给定日期$doj1$doj 之间的确切年份和月份。它返回示例 4.3 表示 4 年零 3 个月。

          <?php
              function cal_exp($doj1)
              {
                  $doj1=strtotime($doj1);
                  $doj=date("m/d/Y",$doj1); //till date or any given date
          
                  $now=date("m/d/Y");
                  //$b=strtotime($b1);
                  //echo $c=$b1-$a2;
                  //echo date("Y-m-d H:i:s",$c);
                  $year=date("Y");
                  //$chk_leap=is_leapyear($year);
          
                  //$year_diff=365.25;
          
                  $x=explode("/",$doj);
                  $y1=explode("/",$now);
          
                  $yy=$x[2];
                  $mm=$x[0];
                  $dd=$x[1];
          
                  $yy1=$y1[2];
                  $mm1=$y1[0];
                  $dd1=$y1[1];
                  $mn=0;
                  $mn1=0;
                  $ye=0;
                  if($mm1>$mm)
                  {
                      $mn=$mm1-$mm;
                      if($dd1<$dd)
                      {
                          $mn=$mn-1;
                      }
                      $ye=$yy1-$yy;
                  }
                  else if($mm1<$mm)
                  {
                      $mn=12-$mm;
                      //$mn=$mn;
          
                      if($mm!=1)
                      {
                          $mn1=$mm1-1;
                      }
          
                      $mn+=$mn1;
                      if($dd1>$dd)
                      {
                          $mn+=1;
                      }
          
                      $yy=$yy+1;
                      $ye=$yy1-$yy;
                  }
                  else
                  {
                      $ye=$yy1-$yy;
                      $ye=$ye-1;
          
                      $mn=12-1;
          
                      if($dd1>$dd)
                      {
                          $ye+=1;
                          $mn=0;
                      }
                  }
          
                  $to=$ye." year and ".$mn." months";
                  return $ye.".".$mn;
          
                  /*return daysDiff($x[2],$x[0],$x[1]);
                   $days=dateDiff("/",$now,$doj)/$year_diff;
                  $days_exp=explode(".",$days);
                  return $years_exp=$days; //number of years exp*/
              }
          ?>
          

          【讨论】:

          • 建议的修改太小了,但是&lt;php需要改成&lt;?php或者批准建议的修改,这样一并消除了bug。
          【解决方案12】:

          要传递 unix 时间戳,请使用此表示法

          $now        = time();
          $now        = new DateTime("@$now");
          

          【讨论】:

          • 注意 在 DateTime 构造函数中使用@ 时,时区将被传递并输出为+0:00。使用DateTime::modify() 方法时,会将时间戳作为+0:00 传递并输出当前时区。或者使用$date = new DateTime(); $date-&gt;setTimestamp($unix_timestamp); 参见:3v4l.org/BoAWI
          【解决方案13】:

          首先,您应该根据一系列日期创建一个区间对象。仅通过这句话中使用的措辞,就可以轻松识别所需的基本抽象。有一个间隔作为一个概念,还有更多实现它的方法,包括已经提到的一种——从一系列日期开始。因此,间隔看起来像这样:

          $interval =
              new FromRange(
                  new FromISO8601('2017-02-14T14:27:39+00:00'),
                  new FromISO8601('2017-03-14T14:27:39+00:00')
              );
          

          FromISO8601 具有相同的语义:它是一个创建 from iso8601-formatted string 的日期时间对象,因此得名。

          当你有一个间隔时,你可以随意格式化它。如果你需要几个完整的小时,你可以有

          (new TotalFullHours($interval))->value();
          

          如果您想要总小时数上限,请点击此处:

          (new TotalCeiledHours($interval))->value();
          

          有关此方法的更多信息和一些示例,请查看this entry

          【讨论】:

            【解决方案14】:

            除了@fyrye's very helpful answer 之外,对于提到的错误 (this one),这是一个不错的解决方法,即 DatePeriod 在进入夏季时减去一小时,但在离开夏季时不增加一小时(因此欧洲/柏林的三月有正确的 743 小时,但 10 月有 744 小时而不是 745 小时):

            计算一个月(或任何时间跨度)的小时数,考虑双向的 DST 转换

            function getMonthHours(string $year, string $month, \DateTimeZone $timezone): int
            {
                // or whatever start and end \DateTimeInterface objects you like
                $start = new \DateTimeImmutable($year . '-' . $month . '-01 00:00:00', $timezone);
                $end = new \DateTimeImmutable((new \DateTimeImmutable($year . '-' . $month . '-01 23:59:59', $timezone))->format('Y-m-t H:i:s'), $timezone);
                
                // count the hours just utilizing \DatePeriod, \DateInterval and iterator_count, hell yeah!
                $hours = iterator_count(new \DatePeriod($start, new \DateInterval('PT1H'), $end));
                
                // find transitions and check, if there is one that leads to a positive offset
                // that isn't added by \DatePeriod
                // this is the workaround for https://bugs.php.net/bug.php?id=75685
                $transitions = $timezone->getTransitions((int)$start->format('U'), (int)$end->format('U'));
                if (2 === count($transitions) && $transitions[0]['offset'] - $transitions[1]['offset'] > 0) {
                    $hours += (round(($transitions[0]['offset'] - $transitions[1]['offset'])/3600));
                }
                
                return $hours;
            }
            
            $myTimezoneWithDST = new \DateTimeZone('Europe/Berlin');
            var_dump(getMonthHours('2020', '01', $myTimezoneWithDST)); // 744
            var_dump(getMonthHours('2020', '03', $myTimezoneWithDST)); // 743
            var_dump(getMonthHours('2020', '10', $myTimezoneWithDST)); // 745, finally!
            
            $myTimezoneWithoutDST = new \DateTimeZone('UTC');
            var_dump(getMonthHours('2020', '01', $myTimezoneWithoutDST)); // 744
            var_dump(getMonthHours('2020', '03', $myTimezoneWithoutDST)); // 744
            var_dump(getMonthHours('2020', '10', $myTimezoneWithoutDST)); // 744
            

            附:如果您检查(更长的)时间跨度,这导致超过这两个转换,我的解决方法不会触及计算的小时数,以减少有趣的副作用的可能性。在这种情况下,必须实施更复杂的解决方案。可以遍历所有找到的转换并将当前转换与最后一个转换并检查它是否是 DST 为 true->false 的转换。

            【讨论】:

              【解决方案15】:
              $diff_min = ( strtotime( $day2 ) - strtotime( $day1 ) ) / 60 / 60;
              $total_time  = $diff_min;
              

              你可以试试这个。

              【讨论】:

                【解决方案16】:

                这在我的项目中有效。我想,这对你会有帮助。

                如果日期是过去的,则反转将 1.
                如果 Date 在未来,则 invert 将为 0。

                $defaultDate = date('Y-m-d');   
                $datetime1   = new DateTime('2013-03-10');  
                $datetime2   = new DateTime($defaultDate);  
                $interval    = $datetime1->diff($datetime2);  
                $days        = $interval->format('%a');
                $invert      = $interval->invert;
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-07-08
                  • 1970-01-01
                  • 2012-07-18
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-09-19
                  相关资源
                  最近更新 更多