FreeBusyData.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <?php
  2. namespace Sabre\VObject;
  3. /**
  4. * FreeBusyData is a helper class that manages freebusy information.
  5. *
  6. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  7. * @author Evert Pot (http://evertpot.com/)
  8. * @license http://sabre.io/license/ Modified BSD License
  9. */
  10. class FreeBusyData
  11. {
  12. /**
  13. * Start timestamp.
  14. *
  15. * @var int
  16. */
  17. protected $start;
  18. /**
  19. * End timestamp.
  20. *
  21. * @var int
  22. */
  23. protected $end;
  24. /**
  25. * A list of free-busy times.
  26. *
  27. * @var array
  28. */
  29. protected $data;
  30. public function __construct($start, $end)
  31. {
  32. $this->start = $start;
  33. $this->end = $end;
  34. $this->data = [];
  35. $this->data[] = [
  36. 'start' => $this->start,
  37. 'end' => $this->end,
  38. 'type' => 'FREE',
  39. ];
  40. }
  41. /**
  42. * Adds free or busytime to the data.
  43. *
  44. * @param int $start
  45. * @param int $end
  46. * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE
  47. */
  48. public function add($start, $end, $type)
  49. {
  50. if ($start > $this->end || $end < $this->start) {
  51. // This new data is outside our timerange.
  52. return;
  53. }
  54. if ($start < $this->start) {
  55. // The item starts before our requested time range
  56. $start = $this->start;
  57. }
  58. if ($end > $this->end) {
  59. // The item ends after our requested time range
  60. $end = $this->end;
  61. }
  62. // Finding out where we need to insert the new item.
  63. $currentIndex = 0;
  64. while ($start > $this->data[$currentIndex]['end']) {
  65. ++$currentIndex;
  66. }
  67. // The standard insertion point will be one _after_ the first
  68. // overlapping item.
  69. $insertStartIndex = $currentIndex + 1;
  70. $newItem = [
  71. 'start' => $start,
  72. 'end' => $end,
  73. 'type' => $type,
  74. ];
  75. $precedingItem = $this->data[$insertStartIndex - 1];
  76. if ($this->data[$insertStartIndex - 1]['start'] === $start) {
  77. // The old item starts at the exact same point as the new item.
  78. --$insertStartIndex;
  79. }
  80. // Now we know where to insert the item, we need to know where it
  81. // starts overlapping with items on the tail end. We need to start
  82. // looking one item before the insertStartIndex, because it's possible
  83. // that the new item 'sits inside' the previous old item.
  84. if ($insertStartIndex > 0) {
  85. $currentIndex = $insertStartIndex - 1;
  86. } else {
  87. $currentIndex = 0;
  88. }
  89. while ($end > $this->data[$currentIndex]['end']) {
  90. ++$currentIndex;
  91. }
  92. // What we are about to insert into the array
  93. $newItems = [
  94. $newItem,
  95. ];
  96. // This is the amount of items that are completely overwritten by the
  97. // new item.
  98. $itemsToDelete = $currentIndex - $insertStartIndex;
  99. if ($this->data[$currentIndex]['end'] <= $end) {
  100. ++$itemsToDelete;
  101. }
  102. // If itemsToDelete was -1, it means that the newly inserted item is
  103. // actually sitting inside an existing one. This means we need to split
  104. // the item at the current position in two and insert the new item in
  105. // between.
  106. if (-1 === $itemsToDelete) {
  107. $itemsToDelete = 0;
  108. if ($newItem['end'] < $precedingItem['end']) {
  109. $newItems[] = [
  110. 'start' => $newItem['end'] + 1,
  111. 'end' => $precedingItem['end'],
  112. 'type' => $precedingItem['type'],
  113. ];
  114. }
  115. }
  116. array_splice(
  117. $this->data,
  118. $insertStartIndex,
  119. $itemsToDelete,
  120. $newItems
  121. );
  122. $doMerge = false;
  123. $mergeOffset = $insertStartIndex;
  124. $mergeItem = $newItem;
  125. $mergeDelete = 1;
  126. if (isset($this->data[$insertStartIndex - 1])) {
  127. // Updating the start time of the previous item.
  128. $this->data[$insertStartIndex - 1]['end'] = $start;
  129. // If the previous and the current are of the same type, we can
  130. // merge them into one item.
  131. if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) {
  132. $doMerge = true;
  133. --$mergeOffset;
  134. ++$mergeDelete;
  135. $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start'];
  136. }
  137. }
  138. if (isset($this->data[$insertStartIndex + 1])) {
  139. // Updating the start time of the next item.
  140. $this->data[$insertStartIndex + 1]['start'] = $end;
  141. // If the next and the current are of the same type, we can
  142. // merge them into one item.
  143. if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) {
  144. $doMerge = true;
  145. ++$mergeDelete;
  146. $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end'];
  147. }
  148. }
  149. if ($doMerge) {
  150. array_splice(
  151. $this->data,
  152. $mergeOffset,
  153. $mergeDelete,
  154. [$mergeItem]
  155. );
  156. }
  157. }
  158. public function getData()
  159. {
  160. return $this->data;
  161. }
  162. }