<?php namespace Sabre\VObject; /** * FreeBusyData is a helper class that manages freebusy information. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class FreeBusyData { /** * Start timestamp * * @var int */ protected $start; /** * End timestamp * * @var int */ protected $end; /** * A list of free-busy times. * * @var array */ protected $data; function __construct($start, $end) { $this->start = $start; $this->end = $end; $this->data = []; $this->data[] = [ 'start' => $this->start, 'end' => $this->end, 'type' => 'FREE', ]; } /** * Adds free or busytime to the data. * * @param int $start * @param int $end * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE * @return void */ function add($start, $end, $type) { if ($start > $this->end || $end < $this->start) { // This new data is outside our timerange. return; } if ($start < $this->start) { // The item starts before our requested time range $start = $this->start; } if ($end > $this->end) { // The item ends after our requested time range $end = $this->end; } // Finding out where we need to insert the new item. $currentIndex = 0; while ($start > $this->data[$currentIndex]['end']) { $currentIndex++; } // The standard insertion point will be one _after_ the first // overlapping item. $insertStartIndex = $currentIndex + 1; $newItem = [ 'start' => $start, 'end' => $end, 'type' => $type, ]; $preceedingItem = $this->data[$insertStartIndex - 1]; if ($this->data[$insertStartIndex - 1]['start'] === $start) { // The old item starts at the exact same point as the new item. $insertStartIndex--; } // Now we know where to insert the item, we need to know where it // starts overlapping with items on the tail end. We need to start // looking one item before the insertStartIndex, because it's possible // that the new item 'sits inside' the previous old item. if ($insertStartIndex > 0) { $currentIndex = $insertStartIndex - 1; } else { $currentIndex = 0; } while ($end > $this->data[$currentIndex]['end']) { $currentIndex++; } // What we are about to insert into the array $newItems = [ $newItem ]; // This is the amount of items that are completely overwritten by the // new item. $itemsToDelete = $currentIndex - $insertStartIndex; if ($this->data[$currentIndex]['end'] <= $end) $itemsToDelete++; // If itemsToDelete was -1, it means that the newly inserted item is // actually sitting inside an existing one. This means we need to split // the item at the current position in two and insert the new item in // between. if ($itemsToDelete === -1) { $itemsToDelete = 0; if ($newItem['end'] < $preceedingItem['end']) { $newItems[] = [ 'start' => $newItem['end'] + 1, 'end' => $preceedingItem['end'], 'type' => $preceedingItem['type'] ]; } } array_splice( $this->data, $insertStartIndex, $itemsToDelete, $newItems ); $doMerge = false; $mergeOffset = $insertStartIndex; $mergeItem = $newItem; $mergeDelete = 1; if (isset($this->data[$insertStartIndex - 1])) { // Updating the start time of the previous item. $this->data[$insertStartIndex - 1]['end'] = $start; // If the previous and the current are of the same type, we can // merge them into one item. if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) { $doMerge = true; $mergeOffset--; $mergeDelete++; $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start']; } } if (isset($this->data[$insertStartIndex + 1])) { // Updating the start time of the next item. $this->data[$insertStartIndex + 1]['start'] = $end; // If the next and the current are of the same type, we can // merge them into one item. if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) { $doMerge = true; $mergeDelete++; $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end']; } } if ($doMerge) { array_splice( $this->data, $mergeOffset, $mergeDelete, [$mergeItem] ); } } function getData() { return $this->data; } }