/core/Intervals.class.php

https://github.com/bgerp/bgerp · PHP · 318 lines · 180 code · 67 blank · 71 comment · 31 complexity · 140207f6acbd3b9103b6e4f3458c468d MD5 · raw file

  1. <?php
  2. /**
  3. * Помощен клас за работа с масиви от целочислени интервали
  4. * В частен случай може да се използва за времеви интервали, представени като
  5. * timestamps
  6. */
  7. class core_Intervals {
  8. /**
  9. * Масив с числовите интервали
  10. */
  11. private $data = array();
  12. /**
  13. * Добавя възможен числов интервеал. Ако началото му или краят му попада в съществуващ интервал - то те се обединяват
  14. */
  15. public function add($begin, $end)
  16. {
  17. expect($begin <= $end, $begin, $end);
  18. $new = array($begin, $end);
  19. // Бързо проверяваме дали не трябва да го сложим в края
  20. if(!count($this->data) || self::comp1($new, end($this->data)) === 1) {
  21. $this->data[] = $new;
  22. return;
  23. }
  24. // Циклим по всички интервали
  25. $last = null;
  26. $first = null;
  27. foreach($this->data as $i => $int) {
  28. $c = self::comp1($new, $int);
  29. switch($c) {
  30. case 1: // $new > $int
  31. $last = $i;
  32. break;
  33. case -1: // $new < $int
  34. $first = $i;
  35. break 2;
  36. default: // $new overlap $int
  37. expect($c === 0);
  38. $new = self::getUnion($new, $int);
  39. }
  40. }
  41. $this->data = $this->combine($last, array($new), $first);
  42. }
  43. /**
  44. * Изрязва подадения интервал време от наличните
  45. *
  46. */
  47. public function cut($begin, $end)
  48. {
  49. expect($begin <= $end, $begin, $end);
  50. $new = array($begin, $end);
  51. // Циклим по всички интервали
  52. $last = null;
  53. $first = null;
  54. $add = array();
  55. foreach($this->data as $i => $int) {
  56. $c = self::comp($new, $int);
  57. switch($c) {
  58. case 1: // $new > $int
  59. $last = $i;
  60. break;
  61. case -1: // $new < $int
  62. $first = $i;
  63. break 2;
  64. default: // $new overlap $int
  65. expect($c === 0, $c);
  66. $add = array_merge($add, self::getDiff($int, $new));
  67. }
  68. }
  69. $this->data = $this->combine($last, $add, $first);
  70. }
  71. /**
  72. * Консумира посоченият интервал, като се среми да използва само интервали между $begin и $end
  73. * Връща масив с начало на консумацията и края й, или false в случай на неуспех
  74. *
  75. * @param int $duration - продължителност в секунди
  76. * @param int|null $begin - timestamp на от коя дата или null за без такава
  77. * @param int|null$end - timestamp на до коя дата или null за без такава
  78. * @param int|null $interruptOffset - секунди, при прекъсване или null ако няма
  79. * @return array|false - масив с начална и крайна дата или false ако не може да се сметне
  80. * @throws core_exception_Expect
  81. */
  82. public function consume($duration, $begin = null, $end = null, $interruptOffset = null)
  83. {
  84. if(isset($begin) && isset($end)) {
  85. expect($begin <= $end, $begin, $end);
  86. }
  87. $last = null;
  88. $first = null;
  89. $add = array();
  90. foreach($this->data as $i => $int) {
  91. // Ако края на интервала е по-малък от началото на разрешеното - пропускаме
  92. if(isset($begin) && $begin > $int[1] ) {
  93. $last = $i;
  94. continue;
  95. }
  96. // Ако началото на интервала е по-голямо от параметъра $end то спираме цикъла
  97. if(isset($end) && $end > $int[0] || $duration == 0) {
  98. $first = $i;
  99. break;
  100. }
  101. expect($duration > 0, $duration);
  102. // Масив за консумация, която ще бъде отрязана
  103. $new = array();
  104. // От къде можем да започнем да консумираме
  105. $new[0] = max(isset($begin) ? $begin : PHP_INT_MIN, $int[0]);
  106. // До къде можем да консумираме
  107. $new[1] = min(isset($end) ? $end : PHP_INT_MAX, $int[1], $new[0] + $duration - 1);
  108. $add = array_merge($add, self::getDiff($int, $new));
  109. $min = isset($min) ? min($min, $new[0]) : $new[0];
  110. $max = isset($max) ? max($max, $new[1]) : $new[1];
  111. $duration -= $new[1] - $new[0] + 1;
  112. }
  113. $this->data = $this->combine($last, $add, $first);
  114. if(isset($min) && isset($max)) {
  115. return array($min, $max);
  116. }
  117. return false;
  118. }
  119. /**
  120. * Връща интервалите, заключени в тази рамка
  121. */
  122. public function getFrame($begin, $end)
  123. {
  124. expect($begin <= $end, $begin, $end);
  125. $new = array($begin, $end);
  126. $sect = array();
  127. foreach($this->data as $i => $int) {
  128. $sect = array_merge($sect, self::getIntersect($new, $int));
  129. }
  130. return $sect;
  131. }
  132. /**
  133. * Връща интервала, който съдържа входната точка
  134. */
  135. public function getByPoint($x)
  136. {
  137. foreach($this->data as $i => $int) {
  138. if($x >= $int[0] && $x <= $int[1]) {
  139. return $int;
  140. }
  141. }
  142. return null;
  143. }
  144. /**
  145. * Връща общата сума на продължителността на всички интервали
  146. */
  147. public function getTotalSum()
  148. {
  149. $sum = 0;
  150. foreach($this->data as $i => $int) {
  151. $sum += $int[1] - $int[0];
  152. }
  153. return $sum;
  154. }
  155. /**
  156. * Комбинира резултата
  157. */
  158. private function combine($last, $add, $first)
  159. {
  160. $res = array();
  161. // Добавяме началните интервали, ако има
  162. if(is_int($last)) {
  163. $res = array_slice($this->data, 0, $last+1);
  164. }
  165. // Добавяме новите интервали
  166. $res = array_merge($res, $add);
  167. // Добавяме останалите до края интервали
  168. if(is_int($first)) {
  169. $res = array_merge($res, array_slice($this->data, $first));
  170. }
  171. return $res;
  172. }
  173. /**
  174. * Връща разликата между интервалите
  175. * @param array $a Основен интервал
  176. * @param array $b Интервал, който ще се премахне
  177. *
  178. * @return array
  179. */
  180. public static function getDiff($a, $b, $minLength = null)
  181. {
  182. $diff = array();
  183. if($b[0] > $a[0] && $b[1] < $a[1]) {
  184. if(!isset($minLength) || ($b[0] - $a[0]) >= $minLength) {
  185. $diff[] = array($a[0], $b[0]-1);
  186. }
  187. if(!isset($minLength) || ($a[1] - $b[1]) >= $minLength) {
  188. $diff[] = array($b[1]+1, $a[1]);
  189. }
  190. } elseif($b[0] <= $a[0] && $b[1] < $a[1]) {
  191. if(!isset($minLength) || ($a[1] - $b[1]) >= $minLength) {
  192. $diff[] = array($b[1]+1, $a[1]);
  193. }
  194. } elseif($b[0] > $a[0] && $b[1] >= $a[1]) {
  195. if(!isset($minLength) || ($b[0] - $a[0]) >= $minLength) {
  196. $diff[] = array($a[0], $b[0]-1);
  197. }
  198. } else {
  199. expect($b[0] <= $a[0] && $b[1] >= $a[1], $b, $a);
  200. }
  201. return $diff;
  202. }
  203. /**
  204. * Намира сечението между два интервала
  205. */
  206. public static function getIntersect($a, $b)
  207. {
  208. $res = array();
  209. $max = max($a[0], $b[0]);
  210. $min = min($a[1], $b[1]);
  211. if($max <= $min) {
  212. $res[] = array($max, $min);
  213. }
  214. return $res;
  215. }
  216. /**
  217. * Намира обедиднението между два интервала
  218. */
  219. public static function getUnion($a, $b)
  220. {
  221. $res = array();
  222. $min = min($a[0], $b[0]);
  223. $max = max($a[1], $b[1]);
  224. $res = array($min, $max);
  225. return $res;
  226. }
  227. /**
  228. * 1 ако $b > $a, -1 ako $a > $b, 0 ако се пресичат
  229. */
  230. public static function comp($a, $b)
  231. {
  232. return ($b[1] < $a[0]) - ($a[1] < $b[0]);
  233. }
  234. /**
  235. * Същото, но дава пресичане и само при докосване на интервалите
  236. */
  237. public function comp1($a, $b)
  238. {
  239. return ($b[1] < $a[0] - 1) - ($a[1] < $b[0] + 1);
  240. }
  241. /**
  242. * Връща масива с интервалите, като преобразува числата към дати, третирайки ги като timestamps
  243. */
  244. public function getDates()
  245. {
  246. foreach($this->data as $i => $int) {
  247. $res[$i] = array(dt::timestamp2Mysql($int[0]), dt::timestamp2Mysql($int[1]));
  248. }
  249. return $res;
  250. }
  251. }