TimeLine.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace App\Helpers;
  3. use DateTime;
  4. class TimeLine {
  5. public $start = null;
  6. public $end = null;
  7. public $available = [];
  8. public $unavailable = [];
  9. public $removeStart = null;
  10. public $removeEnd = null;
  11. public function __construct(DateTime $_start, DateTime $_end)
  12. {
  13. $this->start = $_start->getTimestamp();
  14. $this->end = $_end->getTimestamp();
  15. }
  16. public function addAvailability(DateTime $_start, DateTime $_end) {
  17. $this->available[] = new TimeSlot($_start, $_end);
  18. // sort by start
  19. usort($this->available, function ($item1, $item2) {
  20. return $item1->start <=> $item2->start;
  21. });
  22. // compact (join adjacent overlapping slots)
  23. // $this->vdump($this->available);
  24. $this->normalize();
  25. $this->vdump($this->available);
  26. }
  27. public function removeAvailability(DateTime $_start, DateTime $_end) {
  28. $removeStart = $_start->getTimestamp();
  29. $removeEnd = $_end->getTimestamp();
  30. $cleaned = $this->removeEmbeddedInAvailSlot($removeStart, $removeEnd);
  31. if(!$cleaned) {
  32. for ($i=0; $i<count($this->available); $i++) $this->available[$i]->processed = false;
  33. $this->removeStart($removeStart, $removeEnd);
  34. for ($i=0; $i<count($this->available); $i++) $this->available[$i]->processed = false;
  35. $this->removeEnd($removeStart, $removeEnd);
  36. $this->removeEmbeddedInRemoveSlot($removeStart, $removeEnd);
  37. }
  38. // sort by start
  39. usort($this->available, function ($item1, $item2) {
  40. return $item1->start <=> $item2->start;
  41. });
  42. $this->normalize();
  43. $this->vdump($this->available);
  44. // add to unavailable
  45. $this->unavailable[] = new TimeSlot($_start, $_end);
  46. // $this->normalizeUnavailable();
  47. }
  48. private function removeEmbeddedInAvailSlot($removeStart, $removeEnd) {
  49. for ($i=0; $i<count($this->available); $i++) {
  50. // removeStart at or after slot start
  51. // removeEnd at or before slot end
  52. // split slot into 2
  53. if ($removeStart >= $this->available[$i]->start &&
  54. $removeEnd <= $this->available[$i]->end) {
  55. $newSlot = new TimeSlot(new DateTime(), new DateTime());
  56. $newSlot->start = $removeEnd;
  57. $newSlot->end = $this->available[$i]->end;
  58. $this->available[$i]->end = $removeStart;
  59. array_splice($this->available, $i + 1, 0, [$newSlot]);
  60. return true; // nothing more to do
  61. }
  62. }
  63. return false;
  64. }
  65. private function removeEmbeddedInRemoveSlot($removeStart, $removeEnd) {
  66. $allDone = true;
  67. for ($i=0; $i<count($this->available); $i++) {
  68. // removeStart at or before slot start
  69. // removeEnd at or after slot end
  70. // remove slot
  71. if ($removeStart <= $this->available[$i]->start &&
  72. $removeEnd >= $this->available[$i]->end) {
  73. array_splice($this->available, $i, 1);
  74. $allDone = false;
  75. }
  76. }
  77. if(!$allDone) {
  78. $this->removeEmbeddedInRemoveSlot($removeStart, $removeEnd); // recurse till clean
  79. }
  80. return false;
  81. }
  82. private function removeStart($removeStart, $removeEnd) {
  83. $allDone = true;
  84. for ($i=0; $i<count($this->available); $i++) {
  85. if($this->available[$i]->processed) continue;
  86. // removeStart at or after slot start
  87. // removeStart at or before slot end
  88. // update slot to end at removeStart
  89. if($removeStart >= $this->available[$i]->start &&
  90. $removeStart <= $this->available[$i]->end) {
  91. $this->available[$i]->end = $removeStart;
  92. $this->available[$i]->processed = true;
  93. $allDone = false;
  94. break;
  95. }
  96. }
  97. if(!$allDone) {
  98. $this->removeStart($removeStart, $removeEnd); // recurse till clean
  99. }
  100. }
  101. private function removeEnd($removeStart, $removeEnd) {
  102. $allDone = true;
  103. for ($i=0; $i<count($this->available); $i++) {
  104. if($this->available[$i]->processed) continue;
  105. // removeEnd at or after slot start
  106. // removeEnd at or before slot end
  107. // update slot to start at removeEnd
  108. if($removeEnd >= $this->available[$i]->start &&
  109. $removeEnd <= $this->available[$i]->end) {
  110. $this->available[$i]->start = $removeEnd;
  111. $this->available[$i]->processed = true;
  112. $allDone = false;
  113. break;
  114. }
  115. }
  116. if(!$allDone) {
  117. $this->removeEnd($removeStart, $removeEnd); // recurse till clean
  118. }
  119. }
  120. private function normalize() {
  121. $allDone = true;
  122. for ($i=0; $i<count($this->available)-1; $i++) {
  123. if($this->available[$i]->end >= $this->available[$i+1]->start && // ends in the middle of next slot
  124. $this->available[$i]->end <= $this->available[$i+1]->end)
  125. {
  126. // extend self & delete next
  127. $this->available[$i]->end = $this->available[$i+1]->end;
  128. array_splice($this->available, $i+1, 1);
  129. $allDone = false;
  130. break;
  131. }
  132. if($this->available[$i]->end >= $this->available[$i+1]->end) // ends after next slot end
  133. {
  134. // delete next
  135. array_splice($this->available, $i+1, 1);
  136. $allDone = false;
  137. break;
  138. }
  139. if($this->available[$i]->start >= $this->available[$i]->end) // starts and ends at the same time!
  140. {
  141. // delete self
  142. array_splice($this->available, $i, 1);
  143. $allDone = false;
  144. break;
  145. }
  146. }
  147. if(!$allDone) {
  148. $this->normalize(); // recurse till clean
  149. }
  150. }
  151. private function normalizeUnavailable() {
  152. $allDone = true;
  153. for ($i=0; $i<count($this->unavailable)-1; $i++) {
  154. if($this->unavailable[$i]->end >= $this->unavailable[$i+1]->start && // ends in the middle of next slot
  155. $this->unavailable[$i]->end <= $this->unavailable[$i+1]->end)
  156. {
  157. // extend self & delete next
  158. $this->unavailable[$i]->end = $this->unavailable[$i+1]->end;
  159. array_splice($this->unavailable, $i+1, 1);
  160. $allDone = false;
  161. break;
  162. }
  163. if($this->unavailable[$i]->end >= $this->unavailable[$i+1]->end) // ends after next slot end
  164. {
  165. // delete next
  166. array_splice($this->unavailable, $i+1, 1);
  167. $allDone = false;
  168. break;
  169. }
  170. if($this->unavailable[$i]->start >= $this->unavailable[$i]->end) // starts and ends at the same time!
  171. {
  172. // delete self
  173. array_splice($this->unavailable, $i, 1);
  174. $allDone = false;
  175. break;
  176. }
  177. }
  178. if(!$allDone) {
  179. $this->normalizeUnavailable(); // recurse till clean
  180. }
  181. }
  182. public function getAvailable() {
  183. return $this->available;
  184. }
  185. public function getUnavailable() {
  186. return $this->unavailable;
  187. }
  188. public function vdump($_x) {
  189. // echo "<pre>";
  190. // print_r($_x);
  191. // echo "</pre><hr>";
  192. }
  193. }