Calcular el número de horas entre 2 fechas en PHP

107

¿Cómo calculo la diferencia entre dos fechas en horas?

Por ejemplo:

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

En este caso, el resultado debería ser de 47 horas.

Pensador
fuente
1
Mi respuesta inicial habría sido, convertir ambos valores en marcas de tiempo usando strftime()y dividir la diferencia por 3600, pero ¿funcionará siempre? ¡Maldito seas, horario de verano!
Pekka
@Pekka: no, no siempre funcionará, supongo ... Echa un vistazo a mi respuesta. Allí publiqué una solución considerando, zonas horarias, años bisiestos, segundos bisiestos y dst :)
Fidi
@Pekka, si lo usa strtotime(), siempre funcionará, siempre que use la zona horaria predeterminada O especifique explícitamente el desplazamiento de la zona horaria. No hay razón para maldecir el horario de verano.
Walter Tross

Respuestas:

205

Las nuevas versiones de PHP-proporcionan algunas nuevas clases llamadas DateTime, DateInterval, DateTimeZoney DatePeriod. Lo bueno de estas clases es que considera diferentes zonas horarias, años bisiestos, segundos bisiestos, horario de verano, etc. Y además es muy fácil de usar. Esto es lo que quiere con la ayuda de estos 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');

El objeto DateInterval, que se devuelve también proporciona otros métodos distintos a format. Si desea obtener el resultado solo en horas, puede hacer algo como esto:

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

Y aquí están los enlaces para la documentación:

Todas estas clases también ofrecen una forma procedimental / funcional de operar con fechas. Por lo tanto, eche un vistazo a la descripción general: http://php.net/manual/book.datetime.php

Fidi
fuente
+1 ¡Buen trabajo! Esto parece sólido y es una buena descripción general. Es importante tener en cuenta que los cálculos pueden variar según la zona horaria debido a las diferentes reglas de DST, por lo que probablemente sea una buena idea definir siempre la zona y no depender de la configuración del servidor.
Pekka
Sí. Con este objeto incluso podría calcular entre fechas en diferentes zonas horarias. $date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');y$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');
Fidi
3
Si alguien se encuentra con el mismo problema que acabo de hacer, donde $diff->des igual a 0 (porque estoy tratando de calcular las horas entre dos fechas que tienen exactamente 2 meses de diferencia): Running var_dump($diff)me mostró otro parámetro:, ["days"]=>int(61)así que terminé usando $hours = $diff->days * 24;, y llegó cerca del "promedio" de 1440 horas dados 2 meses de 30 días, por lo que se ve mucho mejor que un resultado de 0. (Adivinando que mi versión de PHP es un poco vieja ...)
semmelbroesel
2
Quiero decir, en muchas partes del mundo un año tiene un día de 23 horas y un día de 25 horas.
Walter Tross
4
@Amal Murali, entonces decidiste otorgar el bono a esta respuesta, ¿cuál es INCORRECTA? ¿Ha intentado calcular con esta respuesta el número de horas entre el mediodía del primero de enero y el mediodía del primero de junio, en cualquier zona horaria que tenga DST (horario de verano)? Obtendrá un resultado par, mientras que el resultado real es impar.
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č
fuente
4
¿Por qué no $diff / 3600?
Alex G
4
@AlexG Es solo una cuestión de estilo. El mismo resultado, pero los programadores suelen utilizar la multiplicación cuando se trata de tiempos
Usuario
le sugiero que le guste: round (($ t1 - $ 22) / 3600); use la ronda para obtener las horas correctas
Shiv Singh
20

Para proporcionar otro método para DatePeriodcuando se usa la zona horaria UTC o GMT .

Cuenta 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)

Cuente las horas con el horario de verano https://3v4l.org/QBQUB

Tenga en cuenta que DatePeriodexcluye una hora para el horario de verano pero no agrega otra hora cuando finaliza el horario de verano. Por lo tanto, su uso es subjetivo al resultado deseado y al rango de fechas.

Ver el informe de errores actual

//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
fuente
1
Para cualquiera que esté tan confundido como yo al ver el parámetro del constructor DateInterval, el formato es una duración ISO 8601
TheKarateKid
Otra nota es que DateIntervalno acepta valores fraccionarios como en la especificación ISO 8601. Entonces P1.2Yno es una duración válida en PHP.
fyrye
NOTA: iterator_count solo devolverá resultados positivos. Si la primera fecha es mayor que la segunda, el resultado de la diferencia será 0.
SubjectDelta
1
@SubjectDelta el problema no está relacionado iterator_count, se debe a que DatePeriodno se pueden generar fechas cuando la fecha de inicio es posterior a la fecha de finalización. Consulte: 3v4l.org/Ypsp1 para usar una fecha negativa, debe especificar un intervalo negativo, DateInterval::createFromDateString('-1 hour');con una fecha de inicio anterior a la fecha de finalización.
fyrye
1
@SubjectDelta este es otro matiz de DatePeriod, ya que por defecto incluirá la fecha de inicio entre los períodos especificados a menos que sean menores o iguales a la fecha de inicio. En efecto, le está diciendo a php que cree un período de 1 hora entre las dos fechas, dentro de 1 segundo de tiempo. Debería eliminar los minutos y segundos de sus objetos de fecha, ya que no son relevantes en el cálculo, utilizando DateTime::setTime(date->format('H'), 0). 3v4l.org/K7uss De esta manera, si está por encima del rango por 1 segundo, no se crea otra fecha.
fyrye
17

tu respuesta es:

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

Sergey Eremin
fuente
5
¿Qué pasa si hay 2 horas y 30 minutos entre? Su respuesta resultará en 3 horas. Creo que sería mejor usar piso para que diera 2 horas. Sin embargo, realmente depende de la situación.
Kapitein Witbaard
14

La forma más fácil de obtener el número correcto de horas entre dos fechas (fecha y hora), incluso en los cambios de horario de verano, es usar la diferencia en las marcas de tiempo de Unix. Las marcas de tiempo de Unix son segundos transcurridos desde 1970-01-01T00: 00: 00 UTC, ignorando los segundos intercalares (esto está bien porque probablemente no necesite esta precisión y porque es bastante difícil tener en cuenta los segundos intercalares).

La forma más flexible de convertir una cadena de fecha y hora con información de zona horaria opcional en una marca de tiempo de Unix es construir un objeto DateTime (opcionalmente con una DateTimeZone como segundo argumento en el constructor) y luego llamar a su 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
fuente
1
De un comentario en el manual parece que, para compatibilidad con fechas anteriores a la época, format("U")es preferible agetTimestamp()
Arth
1
@ Arthur, no sé cuándo fue este el caso, pero en mi PHP 5.5.9 ya no es cierto. getTimestamp()ahora devuelve exactamente el mismo valor que format("U"). Sin embargo, el primero es un número entero, mientras que el segundo es una cadena (menos eficiente aquí).
Walter Tross
Genial, tal vez era cierto en una versión anterior ... Sí, un número entero sería más limpio, así que preferiría getTimestamp()estar seguro.
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
fuente
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);

Supongo que la función strtotime () acepta este formato de fecha.

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

?>
Caminante nocturno
fuente
Esta es también una copia del formulario de respuesta 2010.
Daniel W.
2

Desafortunadamente, la solución proporcionada por FaileN no funciona como lo dijo Walter Tross ... ¡los días pueden no ser 24 horas!

Me gusta usar los objetos PHP siempre que sea posible y, para un poco más de flexibilidad, se me ocurrió la siguiente función:

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

Esto date_diff()crea como un DateTimeInterval, pero con la unidad más alta como horas en lugar de años ... se puede formatear como de costumbre.

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

NB Lo he usado en format('U')lugar de getTimestamp()por el comentario en el manual . También tenga en cuenta que se requieren 64 bits para fechas posteriores a la época y anteriores a la época negativa.

Arth
fuente
0

Esta función le ayuda a calcular los años y meses exactos entre dos fechas determinadas $doj1y $doj. Devuelve el ejemplo 4.3 significa 4 años y 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
fuente
La edición sugerida es demasiado pequeña, pero <phpdebe cambiarse a <?phpO aprobar la edición sugerida, que elimina el error por completo.
anishsane
0

Esto está funcionando en mi proyecto. Creo que esto te será útil.

Si la fecha es pasada, invertirá 1.
Si la fecha es futura, invertirá 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
fuente
0

Para pasar una marca de tiempo de Unix, use esta notación

$now        = time();
$now        = new DateTime("@$now");
Steve Whitby
fuente
1
Nota La zona horaria se pasará y se generará como +0:00cuando se usa @en el constructor DateTime. Al usar el DateTime::modify()método, se pasará la marca de tiempo +0:00y se generará la zona horaria actual. Uso alternativo, $date = new DateTime(); $date->setTimestamp($unix_timestamp);ver: 3v4l.org/BoAWI
fyrye
0

El carbono también podría ser una buena forma de hacerlo.

Desde su sitio web:

Una simple extensión de la API de PHP para DateTime. http://carbon.nesbot.com/

Ejemplo:

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 extiende la clase DateTime para heredar métodos, incluidos diff(). Se añade agradables azúcares como diffInHours, diffInMintutes, diffInSecondsetc.

Joshuamabina
fuente
0

Primero, debe crear un objeto de intervalo a partir de un rango de fechas. Solo por la redacción utilizada en esta oración, uno puede identificar fácilmente las abstracciones básicas necesarias. Hay un intervalo como concepto, y un par de formas más de implementarlo, incluido el que ya se mencionó, de un rango de fechas. Por lo tanto, un intervalo se ve así:

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

FromISO8601tiene la misma semántica: es un objeto de fecha y hora creado from iso8601-formatted string, de ahí el nombre.

Cuando tenga un intervalo, puede formatearlo como desee. Si necesita varias horas completas, puede tener

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

Si quieres un total de horas máximo, aquí tienes:

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

Para obtener más información sobre este enfoque y algunos ejemplos, consulte esta entrada .

Vadim Samokhin
fuente
0

Además de la respuesta muy útil de @ fyrye, esta es una solución aceptable para el error mencionado ( este ), que DatePeriod resta una hora cuando ingresa al verano, pero no agrega una hora al salir del verano (y por lo tanto, la marcha de Europa / Berlín tiene su 743 horas correctas, pero octubre tiene 744 en lugar de 745 horas):

Contar las horas de un mes (o cualquier período de tiempo), considerando las transiciones DST en ambas direcciones

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

PD: Si marca un período de tiempo (más largo), que conduce a más de esas dos transiciones, mi solución no tocará las horas contadas para reducir el potencial de efectos secundarios divertidos. En tales casos, se debe implementar una solución más complicada. Uno podría iterar sobre todas las transiciones encontradas y comparar la actual con la última y verificar si es una con DST verdadero-> falso.

spackmat
fuente
0

$ diff_min = (strtotime ($ día2) - strtotime ($ día1)) / 60/60; $ tiempo_total = $ diff_min;

Puedes probar este.

Shahabuddin
fuente