[英]How to get total time from several date ranges in php
I have several date ranges in form of DateTime $begin, DateTime $end
.我有几个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.我想要做的是获取第一个开始到最新一个结束(在上述情况下为第四个)之间的范围的长度(以秒或DateInterval
为单位),不包括任何范围未涵盖的区域。
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:仅对于两个范围,我有一个返回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;
}
where addIntervals
is a function that returns sum of two DateInterval
objects as another DateInterval
object.其中addIntervals
是一个函数,它将两个DateInterval
对象的总和作为另一个DateInterval
对象返回。
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).为简化起见,假设我们只有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')(会有很多这样的范围)。 The result I'd like to have now is 4 hours (from 6:00 to 9:00 + from 11:00 to 12:00).我现在想要的结果是 4 小时(从 6:00 到 9:00 + 从 11:00 到 12:00)。
$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));
I would recommend that you create a function that returns an instance of your Range class that has its properties set to the start and end of the whole period.我建议您创建一个函数,该函数返回 Range 类的一个实例,该实例的属性设置为整个期间的开始和结束。 Something like this:-像这样的东西:-
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();
}
}
I chose to create a minimal factory class that could, among other things, do this type of calculation for you:我选择创建一个最小的工厂类,除其他外,它可以为您进行此类计算:
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;
}
}
Some code to demonstrate that it works:-一些代码来证明它的工作原理:-
$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());
At the time I ran it I got the following result:-在我运行它时,我得到了以下结果:-
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
This will cope with any number of Range objects in any order and always return a Range object that gives you the earliest and latest dates spanned by all the ranges supplied.这将以任何顺序处理任意数量的 Range 对象,并始终返回一个 Range 对象,该对象为您提供所有提供的范围跨越的最早和最晚日期。
I also added methods to allow you to get the result as a DateInterval instance, or as a number of seconds.我还添加了一些方法,允许您以 DateInterval 实例或秒数的形式获取结果。
The Example For given task给定任务的示例
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;
Following code can be used as part of solution after converting dates to timestamps: https://stackoverflow.com/a/3631016/1414555将日期转换为时间戳后,以下代码可用作解决方案的一部分: https : //stackoverflow.com/a/3631016/1414555
Once $data is array with timestamps you can use it:一旦 $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]);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.