Parser.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. /**
  3. * @author: Jan Konieczny <jkonieczny@gmail.com>, group@hyperf.io
  4. * @license: http://www.gnu.org/licenses/
  5. * @license: https://github.com/hyperf/hyperf/blob/master/LICENSE
  6. *
  7. * This is a simple script to parse crontab syntax to get the execution time
  8. *
  9. * Eg.: $timestamp = Crontab::parse('12 * * * 1-5');
  10. *
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. */
  25. /**
  26. * Provides basic cron syntax parsing functionality
  27. *
  28. * @author: Jan Konieczny <jkonieczny@gmail.com>, group@hyperf.io
  29. * @license: http://www.gnu.org/licenses/
  30. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  31. */
  32. namespace Workerman\Crontab;
  33. /**
  34. * Class Parser
  35. * @package Workerman\Crontab
  36. */
  37. class Parser
  38. {
  39. /**
  40. * Finds next execution time(stamp) parsin crontab syntax.
  41. *
  42. * @param string $crontab_string :
  43. * 0 1 2 3 4 5
  44. * * * * * * *
  45. * - - - - - -
  46. * | | | | | |
  47. * | | | | | +----- day of week (0 - 6) (Sunday=0)
  48. * | | | | +----- month (1 - 12)
  49. * | | | +------- day of month (1 - 31)
  50. * | | +--------- hour (0 - 23)
  51. * | +----------- min (0 - 59)
  52. * +------------- sec (0-59)
  53. *
  54. * @param null|int $start_time
  55. * @throws \InvalidArgumentException
  56. * @return int[]
  57. */
  58. public function parse($crontab_string, $start_time = null)
  59. {
  60. if (! $this->isValid($crontab_string)) {
  61. throw new \InvalidArgumentException('Invalid cron string: ' . $crontab_string);
  62. }
  63. $start_time = $start_time ? $start_time : time();
  64. $date = $this->parseDate($crontab_string);
  65. if (in_array((int) date('i', $start_time), $date['minutes'])
  66. && in_array((int) date('G', $start_time), $date['hours'])
  67. && in_array((int) date('j', $start_time), $date['day'])
  68. && in_array((int) date('w', $start_time), $date['week'])
  69. && in_array((int) date('n', $start_time), $date['month'])
  70. ) {
  71. $result = [];
  72. foreach ($date['second'] as $second) {
  73. $result[] = $start_time + $second;
  74. }
  75. return $result;
  76. }
  77. return [];
  78. }
  79. public function isValid(string $crontab_string): bool
  80. {
  81. if (! preg_match('/^((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)$/i', trim($crontab_string))) {
  82. if (! preg_match('/^((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)\s+((\*(\/[0-9]+)?)|[0-9\-\,\/]+)$/i', trim($crontab_string))) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. }
  88. /**
  89. * Parse each segment of crontab string.
  90. */
  91. protected function parseSegment(string $string, int $min, int $max, ?int $start = null)
  92. {
  93. if ($start === null || $start < $min) {
  94. $start = $min;
  95. }
  96. $result = [];
  97. if ($string === '*') {
  98. for ($i = $start; $i <= $max; ++$i) {
  99. $result[] = $i;
  100. }
  101. } elseif (strpos($string, ',') !== false) {
  102. $exploded = explode(',', $string);
  103. foreach ($exploded as $value) {
  104. if (strpos($value, '/') !== false || strpos($string, '-') !== false) {
  105. $result = array_merge($result, $this->parseSegment($value, $min, $max, $start));
  106. continue;
  107. }
  108. if (trim($value) === '' || ! $this->between((int) $value, (int) ($min > $start ? $min : $start), (int) $max)) {
  109. continue;
  110. }
  111. $result[] = (int) $value;
  112. }
  113. } elseif (strpos($string, '/') !== false) {
  114. $exploded = explode('/', $string);
  115. if (strpos($exploded[0], '-') !== false) {
  116. [$nMin, $nMax] = explode('-', $exploded[0]);
  117. $nMin > $min && $min = (int) $nMin;
  118. $nMax < $max && $max = (int) $nMax;
  119. }
  120. $start < $min && $start = $min;
  121. for ($i = $start; $i <= $max;) {
  122. $result[] = $i;
  123. $i += $exploded[1];
  124. }
  125. } elseif (strpos($string, '-') !== false) {
  126. $result = array_merge($result, $this->parseSegment($string . '/1', $min, $max, $start));
  127. } elseif ($this->between((int) $string, $min > $start ? $min : $start, $max)) {
  128. $result[] = (int) $string;
  129. }
  130. return $result;
  131. }
  132. /**
  133. * Determire if the $value is between in $min and $max ?
  134. */
  135. private function between(int $value, int $min, int $max): bool
  136. {
  137. return $value >= $min && $value <= $max;
  138. }
  139. private function parseDate(string $crontab_string): array
  140. {
  141. $cron = preg_split('/[\\s]+/i', trim($crontab_string));
  142. if (count($cron) == 6) {
  143. $date = [
  144. 'second' => $this->parseSegment($cron[0], 0, 59),
  145. 'minutes' => $this->parseSegment($cron[1], 0, 59),
  146. 'hours' => $this->parseSegment($cron[2], 0, 23),
  147. 'day' => $this->parseSegment($cron[3], 1, 31),
  148. 'month' => $this->parseSegment($cron[4], 1, 12),
  149. 'week' => $this->parseSegment($cron[5], 0, 6),
  150. ];
  151. } else {
  152. $date = [
  153. 'second' => [1 => 0],
  154. 'minutes' => $this->parseSegment($cron[0], 0, 59),
  155. 'hours' => $this->parseSegment($cron[1], 0, 23),
  156. 'day' => $this->parseSegment($cron[2], 1, 31),
  157. 'month' => $this->parseSegment($cron[3], 1, 12),
  158. 'week' => $this->parseSegment($cron[4], 0, 6),
  159. ];
  160. }
  161. return $date;
  162. }
  163. }