129

How do I calculate the difference between two dates in hours?

For example:

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

In this case the result should be 47 hours.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thinker
  • 2,253
  • 6
  • 23
  • 23
  • 1
    My initial response would have been, turn both values into time stamps using `strftime()` and split the difference by 3600, but will that always work? Damn you, Daylight Savings Time! – Pekka Jun 24 '10 at 09:20
  • 1
    @Pekka: no it won't always work I guess... Take a look at my answer. There I've posted a solution considering, timezones, leap years, leap seconds and dst :) – Fidi Jun 24 '10 at 09:49
  • @Pekka, if you use `strtotime()` it WILL always work, as long as you use the default timezone OR explicitly specify the timezone offset. No reason to curse the DST. – Walter Tross Jan 29 '14 at 20:53

17 Answers17

251

The newer PHP-Versions provide some new classes called DateTime, DateInterval, DateTimeZone and DatePeriod. The cool thing about this classes is, that it considers different timezones, leap years, leap seconds, summertime, etc. And on top of that it's very easy to use. Here's what you want with the help of this objects:

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

The DateInterval-object, which is returned also provides other methods than format. If you want the result in hours only, you could to something like this:

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

And here are the links for documentation:

All these classes also offer a procedural/functional way to operate with dates. Therefore take a look at the overview: http://php.net/manual/book.datetime.php

Glavić
  • 42,781
  • 13
  • 77
  • 107
Fidi
  • 5,754
  • 1
  • 18
  • 25
  • +1 Good work! This looks solid and is a fine overview. It's important to note that calculations may vary according to time zone due to different DST rules, so it's probably a good idea to always define the zone and not rely on server settings. – Pekka Jun 24 '10 at 09:51
  • Yep. With this object you could even calcualte between dates in different timezones. `$date1 = new DateTime('2006-04-12T12:30:00 Europe/Berlin');` and `$date2 = new DateTime('2006-04-14T11:30:00 America/New_York');` – Fidi Jun 24 '10 at 10:00
  • 7
    If someone runs into the same issue as I just did where `$diff->d` equals 0 (because I am trying to calculate the hours between two dates that are exactly 2 months apart): Running `var_dump($diff)` showed me another parameter: `["days"]=>int(61)`, so I ended up using `$hours = $diff->days * 24;`, and it came out close to the "average" of 1440 hours given 2 30 day months, so that's looking much better than a result of 0. (Guessing my PHP version is a bit old...) – semmelbroesel Apr 01 '13 at 19:01
  • 3
    I mean, in many parts of the world a year has one 23-hour day and one 25-hour day. – Walter Tross Jan 30 '14 at 13:18
  • 5
    @Amal Murali, so you decided to award the bonus to this answer, which is WRONG? Have you tried to calculate with this answer the number of hours between noon of the first of January and noon of the first of June, in any timezone that has DST (daylight saving time)? You'll get an even result, while the true result is odd. – Walter Tross Jan 30 '14 at 16:01
  • The problem with `DateInterval::format()` is when we want a result specifics units. For example: the difference in 24 hours will shown as `0` hours with `1` day. Thus we need to aware and do another calculation if day are not empty. – GusDeCooL Oct 28 '21 at 06:05
  • 1
    Quite why `DateDiff` can't give us the total hours is beyond me. Thanks for your answer and direction. Was confused why my diff was missing a day's worth of hours. Makes sense now! – Antony May 20 '22 at 09:28
90
$t1 = strtotime( '2006-04-14 11:30:00' );
$t2 = strtotime( '2006-04-12 12:30:00' );
$diff = $t1 - $t2;
$hours = $diff / ( 60 * 60 );
AwesomeGuy
  • 1,059
  • 9
  • 28
Jan Hančič
  • 53,269
  • 16
  • 95
  • 99
20

To provide another method for DatePeriod when using the UTC or GMT timezone.

Count Hours 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)';

Result

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

Count Hours with Daylight Savings https://3v4l.org/QBQUB

Please be advised that DatePeriod excludes an hour for DST but does not add another hour when DST ends. So its usage is subjective to your desired outcome and date range.

See the current bug report

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

Result

#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)
Will B.
  • 17,883
  • 4
  • 67
  • 69
  • 1
    For anyone as confused as I was in seeing the DateInterval constructor parameter, the format is an [ISO 8601 Duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) – TheKarateKid Jul 25 '17 at 18:36
  • Another note is that `DateInterval` does not accept fractional values as in the ISO 8601 specification. So `P1.2Y` is not a valid duration in PHP. – Will B. Jul 25 '17 at 19:34
  • NOTE: iterator_count will return only positive results. If the first date is greater then the second one, the diff result will be 0. – SubjectDelta Jun 21 '19 at 15:21
  • 1
    @SubjectDelta the issue is not related to `iterator_count`, it is due to `DatePeriod` not being able to generate dates when the start date is in the future from the end date. See: https://3v4l.org/Ypsp1 to use a negative date, you need to specify a negative interval, `DateInterval::createFromDateString('-1 hour');` with a start date in the past of from the end date. – Will B. Jun 21 '19 at 15:28
  • Got it. Then there's another issue, how many hours are there between `13:00:00` and `13:00:01`? [3v4l.org/oXYng](https://3v4l.org/oXYng) – SubjectDelta Jun 21 '19 at 17:03
  • 1
    @SubjectDelta this is another nuance of `DatePeriod`, as by default it will include the start date between the specified periods unless they are less than or equal to the start date. In effect you are telling php to create a period of 1 hour between the two dates, within 1 second of time. You would need to remove the minutes and seconds from your date objects, as they are not relevant in the calculation, using `DateTime::setTime(date->format('H'), 0)`. https://3v4l.org/K7uss This way if you are over the range by 1 second, another date is not created. – Will B. Jun 21 '19 at 17:36
18

your answer is:

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

Sergey Eremin
  • 10,994
  • 2
  • 38
  • 44
  • 6
    What if there are 2 hours and 30 minutes between? Your answer will result in 3 hours. I think it would be better to use floor so that would yield 2 hours. Really depends on the situation though. – Kapitein Witbaard Aug 18 '16 at 08:59
17

The easiest way to get the correct number of hours between two dates (datetimes), even across daylight saving time changes, is to use the difference in Unix timestamps. Unix timestamps are seconds elapsed since 1970-01-01T00:00:00 UTC, ignoring leap seconds (this is OK because you probably don't need this precision, and because it's quite difficult to take leap seconds into account).

The most flexible way to convert a datetime string with optional timezone information into a Unix timestamp is to construct a DateTime object (optionally with a DateTimeZone as a second argument in the constructor), and then call its getTimestamp method.

$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
  • 12,237
  • 2
  • 40
  • 64
  • 1
    From a comment in the [manual](http://php.net/manual/en/datetime.gettimestamp.php) it appears that, for compatability with pre-epoch dates, `format("U")` is preferable to `getTimestamp()` – Arth May 06 '15 at 12:36
  • 1
    @Arth, I don't know when this was the case, but in my PHP 5.5.9 it's not true any more. `getTimestamp()` now returns exactly the same value as `format("U")`. The former is an integer, though, while the latter is a string (less efficient here). – Walter Tross Oct 15 '15 at 20:52
  • Cool, perhaps it was true in an earlier version.. Yes an integer would be cleaner, so I'd prefer `getTimestamp()` if I could be sure. – Arth Oct 16 '15 at 09:23
4
<?
     $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;

?>
Community
  • 1
  • 1
Night Walker
  • 104
  • 10
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
  • 1,863
  • 24
  • 8
2

Unfortunately the solution provided by FaileN doesn't work as stated by Walter Tross.. days may not be 24 hours!

I like to use the PHP Objects where possible and for a bit more flexibility I have come up with the following function:

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

This like date_diff() creates a DateTimeInterval, but with the highest unit as hours rather than years.. it can be formatted as usual.

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

N.B. I have used format('U') instead of getTimestamp() because of the comment in the manual. Also note that 64-bit is required for post-epoch and pre-negative-epoch dates!

Arth
  • 12,789
  • 5
  • 37
  • 69
2
$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);

I guess strtotime() function accept this date format.

Boris Delormas
  • 2,509
  • 1
  • 19
  • 27
2

Carbon could also be a nice way to go.

From their website:

A simple PHP API extension for DateTime. http://carbon.nesbot.com/

Example:

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 extends the DateTime class to inherit methods including diff(). It adds nice sugars like diffInHours, diffInMintutes, diffInSeconds e.t.c.

0

This function helps you to calculate exact years and months between two given dates, $doj1 and $doj. It returns example 4.3 means 4 years and 3 month.

<?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
  • 3,230
  • 1
  • 17
  • 27
0

In addition to @fyrye's very helpful answer this is an okayish workaround for the mentioned bug (this one), that DatePeriod substracts one hour when entering summertime, but doesn't add one hour when leaving summertime (and thus Europe/Berlin's March has its correct 743 hours but October has 744 instead of 745 hours):

Counting the hours of a month (or any timespan), considering DST-transitions in both directions

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

P.S. If you check a (longer) timespan, which leads to more than those two transitions, my workaround won't touch the counted hours to reduce the potential of funny side effects. In such cases, a more complicated solution must be implemented. One could iterate over all found transitions and compare the current with the last and check if it is one with DST true->false.

spackmat
  • 892
  • 9
  • 23
0
$diff_min = ( strtotime( $day2 ) - strtotime( $day1 ) ) / 60 / 60;
$total_time  = $diff_min;

You can try this one.

fcdt
  • 2,371
  • 5
  • 14
  • 26
0

The second part of the answer from @fidi doesn't factor in months/years.

$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2010-04-14T11:30:00');
$diff = $date2->diff($date1);
$hours = $diff->h;
$days = intval($diff->format('%a'));
$hours = $hours + ($days*24);
echo $hours;
J Flacks
  • 36
  • 4
-1

To pass a unix timestamp use this notation

$now        = time();
$now        = new DateTime("@$now");
Steve Whitby
  • 403
  • 4
  • 8
  • 1
    **Note** The timezone will be passed and output as `+0:00` when using `@` in the DateTime constructor. While using the `DateTime::modify()` method will pass the timestamp as `+0:00` and output the current timezone. Alternatively use `$date = new DateTime(); $date->setTimestamp($unix_timestamp);` see: https://3v4l.org/BoAWI – Will B. Jul 25 '17 at 12:26
-1
// Create two new DateTime-objects... 
$date1 = new DateTime('2006-04-12T12:30:00');
$date2 = new DateTime('2006-04-14T11:30:00');

// The diff-method returns difference in days...
$diffInDays = $date2->diffInDays($date1);

// The diff-method returns difference in hours...
$diffInHours = $date2->diffInHours($date1);

// The diff-method returns difference in mintes...
$diffInMinutes = $date2->diffInMinutes($date1);
-2

This is working in my project. I think, This will be helpful for you.

If Date is in past then invert will 1.
If Date is in future then invert will 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
  • 512
  • 2
  • 6
  • 18