Вычислить количество часов между двумя датами в PHP

107

Как рассчитать разницу между двумя датами в часах?

Например:

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

В этом случае результат должен составить 47 часов.

Мыслитель
источник
1
Моим первоначальным ответом было бы преобразовать оба значения во временные метки strftime()и разделить разницу на 3600, но всегда ли это будет работать? Черт тебя побери, летнее время!
Pekka
@Pekka: нет, я думаю, это не всегда сработает ... Посмотри на мой ответ. Там я опубликовал решение, учитывающее часовые пояса, високосные годы, високосные секунды и летнее время :)
Fidi
@Pekka, если вы strtotime()его используете, БУДЕТ работать всегда, если вы используете часовой пояс по умолчанию ИЛИ явно указываете смещение часового пояса. Нет причин ругать DST.
Уолтер Тросс

Ответы:

206

Более новые версии PHP-предоставляют некоторые новые классы , называемые DateTime, DateInterval, DateTimeZoneи DatePeriod. Классная особенность этих классов заключается в том, что они учитывают разные часовые пояса, високосные годы, високосные секунды, летнее время и т. Д. И, кроме того, им очень легко пользоваться. Вот что вы хотите с помощью этих объектов:

// 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 Отличная работа! Это выглядит солидно и представляет собой прекрасный обзор. Важно отметить, что расчеты могут варьироваться в зависимости от часового пояса из-за различных правил летнего времени, поэтому, вероятно, лучше всегда определять зону и не полагаться на настройки сервера.
Pekka
Ага. С помощью этого объекта вы даже можете вычислять даты в разных часовых поясах. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');и$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Фиди
3
Если кто-то столкнется с той же проблемой, что и я, где $diff->dравно 0 (потому что я пытаюсь подсчитать часы между двумя датами, которые различаются ровно на 2 месяца): бег var_dump($diff)показал мне другой параметр:, ["days"]=>int(61)поэтому я в конечном итоге использовал $hours = $diff->days * 24;, и он пришел близко к "среднему" значению 1440 часов при 2 30-дневных месяцах, так что это выглядит намного лучше, чем результат 0. (Предполагаю, что моя версия PHP немного
устарела
2
Я имею в виду, что во многих частях света в году один 23-часовой день и один 25-часовой день.
Уолтер Тросс
4
@Amal Murali, значит, вы решили наградить этот ответ бонусом, который НЕПРАВИЛЬНО? Пытались ли вы с помощью этого ответа рассчитать количество часов между полуднем первого января и полуднем первого июня в любом часовом поясе с летним временем (DST)? Вы получите четный результат, а истинный результат будет нечетным.
Уолтер Тросс
79
$t1 = strtotime( '2006-04-14 11:30:00' );
$t2 = strtotime( '2006-04-12 12:30:00' );
$diff = $t1 - $t2;
$hours = $diff / ( 60 * 60 );
Ян Ганчич
источник
5
А почему бы и нет $diff / 3600?
Alex G
4
@AlexG Это просто стиль. Тот же результат, но программисты обычно используют умножение, когда дело доходит до времени
Пользователь
Предлагаю вам понравиться: round ((t1 $ - 22 $) / 3600); используйте раунд, чтобы получить правильные часы
Шив Сингх
20

Чтобы предоставить другой метод DatePeriodиспользования часового пояса UTC или GMT .

Считать часы 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)
Fyrye
источник
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
18

ваш ответ:

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

Сергей Еремин
источник
5
Что, если между ними 2 часа 30 минут? На ваш ответ уйдет 3 часа. Думаю, лучше использовать пол, чтобы выходило 2 часа. На самом деле все зависит от ситуации.
Kapitein Witbaard
14

Самый простой способ получить правильное количество часов между двумя датами (datetime), даже при переходе на летнее время, - использовать разницу во временных метках Unix. Временные метки Unix - это секунды, прошедшие с 1970-01-01T00: 00: 00 UTC, без учета дополнительных секунд (это нормально, потому что вам, вероятно, не нужна эта точность, и потому что довольно сложно учитывать дополнительные секунды).

Самый гибкий способ преобразовать строку 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()
Arth
1
@Arth, я не знаю, когда это было так, но в моем PHP 5.5.9 это уже не так. getTimestamp()теперь возвращает то же значение, что и format("U"). Однако первое - целое число, а второе - строка (здесь менее эффективно).
Уолтер Тросс
Круто, возможно, это было правдой в более ранней версии ... Да, целое число было бы чище, поэтому я бы предпочел, getTimestamp()чтобы я был уверен.
Арт
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.
Дэниел В.
2

К сожалению, решение, предоставленное FaileN, не работает, как указано Уолтером Троссом ... дни могут не быть 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)

NB Я использовал 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Или утвердить предлагаемое изменение, которое устраняет ошибку полностью.
anishsane
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
Примечание . Часовой пояс будет передан и выведен как +0:00при использовании @в конструкторе DateTime. При использовании DateTime::modify()метода будет передана метка времени как +0:00и будет выведен текущий часовой пояс. В качестве альтернативы используйте $date = new DateTime(); $date->setTimestamp($unix_timestamp);см .: 3v4l.org/BoAWI
fyrye
0

Углерод также может быть хорошим вариантом.

Со своего сайта:

Простое расширение 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(). Это добавляет хороший сахар , как diffInHours, diffInMintutes, и diffInSecondsт.д.

Джошуамабина
источник
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 в это okayish обходной путь для Названные ошибки ( это один ), что DatePeriod вычитает один час при переходе в летнее время, но не добавляет один час при выходе из летнего времени (и, таким образом, в марте в Европе / Берлине есть правильно 743 часа, но в октябре 744 вместо 745 часов):

Подсчет часов месяца (или любого промежутка времени) с учетом перехода на летнее время в обоих направлениях

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.

spackmat
источник
0
$diff_min = ( strtotime( $day2 ) - strtotime( $day1 ) ) / 60 / 60;
$total_time  = $diff_min;

Вы можете попробовать это.

Шахабуддин
источник