TimeLine.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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. }
  45. private function removeEmbeddedInAvailSlot($removeStart, $removeEnd) {
  46. for ($i=0; $i<count($this->available); $i++) {
  47. // removeStart at or after slot start
  48. // removeEnd at or before slot end
  49. // split slot into 2
  50. if ($removeStart >= $this->available[$i]->start &&
  51. $removeEnd <= $this->available[$i]->end) {
  52. $newSlot = new TimeSlot(new DateTime(), new DateTime());
  53. $newSlot->start = $removeEnd;
  54. $newSlot->end = $this->available[$i]->end;
  55. $this->available[$i]->end = $removeStart;
  56. array_splice($this->available, $i + 1, 0, [$newSlot]);
  57. return true; // nothing more to do
  58. }
  59. }
  60. return false;
  61. }
  62. private function removeEmbeddedInRemoveSlot($removeStart, $removeEnd) {
  63. $allDone = true;
  64. for ($i=0; $i<count($this->available); $i++) {
  65. // removeStart at or before slot start
  66. // removeEnd at or after slot end
  67. // remove slot
  68. if ($removeStart <= $this->available[$i]->start &&
  69. $removeEnd >= $this->available[$i]->end) {
  70. array_splice($this->available, $i, 1);
  71. $allDone = false;
  72. }
  73. }
  74. if(!$allDone) {
  75. $this->removeEmbeddedInRemoveSlot($removeStart, $removeEnd); // recurse till clean
  76. }
  77. return false;
  78. }
  79. private function removeStart($removeStart, $removeEnd) {
  80. $allDone = true;
  81. for ($i=0; $i<count($this->available); $i++) {
  82. if($this->available[$i]->processed) continue;
  83. // removeStart at or after slot start
  84. // removeStart at or before slot end
  85. // update slot to end at removeStart
  86. if($removeStart >= $this->available[$i]->start &&
  87. $removeStart <= $this->available[$i]->end) {
  88. $this->available[$i]->end = $removeStart;
  89. $this->available[$i]->processed = true;
  90. $allDone = false;
  91. break;
  92. }
  93. }
  94. if(!$allDone) {
  95. $this->removeStart($removeStart, $removeEnd); // recurse till clean
  96. }
  97. }
  98. private function removeEnd($removeStart, $removeEnd) {
  99. $allDone = true;
  100. for ($i=0; $i<count($this->available); $i++) {
  101. if($this->available[$i]->processed) continue;
  102. // removeEnd at or after slot start
  103. // removeEnd at or before slot end
  104. // update slot to start at removeEnd
  105. if($removeEnd >= $this->available[$i]->start &&
  106. $removeEnd <= $this->available[$i]->end) {
  107. $this->available[$i]->start = $removeEnd;
  108. $this->available[$i]->processed = true;
  109. $allDone = false;
  110. break;
  111. }
  112. }
  113. if(!$allDone) {
  114. $this->removeEnd($removeStart, $removeEnd); // recurse till clean
  115. }
  116. }
  117. private function normalize() {
  118. $allDone = true;
  119. for ($i=0; $i<count($this->available)-1; $i++) {
  120. if($this->available[$i]->end >= $this->available[$i+1]->start && // ends in the middle of next slot
  121. $this->available[$i]->end <= $this->available[$i+1]->end)
  122. {
  123. // extend self & delete next
  124. $this->available[$i]->end = $this->available[$i+1]->end;
  125. array_splice($this->available, $i+1, 1);
  126. $allDone = false;
  127. break;
  128. }
  129. if($this->available[$i]->end >= $this->available[$i+1]->end) // ends after next slot end
  130. {
  131. // delete next
  132. array_splice($this->available, $i+1, 1);
  133. $allDone = false;
  134. break;
  135. }
  136. if($this->available[$i]->start >= $this->available[$i]->end) // starts and ends at the same time!
  137. {
  138. // delete self
  139. array_splice($this->available, $i, 1);
  140. $allDone = false;
  141. break;
  142. }
  143. }
  144. if(!$allDone) {
  145. $this->normalize(); // recurse till clean
  146. }
  147. }
  148. public function vdump($_x) {
  149. // echo "<pre>";
  150. // print_r($_x);
  151. // echo "</pre><hr>";
  152. }
  153. }