如何在php中从多个日期范围中获取总时间

imzjd6km  于 10个月前  发布在  PHP
关注(0)|答案(4)|浏览(84)

我有几个以DateTime $begin, DateTime $end的日期范围。这些范围可以以各种可能的方式重叠:

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

字符串
等等。
我想做的是获取第一个范围的开始和最新一个范围的结束之间的长度(以秒或DateInterval为单位)(在上面的例子中是第四个),不包括任何范围未覆盖的区域。
只有两个范围没有问题,但是我不知道如何扩展它来处理两个以上的范围。

编辑:

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


对于两个范围,我只有一个返回DateInterval对象的函数:

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


其中addIntervals是一个函数,它将两个DateInterval对象的和作为另一个DateInterval对象返回。
这是一些基本的版本,在我的生产代码中,我使用了很多其他不相关的东西。
为了简化,假设我们只有DateTime的Time部分:('06:00:00'到'08:00:00')、('07:00:00'到'09:00:00')、('06:00:00'、'08:00:00')、('11:00:00'到'12:00:00')(将存在许多这样的范围)。我现在想要的结果是4个小时(从6:00到9:00 +从11:00到12:00)。

a64a0gku

a64a0gku1#

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

字符串

moiiocjp

moiiocjp2#

我建议您创建一个函数,该函数返回Range类的示例,该示例的属性设置为整个周期的开始和结束。就像这样:-

class Range
{
    public $startDate;
    public $endDate;

    public function __construct(\DateTime $startDate, \DateTime $endDate)
    {
        $this->startDate = $startDate;
        $this->endDate = $endDate;
    }

    public function getInterval()
    {
        return $this->startDate->diff($this->endDate);
    }

    public function getSeconds()
    {
        return $this->endDate->getTimestamp() - $this->startDate->getTimestamp();
    }
}

字符串
我选择创建一个最小的工厂类,它可以为您执行以下类型的计算:

class Ranges
{
    private $ranges = array();

    public function addRange(\Range $range)
    {
        $this->ranges[] = $range;
    }

    public function getFullRange()
    {
        $fullRange = new \Range($this->ranges[0]->startDate, $this->ranges[0]->endDate);
        foreach($this->ranges as $range){
            if($range->startDate < $fullRange->startDate){
                $fullRange->startDate = $range->startDate;
            }
            if($range->endDate > $fullRange->endDate){
                $fullRange->endDate = $range->endDate;
            }
        }
        return $fullRange;
    }
}


一些代码来演示它的工作原理:

$ranges = new \Ranges();
$ranges->addRange(new \Range(new \DateTime(), new \DateTime('+ 2 hours')));
$ranges->addRange(new \Range(new \DateTime('1st Jan 2012'), new \DateTime('3rd Jan 2012')));
$ranges->addRange(new \Range(new \DateTime('- 4 days'), new \DateTime('+ 30 days')));
$fullRange = $ranges->getFullRange();
var_dump($fullRange);
var_dump($fullRange->getInterval());
var_dump($fullRange->getSeconds());


在我运行它的时候,我得到了以下结果:

object(Range)[11]
  public 'startDate' => 
    object(DateTime)[6]
      public 'date' => string '2012-01-01 00:00:00' (length=19)
      public 'timezone_type' => int 3
      public 'timezone' => string 'Europe/London' (length=13)
  public 'endDate' => 
    object(DateTime)[10]
      public 'date' => string '2013-05-14 19:36:06' (length=19)
      public 'timezone_type' => int 3
      public 'timezone' => string 'Europe/London' (length=13)

object(DateInterval)[12]
  public 'y' => int 1
  public 'm' => int 4
  public 'd' => int 13
  public 'h' => int 19
  public 'i' => int 36
  public 's' => int 6
  public 'invert' => int 0
  public 'days' => int 499

int 43180566


这将科普任何顺序的任意数量的Range对象,并始终返回一个Range对象,该对象提供所提供的所有范围所跨越的最早和最晚日期。
我还添加了一些方法,允许您以DateInterval示例或秒数的形式获取结果。

uz75evzq

uz75evzq3#

给定任务的示例

class DateRange
{
    private $startDate;
    private $endDate;

    public function getStart(){
        return clone $this->startDate;
    }

    public function getEnd(){
        return clone $this->endDate;
    }

    public function __construct(\DateTime $startDate, \DateTime $endDate = null)
    {
        $this->startDate = $startDate;
        if (is_null($endDate)) {
            $this->endDate = new \DateTime();
        } else {
            $this->endDate = $endDate;
        }
    }
}

class DateRanges
{
    private $ranges = array();

    public function addRange(\DateRange $range)
    {
        $this->ranges[] = $range;
    }

    private function _RageToArray(\DateRange $_in)
    {
        $_r = array();
        $start = $_in->getStart();
        $end = $_in->getEnd();
        while($start<$end){
            $_r[$start->format('Y-m-d')] = null;
            $start->modify('+1 days');
        }
        return $_r;
    }

    public function getDaysCount()
    {
        $_r = array();

        foreach($this->ranges as $range){
            $_r += $this->_RageToArray($range);
        }
        return count($_r);
    }
}

$today = new DateTime();
$ranges = new DateRanges();

$x = new stdClass();
$x->start = (clone $today);
$x->start->modify('-3 years');
$x->end = (clone $x->start);
$x->end->modify('+1 month');
$ranges->addRange(new DateRange($x->start, $x->end));

$x = new stdClass();
$x->start = (clone $today);
$x->start->modify('-3 years');
$x->end = (clone $x->start);
$x->end->modify('+15 days');
$ranges->addRange(new DateRange($x->start, $x->end));

$x = new stdClass();
$x->start = (clone $today);
$x->start->modify('-4 years');
$x->end = (clone $x->start);
$x->end->modify('+15 days');
$ranges->addRange(new DateRange($x->start, $x->end));

echo $ranges->getDaysCount() . ' must be near ' . (31 + 15) . PHP_EOL;

字符串

iklwldmw

iklwldmw4#

下面的代码可以在将日期转换为时间戳后用作解决方案的一部分:https://stackoverflow.com/a/3631016/1414555
一旦$data是带有时间戳的数组,你就可以使用它了:

usort($data, function($a, $b) { return $a[0] - $b[0]; });

$n = 0; $len = count($data);
for ($i = 1; $i < $len; ++$i) {
    if ($data[$i][0] > $data[$n][1] + 1)
        $n = $i;
    else {
        if ($data[$n][1] < $data[$i][1])
            $data[$n][1] = $data[$i][1];
        unset($data[$i]);
    }
}

$duration = 0; //Duration in seconds
foreach ($data as $range)
    $duration += ($range[1] - $range[0]);

字符串

相关问题