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 =newDateTime('2006-04-12T12:30:00');
$date2 =newDateTime('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:
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
+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.
$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)';
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.
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.
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 .
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);
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 minutesif($cap =='H'){
$h = intval($working/3600);
$working -= $h *3600;
$cap ='M';}// If capped at minutes, calc and remove minutesif($cap =='M'){
$m = intval($working/60);
$working -= $m *60;}// Seconds remain
$s = $working;// Build interval and invert if necessary
$interval =newDateInterval('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!
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.
Carbon estende a classe DateTime para herdar métodos, incluindo diff(). Acrescenta agradáveis açúcares como diffInHours, diffInMintutes, diffInSecondsetc.
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:
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.
strftime()
e dividir a diferença por 3600, mas isso sempre funcionará? Maldito seja, horário de verão!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.Respostas:
As mais recentes do PHP-Versões fornecer algumas novas classes chamados
DateTime
,DateInterval
,DateTimeZone
eDatePeriod
. 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: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: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
fonte
$date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');
e$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
$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çãovar_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 ...)fonte
$diff / 3600
?Para fornecer outro método para
DatePeriod
quando usar o fuso horário UTC ou GMT .Contar horas https://3v4l.org/Mu3HD
Resultado
Contar horas com horário de verão https://3v4l.org/QBQUB
Informamos que
DatePeriod
exclui 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
Resultado
fonte
DateInterval
não aceita valores fracionários como na especificação ISO 8601. Portanto,P1.2Y
não é uma duração válida em PHP.iterator_count
, é devido aDatePeriod
nã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.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, usandoDateTime::setTime(date->format('H'), 0)
. 3v4l.org/K7uss Desta forma, se você ultrapassar o intervalo de 1 segundo, outra data não será criada.sua resposta é:
round((strtotime($day2) - strtotime($day1))/(60*60))
fonte
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 .
fonte
format("U")
é preferível agetTimestamp()
getTimestamp()
agora retorna exatamente o mesmo valor queformat("U")
. O primeiro é um número inteiro, enquanto o último é uma string (menos eficiente aqui).getTimestamp()
que eu pudesse ter certeza.fonte
Eu acho que a função strtotime () aceita este formato de data.
fonte
fonte
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:
Este tipo
date_diff()
cria umDateTimeInterval
, mas com a unidade mais alta em horas em vez de anos ... pode ser formatado como de costume.NB que usei em
format('U')
vez degetTimestamp()
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!fonte
Esta função ajuda você a calcular anos e meses exatos entre duas datas fornecidas,
$doj1
e$doj
. Ele retorna o exemplo 4.3 significa 4 anos e 3 meses.fonte
<php
precisa ser alterada para<?php
Ou aprovar a edição sugerida, que remove o bug completamente.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.
fonte
Para passar um carimbo de data / hora Unix, use esta notação
fonte
+0:00
quando usado@
no construtor DateTime. Ao usar oDateTime::modify()
método, o carimbo de data / hora será transmitido+0:00
e o fuso horário atual será exibido. Como alternativa, use$date = new DateTime(); $date->setTimestamp($unix_timestamp);
ver: 3v4l.org/BoAWIO carbono também pode ser um bom caminho a percorrer.
Do site:
Exemplo:
Carbon estende a classe DateTime para herdar métodos, incluindo
diff()
. Acrescenta agradáveis açúcares comodiffInHours
,diffInMintutes
,diffInSeconds
etc.fonte
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:
FromISO8601
tem a mesma semântica: é um objeto datetime criadofrom 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
Se você quiser um total máximo de horas, aqui está:
Para obter mais informações sobre essa abordagem e alguns exemplos, verifique esta entrada .
fonte
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
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.
fonte