Calculer le nombre d'heures entre 2 dates en PHP

107

Comment calculer la différence entre deux dates en heures?

Par exemple:

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

Dans ce cas, le résultat devrait être de 47 heures.

Penseur
la source
1
Ma réponse initiale aurait été de transformer les deux valeurs en horodatages en utilisant strftime()et de diviser la différence par 3600, mais cela fonctionnera-t-il toujours? Damn you, heure d'été!
Pekka
@Pekka: non ça ne marchera pas toujours je suppose ... Jetez un œil à ma réponse. Là, j'ai posté une solution en tenant compte des fuseaux horaires, des années bissextiles, des secondes bissextiles et dst :)
Fidi
@Pekka, si vous l'utilisez strtotime()fonctionnera toujours, tant que vous utilisez le fuseau horaire par défaut OU spécifiez explicitement le décalage du fuseau horaire. Aucune raison de maudire le DST.
Walter Tross

Réponses:

205

Les versions PHP plus récentes fournissent de nouvelles classes appelées DateTime, DateInterval, DateTimeZoneet DatePeriod. Ce qui est cool à propos de ces cours, c'est qu'ils prennent en compte différents fuseaux horaires, années bissextiles, secondes intercalaires, heure d'été, etc. Et en plus de cela, il est très facile à utiliser. Voici ce que vous voulez avec l'aide de ces objets:

// 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'objet DateInterval, qui est retourné, fournit également d'autres méthodes que format. Si vous voulez le résultat en heures seulement, vous pouvez faire quelque chose comme ceci:

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

Et voici les liens vers la documentation:

Toutes ces classes offrent également une manière procédurale / fonctionnelle d'opérer avec des dates. Jetez donc un œil à l'aperçu: http://php.net/manual/book.datetime.php

Fidi
la source
+1 Bon travail! Cela semble solide et constitue un bon aperçu. Il est important de noter que les calculs peuvent varier en fonction du fuseau horaire en raison de différentes règles d'heure d'été, c'est donc probablement une bonne idée de toujours définir la zone et de ne pas se fier aux paramètres du serveur.
Pekka
Oui. Avec cet objet, vous pouvez même calculer entre des dates dans différents fuseaux horaires. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');et$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi
3
Si quelqu'un rencontre le même problème que moi, où $diff->dest égal à 0 (parce que j'essaie de calculer les heures entre deux dates qui sont exactement à 2 mois d'intervalle): Running var_dump($diff)m'a montré un autre paramètre:, ["days"]=>int(61)alors j'ai fini par utiliser $hours = $diff->days * 24;, et il est venu proche de la "moyenne" de 1440 heures pour 2 mois de 30 jours, donc cela semble beaucoup mieux qu'un résultat de 0. (Je suppose que ma version PHP est un peu ancienne ...)
semmelbroesel
2
Je veux dire, dans de nombreuses régions du monde, une année compte une journée de 23 heures et une journée de 25 heures.
Walter Tross
4
@Amal Murali, vous avez donc décidé d'attribuer le bonus à cette réponse, qui est FAUX? Avez-vous essayé de calculer avec cette réponse le nombre d'heures entre midi du premier janvier et midi du premier juin, dans n'importe quel fuseau horaire qui a DST (heure d'été)? Vous obtiendrez un résultat pair, tandis que le vrai résultat est étrange.
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 );
Jan Hančič
la source
4
Pourquoi pas $diff / 3600?
Alex G du
4
@AlexG C'est juste une question de style. Même résultat, mais les programmeurs utilisent généralement la multiplication quand il s'agit de temps
Utilisateur
je vous suggère d'aimer: round (($ t1 - $ 22) / 3600); utilisez le tour pour obtenir les heures correctes
Shiv Singh
20

Pour fournir une autre méthode DatePeriodlors de l'utilisation du fuseau horaire UTC ou GMT .

Compter les heures 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)';

Résultat

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

Compter les heures avec l'heure d'été https://3v4l.org/QBQUB

Veuillez noter que cela DatePeriodexclut une heure pour l'heure d'été mais n'ajoute pas une autre heure lorsque l'heure d'été se termine. Son utilisation est donc subjective par rapport au résultat souhaité et à la plage de dates.

Voir le rapport de bogue actuel

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

Résultat

#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
la source
1
Pour toute personne aussi confuse que moi en voyant le paramètre du constructeur DateInterval, le format est une durée ISO 8601
TheKarateKid
Une autre note est que DateIntervaln'accepte pas les valeurs fractionnaires comme dans la spécification ISO 8601. Ce P1.2Yn'est donc pas une durée valide en PHP.
fyrye
REMARQUE: iterator_count renverra uniquement des résultats positifs. Si la première date est plus grande que la seconde, le résultat de la différence sera 0.
SubjectDelta
1
@SubjectDelta le problème n'est pas lié iterator_count, il est dû à l' DatePeriodimpossibilité de générer des dates lorsque la date de début est dans le futur à partir de la date de fin. Voir: 3v4l.org/Ypsp1 pour utiliser une date négative, vous devez spécifier un intervalle négatif, DateInterval::createFromDateString('-1 hour');avec une date de début dans le passé de à partir de la date de fin.
fyrye
1
@SubjectDelta c'est une autre nuance de DatePeriod, car par défaut, il inclura la date de début entre les périodes spécifiées à moins qu'elles ne soient inférieures ou égales à la date de début. En effet, vous dites à php de créer une période d'une heure entre les deux dates, dans un délai d'une seconde. Vous devrez supprimer les minutes et les secondes de vos objets de date, car ils ne sont pas pertinents dans le calcul, en utilisant DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss De cette façon, si vous dépassez la plage de 1 seconde, une autre date n'est pas créée.
fyrye
17

votre réponse est:

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

Sergey Eremin
la source
5
Et s'il y a 2 heures et 30 minutes entre les deux? Votre réponse se traduira par 3 heures. Je pense qu'il serait préférable d'utiliser le sol pour que cela donne 2 heures. Cela dépend vraiment de la situation.
Kapitein Witbaard
14

Le moyen le plus simple d'obtenir le nombre d'heures correct entre deux dates (datetimes), même en cas de changement d'heure d'été, est d'utiliser la différence d'horodatage Unix. Les horodatages Unix sont des secondes écoulées depuis 1970-01-01T00: 00: 00 UTC, ignorant les secondes intercalaires (c'est OK parce que vous n'avez probablement pas besoin de cette précision, et parce qu'il est assez difficile de prendre en compte les secondes intercalaires).

Le moyen le plus flexible de convertir une chaîne datetime avec des informations de fuseau horaire facultatives en un horodatage Unix est de construire un objet DateTime (éventuellement avec un DateTimeZone comme second argument dans le constructeur), puis d'appeler sa méthode 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";
Walter Tross
la source
1
D'après un commentaire dans le manuel, il semble que, pour la compatibilité avec les dates antérieures à l'époque, il format("U")est préférable degetTimestamp()
Arth
1
@Arth, je ne sais pas quand c'était le cas, mais dans mon PHP 5.5.9 ce n'est plus vrai. getTimestamp()renvoie maintenant exactement la même valeur que format("U"). Le premier est un entier, tandis que le second est une chaîne (moins efficace ici).
Walter Tross
Cool, c'était peut-être vrai dans une version antérieure. Oui, un entier serait plus propre, donc je préférerais getTimestamp()que je puisse être sûr.
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);
Hoàng Vũ Tgtt
la source
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);

Je suppose que la fonction strtotime () accepte ce format de date.

Boris Delormas
la source
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;

?>
Marcheur de nuit
la source
Ceci est également une copie du formulaire de réponse 2010.
Daniel W.
2

Malheureusement, la solution fournie par FaileN ne fonctionne pas comme indiqué par Walter Tross. Les jours peuvent ne pas être 24 heures!

J'aime utiliser les objets PHP dans la mesure du possible et pour un peu plus de flexibilité, j'ai proposé la fonction suivante:

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

Cela date_diff()crée un DateTimeInterval, mais avec l'unité la plus élevée en heures plutôt qu'en années .. il peut être formaté comme d'habitude.

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

NB je l'ai utilisé format('U')au lieu de à getTimestamp()cause du commentaire dans le manuel . Notez également que 64 bits est requis pour les dates post-époque et pré-période négative!

Arth
la source
0

Cette fonction vous aide à calculer les années et les mois exacts entre deux dates données, $doj1et $doj. Il renvoie l'exemple 4.3 signifie 4 ans et 3 mois.

<?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*/
    }
?>
geomagas
la source
La modification suggérée est trop mineure, mais <phpdoit être remplacée par <?phpOu approuver la modification suggérée, ce qui supprime le bogue dans son ensemble.
anishsane
0

Cela fonctionne dans mon projet. Je pense que cela vous sera utile.

Si la date est dans le passé, alors inverser sera 1.
Si la date est dans le futur, alors inverser sera 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;
Gurudutt Sharma
la source
0

Pour transmettre un horodatage unix, utilisez cette notation

$now        = time();
$now        = new DateTime("@$now");
Steve Whitby
la source
1
Remarque Le fuseau horaire sera passé et affiché comme +0:00lors de son utilisation @dans le constructeur DateTime. Lors de l'utilisation, la DateTime::modify()méthode transmettra l'horodatage +0:00et affichera le fuseau horaire actuel. Vous pouvez également utiliser $date = new DateTime(); $date->setTimestamp($unix_timestamp);voir: 3v4l.org/BoAWI
fyrye
0

Le carbone pourrait également être une bonne solution.

Depuis leur site Web:

Une simple extension d'API PHP pour DateTime. http://carbon.nesbot.com/

Exemple:

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 étend la classe DateTime pour hériter des méthodes, y compris diff(). Il ajoute des sucres comme beaux diffInHours, diffInMintutes, diffInSecondsetc.

Joshuamabina
la source
0

Tout d'abord, vous devez créer un objet intervalle à partir d'une plage de dates. Par le libellé utilisé dans cette phrase seulement, on peut facilement identifier les abstractions de base nécessaires. Il existe un intervalle en tant que concept, et quelques autres façons de le mettre en œuvre, incluent celui déjà mentionné - à partir d'une plage de dates. Ainsi, un intervalle ressemble à ça:

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

FromISO8601a la même sémantique: c'est un objet datetime créé from iso8601-formatted string, d'où le nom.

Lorsque vous avez un intervalle, vous pouvez le formater comme vous le souhaitez. Si vous avez besoin d'un certain nombre d'heures complètes, vous pouvez avoir

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

Si vous voulez un nombre total d'heures plafonné, allez-y:

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

Pour plus d'informations sur cette approche et quelques exemples, consultez cette entrée .

Vadim Samokhin
la source
0

En plus de la réponse très utile de @ fyrye, il s'agit d'une solution de contournement correcte pour le bogue mentionné ( celui-ci ), que DatePeriod soustrait une heure lors de l'entrée en été, mais n'ajoute pas une heure en quittant l'heure d'été (et donc la marche d'Europe / Berlin a son correct 743 heures mais octobre a 744 heures au lieu de 745 heures):

Compter les heures d'un mois (ou n'importe quel intervalle de temps), en tenant compte des transitions DST dans les deux sens

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 Si vous cochez une période (plus longue), ce qui conduit à plus que ces deux transitions, ma solution de contournement ne touchera pas les heures comptées pour réduire le potentiel d'effets secondaires amusants. Dans de tels cas, une solution plus compliquée doit être mise en œuvre. On pourrait parcourir toutes les transitions trouvées et comparer le courant avec le dernier et vérifier s'il en est un avec DST true-> false.

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

Vous pouvez essayer celui-ci.

Shahabuddin
la source