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


107

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

例如:

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

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


1
我最初的反应是,使用strftime()并将两个值都转换为时间戳,然后将差值除以3600,但这总是可行吗?该死,夏时制!
Pekka 2010年

@Pekka:不,我想它永远不会起作用...看看我的回答。在那里,我发布了一个考虑时区,leap年,leap秒和dst的解决方案:)
Fidi 2010年

@Pekka,如果使用strtotime()它,只要使用默认时区或显式指定时区偏移,它将始终有效。没有理由诅咒夏令时。
Walter Tross 2014年

Answers:


205

较新的PHP版本提供了一些新类叫DateTimeDateIntervalDateTimeZoneDatePeriod。此类的优点是,它考虑了不同的时区,leap年,leap秒,夏季等。此外,它非常易于使用。在此对象的帮助下,这是您想要的:

// 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规则不同,计算可能会随时区而变化,因此始终定义时区而不依赖服务器设置可能是个好主意。
Pekka 2010年

是的 使用此对象,您甚至可以计算不同时区中的日期之间。$date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi 2010年

3
如果有人遇到了与我刚遇到的$diff->d等于0的问题(因为我试图计算两个日期之间的时间,恰好相隔2个月):跑步var_dump($diff)给我显示了另一个参数:["days"]=>int(61),所以我最终使用$hours = $diff->days * 24;,它来了出接近给定的二三十天的月,所以这是寻找更好的比0的结果为1440个小时的“平均”(我猜的PHP版本是有点老了...)
semmelbroesel

2
我的意思是,在世界许多地方,一年中有一天23小时和一天25小时。
Walter Tross 2014年

4
@Amal Murali,所以您决定为这个答案奖励加分,这是错误的吗?您是否尝试过使用此答案在任何具有DST(夏令时)时区的1月1日中午至6月1日中午之间的小时数?您会得到一个偶数的结果,而真实的结果是奇数的。
Walter Tross 2014年

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

4
为什么不$diff / 3600呢?
Alex G

4
@AlexG这只是一种风格。输出相同,但是程序员通常在时间上使用乘法
用户

我建议你喜欢:round(($ t1-$ 22)/ 3600); 使用回合获取正确的时间
Shiv Singh

20

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

计数小时数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

请注意,DatePeriodDST排除一个小时,但DST结束时不增加一个小时。因此,其用法取决于您所需的结果和日期范围。

查看当前的错误报告

//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持续时间
TheKarateKid

另一个注意事项是DateInterval不接受ISO 8601规范中的分数。因此P1.2Y在PHP中不是有效的持续时间。
fyrye

注意:iterator_count将仅返回肯定结果。如果第一个日期大于第二个日期,则比较结果将为
0。– SubjectDelta

1
该问题与@SubjectDelta无关iterator_count,这是由于DatePeriod无法从开始日期到结束日期的将来生成日期。请参阅:3v4l.org/Ypsp1 以使用负数日期,您需要指定一个负数间隔,DateInterval::createFromDateString('-1 hour');其起始日期为结束日期的过去。
fyrye

1
@SubjectDelta这是的另一个细微差别DatePeriod,因为默认情况下,它将包括指定时间段之间的开始日期,除非它们小于或等于开始日期。实际上,您是在告诉php在两个日期之间的1秒钟内创建1个小时的时间段。您需要使用来删除日期对象中的分钟和秒,因为它们与计算无关DateTime::setTime(date->format('H'), 0)3v4l.org/K7uss这样,如果您超出范围1秒钟,则不会创建另一个日期。
fyrye


14

即使在夏令时更改之间,也要获得两个日期(日期时间)之间正确的小时数,最简单的方法是使用Unix时间戳中的差异。Unix时间戳是从1970-01-01T00:00:00 UTC开始经过的秒数,忽略了,秒(这是可以的,因为您可能不需要这种精度,并且考虑到leap秒非常困难)。

将带有可选时区信息的datetime字符串转换为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()
阿瑟

1
@Arth,我不知道什么时候是这种情况,但是在我的PHP 5.5.9中,它不再是真的。getTimestamp()现在返回与完全相同的值format("U")。但是,前者是一个整数,而后者是一个字符串(此处效率较低)。
Walter Tross

太酷了,也许在更早的版本中是对的。是的,整数将更干净,所以我希望getTimestamp()可以确定。
Arth 2015年

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

3
$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()函数接受此日期格式。


3
<?
     $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位!


0

此功能可以帮助您计算两个给定日期之间的精确年,月,$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*/
    }
?>

建议的编辑太小,但<php需要更改为<?php或批准建议的编辑,以完全删除该错误。
2013年

0

这正在我的项目中工作。我认为,这将对您有所帮助。

如果日期在过去,则取反1。
如果日期在将来,则取反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;

0

要传递unix时间戳,请使用此符号

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

1
注意时区将在DateTime构造函数中+0:00使用时传递并输出@。使用该DateTime::modify()方法时,会将时间戳记作为+0:00并输出当前时区。另一种使用方法是$date = new DateTime(); $date->setTimestamp($unix_timestamp);3v4l.org/BoAWI
fyrye

0

也可能是一个不错的选择。

从他们的网站:

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


0

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

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

FromISO8601具有相同的语义:这是创建的datetime对象from iso8601-formatted string,因此是名称。

有间隔时,可以按自己的喜好设置格式。如果您需要几个小时的工作时间,则可以

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

如果您想要最高的总小时数,请按以下步骤进行:

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

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


0

除了@fyrye的非常有用的答案外,这是上述bug的不错解决方法( bug ),DatePeriod在进入夏季时减去一小时,而离开夏季时不减去一小时(因此,欧洲/柏林的三月有其正确的743小时,但十月为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

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


0
$diff_min = ( strtotime( $day2 ) - strtotime( $day1 ) ) / 60 / 60;
$total_time  = $diff_min;

你可以试试这个。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.