Oblicz liczbę godzin między 2 datami w PHP


107

Jak obliczyć różnicę między dwiema datami w godzinach?

Na przykład:

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

W tym przypadku wynik powinien wynosić 47 godzin.


1
Moja początkowa odpowiedź brzmiałaby: zamień obie wartości na znaczniki czasu za pomocą strftime()i podziel różnicę przez 3600, ale czy to zawsze zadziała? Cholera, czas letni!
Pekka

@Pekka: nie, to chyba nie zawsze zadziała ... Spójrz na moją odpowiedź. Tam zamieściłem rozwiązanie uwzględniające strefy czasowe, lata przestępne, sekundy przestępne i czas
letni

@Pekka, jeśli strtotime()jej użyjesz , BĘDZIE zawsze działać, o ile używasz domyślnej strefy czasowej LUB wyraźnie określisz przesunięcie strefy czasowej. Nie ma powodu, by przeklinać czas letni.
Walter Tross,

Odpowiedzi:


205

Nowsze wersje PHP podać kilka nowych klas zwane DateTime, DateInterval, DateTimeZonei DatePeriod. Fajną rzeczą w tych zajęciach jest to, że uwzględniają różne strefy czasowe, lata przestępne, sekundy przestępne, czas letni itp. A ponadto są bardzo łatwe w użyciu. Oto, czego chcesz przy pomocy tych obiektów:

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

Zwracany obiekt DateInterval udostępnia również inne metody niż format. Jeśli chcesz uzyskać wynik tylko w godzinach, możesz coś takiego:

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

A oto linki do dokumentacji:

Wszystkie te zajęcia oferują również proceduralny / funkcjonalny sposób operowania datami. Dlatego spójrz na przegląd: http://php.net/manual/book.datetime.php


+1 Dobra robota! Wygląda to solidnie i jest dobrym przeglądem. Należy pamiętać, że obliczenia mogą się różnić w zależności od strefy czasowej ze względu na różne reguły czasu letniego, więc prawdopodobnie dobrym pomysłem jest zawsze definiowanie strefy i nie poleganie na ustawieniach serwera.
Pekka

Tak. Dzięki temu obiektowi możesz nawet obliczyć daty w różnych strefach czasowych. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');i$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi

3
Jeśli ktoś napotka ten sam problem, co właśnie zrobiłem, gdzie $diff->djest równe 0 (ponieważ próbuję obliczyć godziny między dwiema datami, które są dokładnie 2-miesięczne): Uruchomienie var_dump($diff)pokazało mi inny parametr:, ["days"]=>int(61)więc skończyło się na używaniu $hours = $diff->days * 24;i nadeszło zbliżone do "średniej" 1440 godzin z 2 30
dniowymi

2
Chodzi mi o to, że w wielu częściach świata rok ma jeden 23-godzinny dzień i jeden 25-godzinny dzień.
Walter Tross

4
@Amal Murali, więc zdecydowałeś się przyznać premię za tę odpowiedź, która jest ZŁA? Czy próbowałeś obliczyć za pomocą tej odpowiedzi liczbę godzin między południem pierwszego stycznia a południem pierwszego czerwca w dowolnej strefie czasowej, w której obowiązuje czas letni (DST)? Otrzymasz parzysty wynik, podczas gdy prawdziwy wynik jest dziwny.
Walter Tross

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
Dlaczego nie $diff / 3600?
Alex G

4
@AlexG To tylko kwestia stylu. To samo wyjście, ale programiści zwykle używają mnożenia, jeśli chodzi o czasy
użytkownik

Proponuję polubić: round (($ t1 - 22 $) / 3600); użyj rundy, aby uzyskać prawidłowe godziny
Shiv Singh

20

Aby zapewnić inną metodę DatePeriodkorzystania ze strefy czasowej UTC lub GMT .

Liczenie godzin 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)';

Wynik

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

Liczenie godzin z uwzględnieniem czasu letniego https://3v4l.org/QBQUB

Należy pamiętać, że DatePeriodwyklucza to godzinę czasu letniego, ale nie dodaje kolejnej godziny po zakończeniu czasu letniego. Dlatego jego użycie jest subiektywne w stosunku do pożądanego wyniku i zakresu dat.

Zobacz aktualny raport o błędzie

//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';

Wynik

#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
Dla każdego tak zdezorientowanego, jak widząc parametr konstruktora DateInterval, format to ISO 8601 Duration
TheKarateKid

Inną uwagą jest to, że DateIntervalnie akceptuje wartości ułamkowych, jak w specyfikacji ISO 8601. Więc P1.2Ynie jest to poprawny czas trwania w PHP.
fyrye

UWAGA: iterator_count zwróci tylko pozytywne wyniki. Jeśli pierwsza data jest większa niż druga, wynik różnicy będzie
wynosił

1
@SubjectDelta problem nie jest związany iterator_count, jest to spowodowane brakiem DatePeriodmożliwości wygenerowania dat, gdy data początkowa przypada w przyszłości od daty zakończenia. Zobacz: 3v4l.org/Ypsp1 aby użyć daty ujemnej, musisz określić przedział ujemny, DateInterval::createFromDateString('-1 hour');z datą początkową przypadającą w przeszłości lub od daty zakończenia.
fyrye

1
@SubjectDelta to kolejny niuans DatePeriod, ponieważ domyślnie będzie zawierał datę rozpoczęcia między określonymi okresami, chyba że są one mniejsze lub równe dacie rozpoczęcia. W efekcie mówisz php, aby utworzył okres 1 godziny między dwiema datami, w ciągu 1 sekundy. Musisz usunąć minuty i sekundy z obiektów daty, ponieważ nie są one istotne w obliczeniach za pomocą DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss W ten sposób, jeśli przekroczysz zakres o 1 sekundę, inna data nie zostanie utworzona.
fyrye

17

Twoja odpowiedź brzmi:

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


5
A co jeśli między 2 godzinami a 30 minutami? Twoja odpowiedź potrwa 3 godziny. Myślę, że lepiej byłoby użyć podłogi, aby dać 2 godziny. Naprawdę zależy to od sytuacji.
Kapitein Witbaard,

14

Najłatwiejszym sposobem uzyskania prawidłowej liczby godzin między dwiema datami (datami), nawet w przypadku zmian czasu letniego, jest użycie różnicy w sygnaturach czasowych systemu Unix. Uniksowe znaczniki czasu to sekundy, które upłynęły od 1970-01-01T00: 00: 00 UTC, ignorując sekundy przestępne (jest to w porządku, ponieważ prawdopodobnie nie potrzebujesz tej precyzji i ponieważ dość trudno jest wziąć pod uwagę sekundy przestępne).

Najbardziej elastycznym sposobem konwersji ciągu z datą i godziną z opcjonalnymi informacjami o strefie czasowej na znacznik czasu systemu Unix jest skonstruowanie obiektu DateTime (opcjonalnie z DateTimeZone jako drugim argumentem w konstruktorze), a następnie wywołanie jego metody 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
Z komentarza w instrukcji wynika, że ​​ze względu na zgodność z datami sprzed epoki, format("U")jest lepsze niżgetTimestamp()
Arth

1
@Arth, nie wiem, kiedy to miało miejsce, ale w moim PHP 5.5.9 to już nie jest prawda. getTimestamp()teraz zwraca dokładnie taką samą wartość jak format("U"). Pierwsza jest liczbą całkowitą, podczas gdy druga jest łańcuchem (tutaj jest mniej wydajna).
Walter Tross

Fajnie, może to prawda we wcześniejszej wersji .. Tak, liczba całkowita byłaby czystsza, więc wolałbym, getTimestamp()gdybym był pewien.
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);

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

Domyślam się, że funkcja strtotime () akceptuje ten format daty.


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;

?>

To jest również kopia odpowiedzi z 2010 roku.
Daniel W.

2

Niestety rozwiązanie dostarczone przez FaileN nie działa zgodnie z twierdzeniami Waltera Trossa .. dni mogą nie wynosić 24 godziny!

Lubię używać obiektów PHP tam, gdzie jest to możliwe, i dla nieco większej elastyczności wymyśliłem następującą funkcję:

/**
 * @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;
}

To tak jak date_diff()tworzy DateTimeInterval, ale z najwyższą jednostką jako godziny, a nie lata .. można ją sformatować jak zwykle.

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

NB użyłem format('U')zamiast ze getTimestamp()względu na komentarz w instrukcji . Należy również pamiętać, że 64-bitowy jest wymagany w przypadku dat po epoce i przed epoką ujemną!


0

Ta funkcja pomaga obliczyć dokładne lata i miesiące między dwiema podanymi datami $doj1i $doj. Zwraca przykład 4.3 oznacza 4 lata i 3 miesiące.

<?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*/
    }
?>

Sugerowana edycja jest zbyt drobna, ale <phpnależy ją zmienić na <?phpLub zatwierdzić sugerowaną zmianę, co całkowicie usuwa błąd.
anishsane

0

To działa w moim projekcie. Myślę, że to ci pomoże.

Jeśli data jest w przeszłości, odwrócenie będzie równe 1.
Jeśli data jest w przyszłości, odwrócenie będzie równe 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

Aby przekazać uniksowy znacznik czasu, użyj tej notacji

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

1
Uwaga Strefa czasowa zostanie przekazana i wyprowadzona tak, jak w +0:00przypadku użycia @w konstruktorze DateTime. Podczas korzystania z DateTime::modify()metody zostanie przekazany znacznik czasu jako +0:00i wyprowadzi bieżącą strefę czasową. Alternatywnie użyj $date = new DateTime(); $date->setTimestamp($unix_timestamp);patrz: 3v4l.org/BoAWI
fyrye

0

Węgiel też mógłby być dobrym rozwiązaniem.

Z ich strony internetowej:

Proste rozszerzenie API PHP dla DateTime. http://carbon.nesbot.com/

Przykład:

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 rozszerza klasę DateTime, aby dziedziczyć metody, w tym diff(). Dodaje ładne cukry, takie jak diffInHours, diffInMintutes, diffInSecondsetc


0

Najpierw należy utworzyć obiekt interwału z zakresu dat. Już samo sformułowanie użyte w tym zdaniu pozwala łatwo zidentyfikować podstawowe potrzebne abstrakcje. Istnieje interwał jako koncepcja i kilka innych sposobów jej realizacji, w tym ten już wspomniany - z różnych dat. Tak więc interwał wygląda następująco:

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

FromISO8601ma tę samą semantykę: jest to utworzony obiekt typu data-godzina from iso8601-formatted string, stąd nazwa.

Gdy masz interwał, możesz go sformatować w dowolny sposób. Jeśli potrzebujesz pełnej liczby godzin, możesz to zrobić

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

Jeśli chcesz, aby całkowita liczba godzin była ograniczona, proszę bardzo:

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

Aby uzyskać więcej informacji na temat tego podejścia i kilka przykładów, zapoznaj się z tym wpisem .


0

Oprócz bardzo pomocnej odpowiedzi @ fyrye jest to dobre obejście wspomnianego błędu ( ten ), że DatePeriod odejmuje jedną godzinę przy wejściu w lato, ale nie dodaje jednej godziny przy wyjściu z lata (i dlatego marsz Europy / Berlina ma swój poprawne 743 godziny, ale październik ma 744 zamiast 745):

Liczenie godzin w miesiącu (lub w dowolnym przedziale czasowym), biorąc pod uwagę przejścia na czas letni w obu kierunkach

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 Jeśli zaznaczysz (dłuższy) okres czasu, który prowadzi do więcej niż tych dwóch przejść, moje obejście nie dotknie liczonych godzin, aby zmniejszyć potencjalne śmieszne efekty uboczne. W takich przypadkach trzeba wdrożyć bardziej skomplikowane rozwiązanie. Można iterować po wszystkich znalezionych przejściach i porównać prąd z ostatnim i sprawdzić, czy jest to jedno z DST prawda-> fałsz.


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

Możesz spróbować tego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.