【问题标题】:How to get total time from several date ranges in php如何从php中的多个日期范围获取总时间
【发布时间】:2013-03-21 02:17:36
【问题描述】:

我有几个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 的时间部分:('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') (会有很多这样的范围)。我现在想要的结果是 4 小时(从 6:00 到 9:00 + 从 11:00 到 12:00)。

【问题讨论】:

  • 你能提供一些代码吗?
  • 这些范围是否存储在数据库中?
  • 当你说“第一个的开始......不包括任何范围未涵盖的区域”时,你的意思是你上面的图表代表 两个 答案(因为那个差距在中间)或一个答案(总持续时间减去该间隙的持续时间)?
  • 我不精通 php,因此我将答案限制在算法描述上。将所有 DateTime 值排序为一维数组对 ( DateTime, bool ),其中 bool 标记这是否是开始时间。您需要 2 个变量(depth, curstart ) = (0, undef)。遍历数组,当当前数组元素的布尔分量为真时递增 depth,否则递减。每当 depth 从 0 变为 1 时,将 curstart 设置为日期时间当前数组元素。每当depth变为0时,添加区间[curstart, ] ...
  • ... 到覆盖区间列表并将curstart 重置为undef

标签: php datetime sum range


【解决方案1】:
$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));

【讨论】:

    【解决方案2】:

    我建议您创建一个函数来返回 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 实例或秒数的形式获取结果。

    【讨论】:

      【解决方案3】:

      给定任务的示例

      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;
      

      【讨论】:

        【解决方案4】:

        在将日期转换为时间戳后,以下代码可用作解决方案的一部分: 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]);
        

        【讨论】:

          猜你喜欢
          • 2018-04-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-24
          • 2020-10-09
          • 1970-01-01
          • 1970-01-01
          • 2023-03-31
          相关资源
          最近更新 更多