Skip to content

Instantly share code, notes, and snippets.

@hakre
Created October 30, 2012 00:49
Show Gist options
  • Select an option

  • Save hakre/3977645 to your computer and use it in GitHub Desktop.

Select an option

Save hakre/3977645 to your computer and use it in GitHub Desktop.
DateTime Range and Ranges
<?php
/**
* @link http://stackoverflow.com/questions/13129336/split-a-time-range-into-pieces-by-other-time-ranges
* @link https://gist.github.com/gists/3977645
* @author hakre
*/
class Range
{
/**
* @var DateTime
*/
protected $start;
/**
* @var DateTime
*/
protected $end;
public function __construct(DateTime $start, DateTime $end) {
$this->setStart($start);
$this->setEnd($end);
}
/**
* @return DateTime
*/
public function getStart() {
return $this->start;
}
public function setStart(DateTime $start) {
$this->start = $start;
}
/**
* @return \DateTime
*/
public function getEnd() {
return $this->end;
}
/**
* @param DateTime $end
* @throws InvalidArgumentException
*/
public function setEnd(DateTime $end) {
if ($end < $this->start) {
throw new InvalidArgumentException('End before Start');
}
$this->end = $end;
}
public function hasTime(DateTime $time) {
return $this->start <= $time and $this->end >= $time;
}
public function hasContact(Range $range) {
return $this->hasTime($range->start)
or $this->hasTime($range->end);
}
public function isSame(Range $range) {
return $this->start == $range->start
and $this->end == $range->end;
}
public function isWithin(Range $range) {
return $range->start > $this->start
and $range->end < $this->end;
}
public function isSubset(Range $range) {
return $range->hasTime($this->start)
and $range->hasTime($this->end);
}
public function add(Range $range) {
if (!$this->hasContact($range)) {
throw new InvalidArgumentException('Range needs to overlap.');
}
if ($range->start < $this->start) {
$this->start = $range->start;
}
if ($range->end > $this->end) {
$this->end = $range->end;
}
}
public function substract(Range $range) {
if ($this->isWithIn($range)) {
throw new InvalidArgumentException('Range would divide.');
}
if ($this->isSubset($range)) {
throw new InvalidArgumentException('Range would delete.');
}
if (!$this->hasContact($range)) {
return;
}
if ($range->start == $this->start) {
$this->start = $range->end;
return;
}
if ($range->end == $this->end) {
$this->end = $range->start;
return;
}
if ($range->start < $this->end) {
$this->end = $range->start;
} elseif ($range->end > $this->start) {
$this->start = $range->end;
}
}
public function getDifferenceArray(Range $range) {
if ($this->isSubset($range)) {
return [];
}
if (!$this->hasContact($range)) {
return [clone $this];
}
if ($this->isWithin($range)) {
$result[1] = clone $result[0] = clone $this;
$result[0]->end = $range->start;
$result[1]->start = $range->end;
return $result;
}
$result = clone $this;
$result->substract($range);
return [$result];
}
public function format($format) {
return [
$this->start->format($format),
$this->end->format($format)
];
}
}
class Ranges implements IteratorAggregate, Countable
{
protected $ranges = [];
public function __construct($ranges = NULL, DateTime $end = NULL) {
if ($ranges) {
if ($ranges instanceof DateTime) {
if (null === $end) {
throw new InvalidArgumentException('Need start and end.');
}
$ranges = new Range($ranges, $end);
}
if ($ranges instanceof Range) {
$ranges = [$ranges];
}
foreach ($ranges as $range) {
$this->append($range);
}
}
}
public function getStart() {
if (!$this->ranges) {
throw new BadMethodCallException('Enpty Range');
}
return $this->ranges[0]->getStart();
}
public function getEnd() {
if (!$this->ranges) {
throw new BadMethodCallException('Enpty Range');
}
return $this->ranges[count($this->ranges) - 1]->getEnd();
}
public function append(Range $range) {
if ($this->ranges) {
if ($range->getStart() <= $this->getEnd()) {
throw new InvalidArgumentException('Can not append Range that is inside ranged time already');
}
}
$this->ranges[] = $range;
}
/**
* @param Range $range
* @return Ranges
*/
public function substractRange(Range $range) {
$result = new self();
foreach ($this as $member) {
/* @var Range $member */
foreach ($member->getDifferenceArray($range) as $new) {
$result->append($new);
}
}
$this->ranges = $result->ranges;
}
public function substract(Ranges $ranges) {
$result = clone $this;
foreach ($ranges as $range) {
$result->substractRange($range);
}
$this->ranges = $result->ranges;
}
public function getIterator() {
return new ArrayIterator($this->ranges);
}
public function getRange() {
return new Range($this->getStart(), $this->getEnd());
}
public function count() {
return count($this->ranges);
}
}
$shift = new Ranges(new DateTime('14:30:00'), new DateTime('18:30:00'));
$unavailables = new Ranges([
new Range(new DateTime('15:30:00'), new DateTime('16:30:00')),
new Range(new DateTime('17:30:00'), new DateTime('18:30:00')),
]);
$shift->substract($unavailables);
foreach ($shift as $range) {
vprintf("%s - %s\n", $range->format('H:i:s'));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment