elysia_cron_scheduler.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. function elysia_cron_should_run($conf, $now = -1, $ignore_disable = false, $ignore_time = false) {
  3. $prev_rule_run = 0; // What time SHOULD the job be executed last time
  4. if (!$ignore_disable && $conf['disabled']) {
  5. return false;
  6. }
  7. if ($ignore_time) {
  8. return true;
  9. }
  10. if ($now < 0) {
  11. $now = time();
  12. }
  13. if ((!$conf['last_run']) || ($now - $conf['last_run'] > 365 * 86400)) {
  14. return true;
  15. }
  16. $next_run = _elysia_cron_next_run($conf);
  17. return $now >= $next_run;
  18. }
  19. function _elysia_cron_next_run($conf) {
  20. if (!isset($conf['rule'])) {
  21. return false;
  22. }
  23. $ranges = array(
  24. array(0, 59),
  25. array(0, 23),
  26. array(1, 31), // TODO
  27. array(1, 12),
  28. array(0, 3000),
  29. array(0, 6),
  30. );
  31. if (!preg_match('/^([0-9*,\/-]+)[ ]+([0-9*,\/-]+)[ ]+([0-9*,\/-]+)[ ]+([0-9*,\/-]+)[ ]+([0-9*,\/-]+)$/', $conf['rule'], $rules)) {
  32. elysia_cron_warning('Invalid rule found: %rule', array('%rule' => $conf['rule']));
  33. return false;
  34. }
  35. $rule = array($rules[1], $rules[2], array($rules[3], $rules[5]), $rules[4]);
  36. $ruledec = array();
  37. $date = __cronDecodeDate($conf['last_run'], 1);
  38. $expected_date = __cronDecodeDate(!empty($conf['last_run_expected']) ? $conf['last_run_expected'] : 0);
  39. for ($i = 0; $i < 4; $i++) {
  40. if ($i != 2) {
  41. // Standard scheme for mins, hours, month
  42. $ruledec[$i] = __cronDecodeRule($rule[$i], $ranges[$i][0], $ranges[$i][1]);
  43. } else {
  44. // For mday+week we follow another scheme
  45. $ruledec[$i] = __cronDecodeRuleMday($rule[2], $date[3], $date[4]);
  46. }
  47. $r = $ruledec[$i];
  48. $new = $date[$i];
  49. if ($r['d']) {
  50. if ($expected_date[$i] > $date[$i]) {
  51. $expected_date[$i] -= $ranges[$i][1] + 1;
  52. }
  53. $new = $expected_date[$i] + ceil(($date[$i] - $expected_date[$i]) / $r['d']) * $r['d'];
  54. }
  55. elseif ($r['n']) {
  56. $new = __cronNextOrEqual($date[$i], $r['n'], $ranges[$i][0], $ranges[$i][1]);
  57. }
  58. if ($new != $date[$i]) {
  59. $date[$i] = $new;
  60. if ($date[$i] > $ranges[$i][1]) {
  61. $date[$i + 1]++;
  62. $date[$i] = $ranges[$i][0] + $date[$i] - $ranges[$i][1] - 1;
  63. }
  64. for ($j = 0; $j < $i; $j++) {
  65. if ($j == 2) {
  66. // For mday+week decoded rule could be changed (by month+year)
  67. $ruledec[$j] = __cronDecodeRuleMday($rule[2], $date[3], $date[4]);
  68. }
  69. $date[$j] = $ruledec[$j]['d'] ? ($ranges[$j][0] == 0 ? 0 : $ruledec[$j]['d']) : ($ruledec[$j]['n'] ? reset($ruledec[$j]['n']) : $ranges[$j][0]);
  70. $expected_date[$j] = 0;
  71. }
  72. }
  73. }
  74. return __cronEncodeDate($date);
  75. }
  76. function __cronDecodeDate($timestamp, $min_diff = 0) {
  77. $time = floor($timestamp / 60);
  78. $time += $min_diff;
  79. $date = $time ? getdate($time * 60) : 0;
  80. $date = array(
  81. $time ? $date['minutes'] : 0,
  82. $time ? $date['hours'] : 0,
  83. $time ? $date['mday'] : 0,
  84. $time ? $date['mon'] : 0,
  85. $time ? $date['year'] : 0,
  86. );
  87. return $date;
  88. }
  89. function __cronEncodeDate($date) {
  90. return mktime($date[1], $date[0], 0, $date[3], $date[2], $date[4]);
  91. }
  92. function __cronNextOrEqual($el, $arr, $range_start, $range_end) {
  93. if (empty($arr)) {
  94. return $el;
  95. }
  96. foreach ($arr as $x) {
  97. if ($x >= $el) {
  98. return $x;
  99. }
  100. }
  101. return $range_end + reset($arr) + 1 - $range_start;
  102. }
  103. function __cronDecodeRule($rule, $min, $max) {
  104. if ($rule == '*') {
  105. return array('n' => array(), 'd' => 0);
  106. }
  107. $result = array('n' => array(), 'd' => 0);
  108. foreach (explode(',', $rule) as $token) {
  109. if (preg_match('/^([0-9]+)-([0-9]+)$/', $token, $r)) {
  110. $result['n'] = array_merge($result['n'], range($r[1], $r[2]));
  111. }
  112. elseif (preg_match('/^\*\/([0-9]+)$/', $token, $r)) {
  113. $result['d'] = $r[1];
  114. }
  115. elseif (is_numeric($token)) {
  116. $result['n'][] = $token;
  117. }
  118. }
  119. sort($result['n']);
  120. return $result;
  121. }
  122. function __cronDecodeRuleMday($rule, $month, $year) {
  123. $range_from = 1;
  124. $range_to = $month != 2 ? (in_array($month, array(4,6,9,11)) ? 30 : 31) : ($year % 4 == 0 ? 29 : 28);
  125. $r1 = __cronDecodeRule($rule[0], $range_from, $range_to);
  126. $r2 = __cronDecodeRule($rule[1], $range_from, $range_to);
  127. if ($r2['d']) {
  128. for ($i = 0; $i < 7; $i++) {
  129. if ($i % $r2['d'] == 0) {
  130. $r2['n'][] = $i;
  131. }
  132. }
  133. }
  134. if ($r2['n']) {
  135. $r2['n'] = array_unique($r2['n']);
  136. $r1['n'] = array_merge($r1['n'], __cronMonDaysFromWeekDays($year, $month, $r2['n']), __cronMonDaysFromWeekDays($year, $month + 1, $r2['n'], 31)); // Use always "31" and not $range_to, see http://drupal.org/node/1668302
  137. }
  138. return $r1;
  139. }
  140. // Used by elysia_cron_should_run
  141. function __cronMonDaysFromWeekDays($year, $mon, $weekdays, $offset = 0) {
  142. if ($mon > 12) {
  143. $year ++;
  144. $mon = $mon - 12;
  145. }
  146. $result = array();
  147. for ($i = 1; checkdate($mon, $i, $year); $i++) {
  148. $w = date('w', mktime(12, 00, 00, $mon, $i, $year));
  149. if (in_array($w, $weekdays)) {
  150. $result[] = $i + $offset;
  151. }
  152. }
  153. return $result;
  154. }
  155. /*******************************************************************************
  156. * TESTS
  157. ******************************************************************************/
  158. function test_elysia_cron_should_run() {
  159. dprint("Start test");
  160. $start = microtime(true);
  161. //mktime: hr min sec mon day yr
  162. dprint(" 1." . (false == elysia_cron_should_run(array('rule' => '0 12 * * *', 'last_run' => mktime(12, 0, 0, 1, 2, 2008)), mktime(12, 01, 0, 1, 2, 2008))));
  163. dprint(" 2." . (false == elysia_cron_should_run(array('rule' => '0 12 * * *', 'last_run' => mktime(12, 0, 0, 1, 2, 2008)), mktime(15, 00, 0, 1, 2, 2008))));
  164. dprint(" 3." . (false == elysia_cron_should_run(array('rule' => '0 12 * * *', 'last_run' => mktime(12, 0, 0, 1, 2, 2008)), mktime(11, 59, 0, 1, 3, 2008))));
  165. dprint(" 4." . (true == elysia_cron_should_run(array('rule' => '0 12 * * *', 'last_run' => mktime(12, 0, 0, 1, 2, 2008)), mktime(12, 00, 0, 1, 3, 2008))));
  166. dprint(" 5." . (false == elysia_cron_should_run(array('rule' => '59 23 * * *', 'last_run' => mktime(23, 59, 0, 1, 2, 2008)), mktime(0, 00, 0, 1, 3, 2008))));
  167. dprint(" 6." . (true == elysia_cron_should_run(array('rule' => '59 23 * * *', 'last_run' => mktime(23, 59, 0, 1, 2, 2008)), mktime(23, 59, 0, 1, 3, 2008))));
  168. dprint(" 7." . (true == elysia_cron_should_run(array('rule' => '59 23 * * *', 'last_run' => mktime(23, 59, 0, 1, 2, 2008)), mktime(0, 00, 0, 1, 4, 2008))));
  169. dprint(" 8." . (true == elysia_cron_should_run(array('rule' => '59 23 * * *', 'last_run' => mktime(23, 58, 0, 1, 2, 2008)), mktime(23, 59, 0, 1, 2, 2008))));
  170. dprint(" 9." . (true == elysia_cron_should_run(array('rule' => '59 23 * * *', 'last_run' => mktime(23, 58, 0, 1, 2, 2008)), mktime(0, 0, 0, 1, 3, 2008))));
  171. dprint("10." . (false == elysia_cron_should_run(array('rule' => '59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 5, 2008))));
  172. dprint("11." . (false == elysia_cron_should_run(array('rule' => '59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(0, 0, 0, 1, 6, 2008))));
  173. dprint("12." . (true == elysia_cron_should_run(array('rule' => '59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 6, 2008))));
  174. dprint("13." . (true == elysia_cron_should_run(array('rule' => '59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(00, 00, 0, 1, 7, 2008))));
  175. dprint("14." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 29, 0, 1, 6, 2008))));
  176. dprint("15." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 6, 2008))));
  177. dprint("16." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 5, 2008))));
  178. dprint("17." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 58, 0, 1, 6, 2008))));
  179. dprint("18." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 58, 0, 1, 5, 2008)), mktime(23, 28, 0, 1, 6, 2008))));
  180. dprint("19." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 28, 0, 1, 5, 2008)), mktime(23, 29, 0, 1, 5, 2008))));
  181. dprint("20." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 28, 0, 1, 5, 2008)), mktime(23, 30, 0, 1, 5, 2008))));
  182. dprint("21." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 28, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 5, 2008))));
  183. dprint("22." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 0', 'last_run' => mktime(23, 28, 0, 1, 5, 2008)), mktime(23, 29, 0, 1, 6, 2008))));
  184. dprint("23." . (false == elysia_cron_should_run(array('rule' => '29,59 23 * * 5', 'last_run' => mktime(23, 59, 0, 2, 22, 2008)), mktime(23, 59, 0, 2, 28, 2008))));
  185. dprint("24." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 5', 'last_run' => mktime(23, 59, 0, 2, 22, 2008)), mktime(23, 59, 0, 2, 29, 2008))));
  186. dprint("25." . (true == elysia_cron_should_run(array('rule' => '29,59 23 * * 5', 'last_run' => mktime(23, 59, 0, 2, 22, 2008)), mktime(0, 0, 0, 3, 1, 2008))));
  187. dprint("26." . (false == elysia_cron_should_run(array('rule' => '59 23 * * 3', 'last_run' => mktime(23, 59, 0, 12, 31, 2008)), mktime(0, 0, 0, 1, 1, 2009))));
  188. dprint("27." . (false == elysia_cron_should_run(array('rule' => '59 23 * * 3', 'last_run' => mktime(23, 59, 0, 12, 31, 2008)), mktime(0, 0, 0, 1, 7, 2009))));
  189. dprint("28." . (true == elysia_cron_should_run(array('rule' => '59 23 * * 3', 'last_run' => mktime(23, 59, 0, 12, 31, 2008)), mktime(23, 59, 0, 1, 7, 2009))));
  190. dprint("29." . (true == elysia_cron_should_run(array('rule' => '59 23 * 2 5', 'last_run' => mktime(23, 59, 0, 2, 22, 2008)), mktime(23, 59, 0, 2, 29, 2008))));
  191. dprint("30." . (true == elysia_cron_should_run(array('rule' => '59 23 * 2 5', 'last_run' => mktime(23, 59, 0, 2, 22, 2008)), mktime(0, 0, 0, 3, 1, 2008))));
  192. dprint("31." . (false == elysia_cron_should_run(array('rule' => '59 23 * 2 5', 'last_run' => mktime(23, 59, 0, 2, 29, 2008)), mktime(23, 59, 0, 3, 7, 2008))));
  193. dprint("32." . (false == elysia_cron_should_run(array('rule' => '59 23 * 2 5', 'last_run' => mktime(23, 59, 0, 2, 29, 2008)), mktime(23, 58, 0, 2, 6, 2009))));
  194. dprint("33." . (true == elysia_cron_should_run(array('rule' => '59 23 * 2 5', 'last_run' => mktime(23, 59, 0, 2, 29, 2008)), mktime(23, 59, 0, 2, 6, 2009))));
  195. dprint("34." . (true == elysia_cron_should_run(array('rule' => '59 23 *' . '/10 * *', 'last_run' => mktime(23, 58, 0, 1, 10, 2008)), mktime(23, 59, 0, 1, 10, 2008))));
  196. dprint("35." . (false == elysia_cron_should_run(array('rule' => '59 23 *' . '/10 * *', 'last_run' => mktime(23, 59, 0, 1, 10, 2008)), mktime(23, 59, 0, 1, 11, 2008))));
  197. dprint("36." . (true == elysia_cron_should_run(array('rule' => '59 23 *' . '/10 * *', 'last_run' => mktime(23, 59, 0, 1, 10, 2008)), mktime(23, 59, 0, 1, 20, 2008))));
  198. dprint("37." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 4, 2008)), mktime(23, 59, 0, 1, 5, 2008))));
  199. dprint("38." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 4, 2008)), mktime(23, 59, 0, 1, 6, 2008))));
  200. dprint("39." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 6, 2008))));
  201. dprint("40." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 5, 2008)), mktime(23, 58, 0, 1, 10, 2008))));
  202. dprint("41." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 10, 2008))));
  203. dprint("42." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5,10-15 * *', 'last_run' => mktime(23, 59, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 16, 2008))));
  204. dprint("43." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 1, 4, 2008)), mktime(23, 59, 0, 1, 5, 2008))));
  205. dprint("44." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 1, 5, 2008)), mktime(23, 59, 0, 1, 6, 2008))));
  206. dprint("45." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 1, 6, 2008)), mktime(23, 59, 0, 1, 7, 2008))));
  207. dprint("46." . (true == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 1, 6, 2008)), mktime(23, 59, 0, 1, 13, 2008))));
  208. dprint("47." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 2, 4, 2008)), mktime(23, 59, 0, 2, 5, 2008))));
  209. dprint("48." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 2, 5, 2008)), mktime(23, 59, 0, 2, 10, 2008))));
  210. dprint("49." . (false == elysia_cron_should_run(array('rule' => '59 23 1-5 1 0', 'last_run' => mktime(23, 59, 0, 2, 10, 2008)), mktime(23, 59, 0, 2, 17, 2008))));
  211. dprint("49." . (true == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(8, 58, 0, 2, 10, 2008)), mktime(8, 59, 0, 2, 10, 2008))));
  212. dprint("50." . (false == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(8, 59, 0, 2, 10, 2008)), mktime(9, 00, 0, 2, 10, 2008))));
  213. dprint("51." . (false == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(8, 59, 0, 2, 10, 2008)), mktime(17, 59, 0, 2, 10, 2008))));
  214. dprint("52." . (true == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(8, 59, 0, 2, 10, 2008)), mktime(18, 00, 0, 2, 10, 2008))));
  215. dprint("53." . (true == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(18, 00, 0, 2, 10, 2008)), mktime(18, 01, 0, 2, 10, 2008))));
  216. dprint("54." . (true == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(18, 00, 0, 2, 10, 2008)), mktime(19, 0, 0, 2, 10, 2008))));
  217. dprint("55." . (true == elysia_cron_should_run(array('rule' => '* 0,1,2,3,4,5,6,7,8,18,19,20,21,22,23 * * *', 'last_run' => mktime(18, 00, 0, 2, 10, 2008)), mktime(9, 0, 0, 3, 10, 2008))));
  218. dprint("56." . (true == elysia_cron_should_run(array('rule' => '* * * * *', 'last_run' => mktime(18, 00, 0, 2, 10, 2008)), mktime(18, 01, 0, 2, 10, 2008))));
  219. dprint("End test (" . (microtime(true) - $start) . ")");
  220. }
  221. // Remove comment to run tests
  222. //test_elysia_cron_should_run();die();