Calcola il numero di ore tra 2 date in PHP


107

Come si calcola la differenza tra due date in ore?

Per esempio:

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

In questo caso il risultato dovrebbe essere di 47 ore.


1
La mia risposta iniziale sarebbe stata, trasformare entrambi i valori in timestamp utilizzando strftime()e dividere la differenza per 3600, ma funzionerà sempre? Accidenti a te, ora legale!
Pekka

@Pekka: no, immagino che non funzionerà sempre ... Dai un'occhiata alla mia risposta. Lì ho pubblicato una soluzione considerando, fusi orari, anni bisestili, secondi intercalari e dst :)
Fidi

@Pekka, se lo usi strtotime(), funzionerà sempre, purché utilizzi il fuso orario predefinito OPPURE specifichi esplicitamente l'offset del fuso orario. Nessun motivo per maledire l'ora legale.
Walter Tross

Risposte:


205

I più recenti PHP-versioni forniscono alcune nuove classi denominate DateTime, DateInterval, DateTimeZonee DatePeriod. La cosa interessante di questo corso è che considera diversi fusi orari, anni bisestili, secondi intercalari, ora estiva, ecc. E inoltre è molto facile da usare. Ecco cosa vuoi con l'aiuto di questi oggetti:

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

L'oggetto DateInterval, che viene restituito, fornisce anche altri metodi oltre a format. Se vuoi il risultato solo in ore, potresti fare qualcosa del genere:

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

Ed ecco i link per la documentazione:

Tutte queste classi offrono anche un modo procedurale / funzionale per operare con le date. Quindi dai un'occhiata alla panoramica: http://php.net/manual/book.datetime.php


+1 Ottimo lavoro! Questo sembra solido ed è un'ottima panoramica. È importante notare che i calcoli possono variare in base al fuso orario a causa delle diverse regole dell'ora legale, quindi è probabilmente una buona idea definire sempre la zona e non fare affidamento sulle impostazioni del server.
Pekka

Sì. Con questo oggetto potresti persino calcolare le date in fusi orari diversi. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');e$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi

3
Se qualcuno si imbatte nello stesso problema che ho appena fatto dove è $diff->duguale a 0 (perché sto cercando di calcolare le ore tra due date che sono esattamente a 2 mesi di distanza): Running var_dump($diff)mi ha mostrato un altro parametro:, ["days"]=>int(61)quindi ho finito per usare $hours = $diff->days * 24;, ed è arrivato vicino alla "media" di 1440 ore date 2 mesi da 30 giorni, quindi sembra molto meglio di un risultato di 0. (Immagino che la mia versione PHP sia un po 'vecchia ...)
semmelbroesel

2
Voglio dire, in molte parti del mondo un anno ha un giorno di 23 ore e un giorno di 25 ore.
Walter Tross

4
@Amal Murali, quindi hai deciso di assegnare il bonus a questa risposta, che è SBAGLIATO? Hai provato a calcolare con questa risposta il numero di ore tra mezzogiorno del primo gennaio e mezzogiorno del primo giugno, in qualsiasi fuso orario che abbia DST (ora legale)? Otterrai un risultato pari, mentre il risultato vero è dispari.
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
Perché no $diff / 3600?
Alex G

4
@AlexG È solo una cosa di stile. Stesso output, ma i programmatori di solito usano la moltiplicazione quando si tratta di tempi
Utente

ti consiglio di apprezzare: round (($ t1 - $ 22) / 3600); usa il giro per ottenere gli orari corretti
Shiv Singh

20

Per fornire un altro metodo per DatePeriodquando si utilizza il fuso orario UTC o GMT .

Conta ore 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)';

Risultato

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

Conta le ore con l'ora legale https://3v4l.org/QBQUB

Si prega di notare che DatePeriodesclude un'ora per l'ora legale ma non aggiunge un'altra ora al termine dell'ora legale. Quindi il suo utilizzo è soggettivo al risultato e all'intervallo di date desiderati.

Vedi l'attuale segnalazione di bug

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

Risultato

#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
Per chiunque sia confuso come me nel vedere il parametro del costruttore DateInterval, il formato è una Durata ISO 8601
TheKarateKid

Un'altra nota è che DateIntervalnon accetta valori frazionari come nella specifica ISO 8601. Quindi P1.2Ynon è una durata valida in PHP.
fyrye

NOTA: iterator_count restituirà solo risultati positivi. Se la prima data è maggiore della seconda, il risultato della differenza sarà 0.
OggettoDelta

1
@SubjectDelta il problema non è correlato iterator_count, è dovuto al fatto che DatePeriodnon è possibile generare date quando la data di inizio è nel futuro dalla data di fine. Vedere: 3v4l.org/Ypsp1 per utilizzare una data negativa, è necessario specificare un intervallo negativo, DateInterval::createFromDateString('-1 hour');con una data di inizio nel passato dalla data di fine.
fyrye

1
@SubjectDelta questa è un'altra sfumatura di DatePeriod, poiché per impostazione predefinita includerà la data di inizio tra i periodi specificati a meno che non siano inferiori o uguali alla data di inizio. In effetti stai dicendo a php di creare un periodo di 1 ora tra le due date, entro 1 secondo di tempo. Dovresti rimuovere i minuti e i secondi dagli oggetti data, poiché non sono rilevanti nel calcolo, utilizzando DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss In questo modo, se si supera l'intervallo di 1 secondo, non viene creata un'altra data.
fyrye

17

la tua risposta è:

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


5
E se ci sono 2 ore e 30 minuti tra? La tua risposta risulterà in 3 ore. Penso che sarebbe meglio usare il pavimento in modo da ottenere 2 ore. Dipende davvero dalla situazione.
Kapitein Witbaard

14

Il modo più semplice per ottenere il numero corretto di ore tra due date (datetimes), anche durante i cambi di ora legale, è usare la differenza nei timestamp Unix. I timestamp Unix sono i secondi trascorsi dal 1970-01-01T00: 00: 00 UTC, ignorando i secondi intercalari (questo va bene perché probabilmente non hai bisogno di questa precisione, e perché è abbastanza difficile prendere in considerazione i secondi intercalari).

Il modo più flessibile per convertire una stringa datetime con informazioni opzionali sul fuso orario in un timestamp Unix consiste nel costruire un oggetto DateTime (facoltativamente con un DateTimeZone come secondo argomento nel costruttore) e quindi chiamare il relativo metodo 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
Da un commento nel manuale risulta che, per compatibilità con date antecedenti, format("U")è preferibilegetTimestamp()
Arth

1
@Arth, non so quando è stato così, ma nel mio PHP 5.5.9 non è più vero. getTimestamp()ora restituisce esattamente lo stesso valore di format("U"). Il primo è un numero intero, mentre il secondo è una stringa (meno efficiente qui).
Walter Tross

Fantastico, forse era vero in una versione precedente .. Sì, un intero sarebbe più pulito, quindi preferirei getTimestamp()se potessi esserne sicuro.
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);

Immagino che la funzione strtotime () accetti questo formato di data.


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;

?>

Questa è anche una copia del modulo di risposta 2010.
Daniel W.

2

Purtroppo la soluzione fornita da FaileN non funziona come dichiarato da Walter Tross .. i giorni potrebbero non essere 24 ore!

Mi piace usare gli oggetti PHP dove possibile e per un po 'più di flessibilità ho ideato la seguente funzione:

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

In questo modo viene date_diff()creato un DateTimeInterval, ma con l'unità più alta come ore anziché anni .. può essere formattato come al solito.

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

NB L' ho usato al format('U')posto di getTimestamp()per via del commento nel manuale . Si noti inoltre che 64 bit è richiesto per le date post-epoch e pre-negative-epoch!


0

Questa funzione ti aiuta a calcolare anni e mesi esatti tra due date date $doj1e $doj. Restituisce l'esempio 4.3 significa 4 anni e 3 mesi.

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

La modifica suggerita è troppo minore, ma <phpdeve essere modificata in <?phpOppure approva la modifica suggerita, che rimuove il bug insieme.
anishsane

0

Questo sta funzionando nel mio progetto. Penso, questo ti sarà utile.

Se la data è nel passato, invert sarà 1.
Se la data è nel futuro, invert sarà 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

Per passare un timestamp unix usa questa notazione

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

1
Nota Il fuso orario verrà passato e restituito come +0:00quando si utilizza @nel costruttore DateTime. Durante l'utilizzo il DateTime::modify()metodo passerà il timestamp come +0:00e produrrà il fuso orario corrente. In alternativa, $date = new DateTime(); $date->setTimestamp($unix_timestamp);vedere: 3v4l.org/BoAWI
fyrye

0

Anche il carbonio potrebbe essere un bel modo di procedere.

Dal loro sito web:

Una semplice estensione API PHP per DateTime. http://carbon.nesbot.com/

Esempio:

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 estende la classe DateTime per ereditare metodi inclusi diff(). Si aggiunge piacevoli zuccheri come diffInHours, diffInMintutes, diffInSecondsecc


0

Innanzitutto, dovresti creare un oggetto intervallo da un intervallo di date. Solo dalla formulazione usata in questa frase, si possono facilmente identificare le astrazioni di base necessarie. C'è un intervallo come concetto e un paio di altri modi per implementarlo, includono quello già menzionato - da un intervallo di date. Quindi, un intervallo ha questo aspetto:

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

FromISO8601ha la stessa semantica: è un oggetto datetime creato from iso8601-formatted string, da cui il nome.

Quando hai un intervallo, puoi formattarlo come preferisci. Se hai bisogno di un numero di ore intere, puoi farlo

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

Se vuoi un totale di ore massime, ecco qui:

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

Per ulteriori informazioni su questo approccio e alcuni esempi, controlla questa voce .


0

Oltre alla risposta molto utile di @ fyrye, questa è una soluzione alternativa per il bug menzionato ( questo ), che DatePeriod sottrae un'ora quando entra in estate, ma non aggiunge un'ora quando lascia l'estate (e quindi Europa / Berlino ha il suo 743 ore corrette ma ottobre ne ha 744 invece di 745):

Contando le ore di un mese (o qualsiasi periodo di tempo), considerando le transizioni dell'ora legale in entrambe le direzioni

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 Se controlli un periodo di tempo (più lungo), che porta a più di quelle due transizioni, la mia soluzione alternativa non toccherà le ore contate per ridurre il potenziale di effetti collaterali divertenti. In questi casi, è necessario implementare una soluzione più complicata. Si potrebbe iterare su tutte le transizioni trovate e confrontare la corrente con l'ultima e controllare se è una con DST true-> false.


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

Puoi provare questo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.