Skip to content
Advertisement

How to get total time from several date ranges in php

I have several date ranges in form of DateTime $begin, DateTime $end. Those ranges can overlap in every possible way:

|-------|
               |=======|
           |------|
                     |======|
           |------------|
|=======|
  |---|

etc.

What I am trying to do is to get length (in seconds or DateInterval) of those ranges between start of the first one and the end of the latest one (fourth in the case above), excluding regions not covered by any range.

There is no problem for only two ranges, but I can’t work out how to extend it to handle more than two.

EDIT:

class Range {
    public DateTime $begin;
    public DateTime $end;
}

$ranges = getRanges(); # function that returns array of Range objects

function getActiveHours($_ranges = array()) {
  $sum = 0;
  # this is the function I'd like to have
  return $sum;
}

For two ranges only I have a function which returns DateInterval object:

function addTimeRanges(DateTime $b1, DateTime $e1, DateTime $b2, DateTime $e2) {
    $res = null;
    if ($e1 < $b2 || $e2 < $b1) { # separate ranges
        $r1 = $b1->diff($e1);
        $r2 = $b2->diff($e2);
        $res = addIntervals($r1, $r2);
    } else if ($b1 <= $b2 && $e1 >= $e2) { # first range includes second
        $res = $b1->diff($e1);
    } else if ($b1 > $b2 && $e1 < $e2) { # second range includes first
        $res = $b2->diff($e2);
    } else if ($b1 < $b2 && $e1 <= $e2 && $b2 <= $e1) { # partial intersection
        $res = $b1->diff($e2);
    } else if ($b2 < $b1 && $e2 <= $e1 && $b1 <= $e2) { # partial intersection
        $res = $b2->diff($e1);
    }
    return $res;
}

where addIntervals is a function that returns sum of two DateInterval objects as another DateInterval object.

This is some basic version, in my production code I use a lot of other irrelevant stuff.

To simplify let’s say we have only Time part of DateTime: (’06:00:00′ to ’08:00:00′), (’07:00:00′ to ’09:00:00′), (’06:00:00′, ’08:00:00′), (’11:00:00′ to ’12:00:00′) (there will be many such ranges). The result I’d like to have now is 4 hours (from 6:00 to 9:00 + from 11:00 to 12:00).

Advertisement

Answer

$ranges = array(
    array(date_create_from_format('U', 1364654958), date_create_from_format('U', 1364655758)), //800s (intersect with 2 row, 700s) = 100s
    array(date_create_from_format('U', 1364654658), date_create_from_format('U', 1364655658)), //1000s (intersect with 1 row)
    array(date_create_from_format('U', 1364656858), date_create_from_format('U', 1364656958)), //100s
);  //total 1200s = 20m
array_multisort($ranges, SORT_ASC, array_map(function($a){return $a[0];}, $ranges));
$count = count($ranges)-1;
for ($i=0; $i < $count; $i++) {
    if ($ranges[$i+1][0] < $ranges[$i][1]) {
        $ranges[$i][1] = max($ranges[$i][1], $ranges[$i+1][1]);
        unset($ranges[$i+1]);
        $i--;
        $count--;
    }
}
$sum = date_create();
foreach ($ranges as $value) {
    date_add($sum, date_diff($value[0],$value[1]));
}
print_r(date_diff(date_create(), $sum));
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement