Обчисліть кількість годин між двома датами в PHP


107

Як обчислити різницю між двома датами в годинах?

Наприклад:

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

У цьому випадку результат повинен становити 47 годин.


1
Моєю початковою відповіддю було б перетворити обидва значення на часові позначки, використовуючи strftime()та розділити різницю на 3600, але чи завжди це буде працювати? Чорт забирай, літній час!
Пекка

@ Пекка: ні, це не завжди спрацює, я думаю ... Погляньте на мою відповідь. Там я опублікував рішення, враховуючи часові пояси, високосні роки, високосні секунди та інше:
Fidi,

@Pekka, якщо ви користуєтесь strtotime()ним, БУДЕ завжди працювати, якщо ви використовуєте часовий пояс за замовчуванням АБО явно вкажіть зміщення часового поясу. Немає приводу проклинати ДСТ.
Вальтер Трос

Відповіді:


205

Новіші версії 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 Гарна робота! Це виглядає суцільно і є прекрасним оглядом. Важливо зауважити, що обчислення можуть відрізнятися залежно від часового поясу через різні правила DST, тому, ймовірно, гарна ідея завжди визначати зону, а не покладатися на налаштування сервера.
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 (тому що я намагаюсь обчислити години між двома датами, які мають рівно два місяці): Біг var_dump($diff)показав мені ще один параметр:, ["days"]=>int(61)тому я в кінцевому підсумку використовував $hours = $diff->days * 24;, і він з’явився ближче до "середнього" 1440 годин, що дається 2 30-денних місяців, тому це виглядає набагато краще, ніж результат 0. (Зрозумівши, що моя версія PHP трохи стара ...)
semmelbroesel

2
Я маю на увазі, що у багатьох частинах світу рік має один 23-годинний день та один 25-годинний день.
Вальтер Трос

4
@Amal Murali, тож ти вирішив присвоїти бонус за цю відповідь, а це НЕБЕЗПЕЧНО? Чи намагалися ви обчислити за допомогою цієї відповіді кількість годин між полуднем першого січня і полуднем першого червня в будь-якому часовому поясі, де встановлено літній час (літній час)? Ви отримаєте рівний результат, а справжній результат - непарний.
Вальтер Трос

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?
Алекс Г

4
@AlexG Це просто річ у стилі. Вихід той самий, але програмісти зазвичай використовують множення, коли мова йде про рази
Користувач

Я пропоную вам сподобатися: круглий (($ 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майте на увазі, що виключається година для DST, але не додається ще одна година, коли закінчується 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 поверне лише позитивні результати. Якщо перша дата більша за другу, то
різний

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

17

ваша відповідь:

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


5
Що робити, якщо між ними є 2 години 30 хвилин? Ваша відповідь приведе через 3 години. Я думаю, що було б краще використовувати підлогу, щоб це дало 2 години. Дійсно залежить від ситуації.
Kapitein Witbaard

14

Найпростіший спосіб отримати правильну кількість годин між двома датами (датами), навіть через зміни літнього часу, це використовувати різницю в часових позначках 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"). Хоча перший є цілим числом, тоді як останній є рядком (тут менш ефективний).
Вальтер Трос

Класно, можливо, це було правдою в попередній версії. Так, ціле число було б чистішим, тому я вважаю за краще, 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)

Примітка: Я використовував 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

Вуглець також може бути хорошим шляхом.

З їх веб-сайту:

Просте розширення API PHP для 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має таку саму семантику: це створений об’єкт дати from iso8601-formatted string, звідси і назва.

Якщо у вас є інтервал, ви можете відформатувати його, як завгодно. Якщо вам потрібна кількість повних годин, ви можете мати

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

Якщо ви хочете, щоб вичерпали загальну кількість годин, ось вам:

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

Докладніше про цей підхід та деякі приклади дивіться у цій статті .


0

На додаток до дуже корисної відповіді @ fyrye, це нормальне вирішення згаданої помилки ( цього ), що 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;

Ви можете спробувати це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.