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.