Calcule o número de horas entre 2 datas em PHP

107

Como calculo a diferença entre duas datas em horas?

Por exemplo:

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

Nesse caso, o resultado deve ser 47 horas.

Pensador
fonte
1
Minha resposta inicial seria transformar ambos os valores em carimbos de data / hora usando strftime()e dividir a diferença por 3600, mas isso sempre funcionará? Maldito seja, horário de verão!
Pekka
@Pekka: não, nem sempre vai funcionar, eu acho ... Dê uma olhada na minha resposta. Publiquei uma solução considerando fusos horários, anos bissextos, segundos bissextos e dst :)
Fidi
@Pekka, se você usá- strtotime()lo, sempre funcionará, desde que você use o fuso horário padrão OU especifique explicitamente a diferença de fuso horário. Não há razão para amaldiçoar o DST.
Walter Tross

Respostas:

205

As mais recentes do PHP-Versões fornecer algumas novas classes chamados DateTime, DateInterval, DateTimeZonee DatePeriod. O legal dessas aulas é que elas levam em consideração diferentes fusos horários, anos bissextos, segundos bissextos, horário de verão, etc. E, além disso, é muito fácil de usar. Aqui está o que você deseja com a ajuda destes objetos:

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

O objeto DateInterval, que é retornado, também fornece outros métodos além de format. Se você quiser o resultado em apenas horas, poderá fazer algo assim:

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

E aqui estão os links para documentação:

Todas essas classes também oferecem uma maneira procedimental / funcional de operar com datas. Portanto, dê uma olhada na visão geral: http://php.net/manual/book.datetime.php

Fidi
fonte
+1 Bom trabalho! Isso parece sólido e é uma boa visão geral. É importante observar que os cálculos podem variar de acordo com o fuso horário devido a diferentes regras de horário de verão, então provavelmente é uma boa ideia sempre definir a zona e não depender das configurações do servidor.
Pekka
Sim. Com este objeto, você pode até mesmo calcular datas em diferentes fusos horários. $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 alguém tiver o mesmo problema que eu acabei de encontrar, onde $diff->dé igual a 0 (porque estou tentando calcular as horas entre duas datas que têm exatamente 2 meses de diferença): A execução var_dump($diff)me mostrou outro parâmetro:, ["days"]=>int(61)então acabei usando $hours = $diff->days * 24;, e veio perto da "média" de 1440 horas dadas 2 meses de 30 dias, então está parecendo muito melhor do que um resultado de 0. (Supondo que minha versão do PHP é um pouco velha ...)
semmelbroesel
2
Quer dizer, em muitas partes do mundo, um ano tem um dia de 23 horas e um dia de 25 horas.
Walter Tross
4
@Amal Murali, então você decidiu conceder o bônus a esta resposta, o que é ERRADO? Você já tentou calcular com esta resposta o número de horas entre meio-dia de primeiro de janeiro e meio-dia de primeiro de junho, em algum fuso horário que tenha DST (horário de verão)? Você obterá um resultado par, enquanto o resultado verdadeiro é ímpar.
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č
fonte
4
Porque não $diff / 3600?
Alex G
4
@AlexG É apenas uma questão de estilo. Mesma saída, mas os programadores geralmente usam multiplicação quando se trata de tempos
Usuário
sugiro que você goste de: round (($ t1 - $ 22) / 3600); use a rodada para obter os horários corretos
Shiv Singh
20

Para fornecer outro método para DatePeriodquando usar o fuso horário UTC ou GMT .

Contar horas 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)';

Resultado

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

Contar horas com horário de verão https://3v4l.org/QBQUB

Informamos que DatePeriodexclui uma hora para o DST, mas não adiciona outra hora quando o DST termina. Portanto, seu uso é subjetivo para o resultado e intervalo de datas desejados.

Veja o relatório de bug atual

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

Resultado

#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
fonte
1
Para qualquer um tão confuso quanto eu ao ver o parâmetro do construtor DateInterval, o formato é uma Duração ISO 8601
TheKarateKid
Outra observação é que DateIntervalnão aceita valores fracionários como na especificação ISO 8601. Portanto, P1.2Ynão é uma duração válida em PHP.
fyrye
NOTA: iterator_count retornará apenas resultados positivos. Se a primeira data for maior que a segunda, o resultado da diferença será 0.
SubjectDelta
1
@SubjectDelta o problema não está relacionado iterator_count, é devido a DatePeriodnão ser capaz de gerar datas quando a data de início está no futuro a partir da data de término. Veja: 3v4l.org/Ypsp1 para usar uma data negativa, você precisa especificar um intervalo negativo, DateInterval::createFromDateString('-1 hour');com uma data de início no passado a partir da data de término.
fyrye de
1
@SubjectDelta é outra nuance de DatePeriod, pois por padrão inclui a data de início entre os períodos especificados, a menos que sejam menores ou iguais à data de início. Na verdade, você está dizendo ao php para criar um período de 1 hora entre as duas datas, dentro de 1 segundo. Você precisaria remover os minutos e segundos de seus objetos de data, pois eles não são relevantes no cálculo, usando DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss Desta forma, se você ultrapassar o intervalo de 1 segundo, outra data não será criada.
fyrye
17

sua resposta é:

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

Sergey Eremin
fonte
5
E se houver 2 horas e 30 minutos entre? Sua resposta resultará em 3 horas. Acho que seria melhor usar chão para que desse 2 horas. Porém, realmente depende da situação.
Kapitein Witbaard de
14

A maneira mais fácil de obter o número correto de horas entre duas datas (data e hora), mesmo em mudanças de horário de verão, é usar a diferença em carimbos de data / hora Unix. Os carimbos de data / hora Unix são os segundos decorridos desde 1970-01-01T00: 00: 00 UTC, ignorando os segundos bissextos (isso está OK porque você provavelmente não precisa dessa precisão e porque é muito difícil levar os segundos bissextos em consideração).

A maneira mais flexível de converter uma string de data e hora com informações opcionais de fuso horário em um carimbo de data / hora Unix é construir um objeto DateTime (opcionalmente com DateTimeZone como um segundo argumento no construtor) e, em seguida, chamar seu método 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
fonte
1
A partir de um comentário no manual , parece que, para compatibilidade com datas pré-época, format("U")é preferível agetTimestamp()
Arth
1
@Arth, não sei quando foi esse o caso, mas no meu PHP 5.5.9 não é mais verdade. getTimestamp()agora retorna exatamente o mesmo valor que format("U"). O primeiro é um número inteiro, enquanto o último é uma string (menos eficiente aqui).
Walter Tross,
Legal, talvez fosse verdade em uma versão anterior. Sim, um número inteiro seria mais limpo, então eu preferia getTimestamp()que eu pudesse ter certeza.
Dia
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
fonte
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);

Eu acho que a função strtotime () aceita este formato de data.

Boris Delormas
fonte
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;

?>
Night Walker
fonte
Esta é também uma cópia do formulário de resposta de 2010.
Daniel W.
2

Infelizmente a solução fornecida pela FaileN não funciona conforme afirmado por Walter Tross .. dias podem não ser 24 horas!

Gosto de usar os objetos PHP sempre que possível e, para um pouco mais de flexibilidade, criei a seguinte função:

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

Este tipo date_diff()cria um DateTimeInterval, mas com a unidade mais alta em horas em vez de anos ... pode ser formatado como de costume.

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

NB que usei em format('U')vez de getTimestamp()por causa do comentário no manual . Observe também que 64 bits são necessários para datas pós-época e pré-época negativa!

Arth
fonte
0

Esta função ajuda você a calcular anos e meses exatos entre duas datas fornecidas, $doj1e $doj. Ele retorna o exemplo 4.3 significa 4 anos e 3 meses.

<?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
fonte
A edição sugerida é muito pequena, mas <phpprecisa ser alterada para <?phpOu aprovar a edição sugerida, que remove o bug completamente.
anishsane
0

Isso está funcionando no meu projeto. Eu acho: isso será útil para você.

Se a data estiver no passado, então inverter será 1.
Se a data estiver no futuro, então inverter será 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
fonte
0

Para passar um carimbo de data / hora Unix, use esta notação

$now        = time();
$now        = new DateTime("@$now");
Steve Whitby
fonte
1
Nota O fuso horário será transmitido e gerado como +0:00quando usado @no construtor DateTime. Ao usar o DateTime::modify()método, o carimbo de data / hora será transmitido +0:00e o fuso horário atual será exibido. Como alternativa, use $date = new DateTime(); $date->setTimestamp($unix_timestamp);ver: 3v4l.org/BoAWI
fyrye
0

O carbono também pode ser um bom caminho a percorrer.

Do site:

Uma extensão simples da API PHP para DateTime. http://carbon.nesbot.com/

Exemplo:

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 a classe DateTime para herdar métodos, incluindo diff(). Acrescenta agradáveis açúcares como diffInHours, diffInMintutes, diffInSecondsetc.

Joshuamabina
fonte
0

Primeiro, você deve criar um objeto de intervalo a partir de um intervalo de datas. Pela formulação usada apenas nesta frase, pode-se facilmente identificar as abstrações básicas necessárias. Existe um intervalo como conceito e mais algumas maneiras de implementá-lo, incluindo aquela já mencionada - a partir de um intervalo de datas. Assim, um intervalo se parece com isto:

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

FromISO8601tem a mesma semântica: é um objeto datetime criado from iso8601-formatted string, daí o nome.

Quando você tem um intervalo, pode formatá-lo como quiser. Se precisar de várias horas inteiras, você pode ter

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

Se você quiser um total máximo de horas, aqui está:

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

Para obter mais informações sobre essa abordagem e alguns exemplos, verifique esta entrada .

Vadim Samokhin
fonte
0

Além da resposta muito útil de @fyrye esta é uma solução normal para o bug mencionado ( este ), que DatePeriod subtrai uma hora ao entrar no verão, mas não adiciona uma hora ao sair do verão (e, portanto, Europa / Berlim tem seu correto 743 horas, mas outubro tem 744 em vez de 745 horas):

Contando as horas de um mês (ou qualquer intervalo de tempo), considerando as transições do horário de verão em ambas as direções

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 você marcar um intervalo de tempo (mais longo), o que leva a mais do que essas duas transições, minha solução alternativa não vai tocar nas horas contadas para reduzir o potencial de efeitos colaterais engraçados. Nesses casos, uma solução mais complicada deve ser implementada. Pode-se iterar sobre todas as transições encontradas e comparar a corrente com a última e verificar se é uma com DST verdadeiro-> falso.

spackmat
fonte