/src/crontab/src/Parser.php

https://github.com/hyperf/hyperf · PHP · 156 lines · 112 code · 8 blank · 36 comment · 24 complexity · af0d8a18c9c7e2c72eec7d6792e6a9d2 MD5 · raw file

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Crontab;
  12. use Carbon\Carbon;
  13. class Parser
  14. {
  15. /**
  16. * 解析crontab的定时格式,linux只支持到分钟/,这个类支持到秒.
  17. *
  18. * @param string $crontabString :
  19. * 0 1 2 3 4 5
  20. * * * * * * *
  21. * - - - - - -
  22. * | | | | | |
  23. * | | | | | +----- day of week (0 - 6) (Sunday=0)
  24. * | | | | +----- month (1 - 12)
  25. * | | | +------- day of month (1 - 31)
  26. * | | +--------- hour (0 - 23)
  27. * | +----------- min (0 - 59)
  28. * +------------- sec (0-59)
  29. * @param null|Carbon|int $startTime
  30. * @throws \InvalidArgumentException
  31. * @return Carbon[]
  32. */
  33. public function parse(string $crontabString, $startTime = null)
  34. {
  35. if (! $this->isValid($crontabString)) {
  36. throw new \InvalidArgumentException('Invalid cron string: ' . $crontabString);
  37. }
  38. $startTime = $this->parseStartTime($startTime);
  39. $date = $this->parseDate($crontabString);
  40. if (in_array((int) date('i', $startTime), $date['minutes'])
  41. && in_array((int) date('G', $startTime), $date['hours'])
  42. && in_array((int) date('j', $startTime), $date['day'])
  43. && in_array((int) date('w', $startTime), $date['week'])
  44. && in_array((int) date('n', $startTime), $date['month'])
  45. ) {
  46. $result = [];
  47. foreach ($date['second'] as $second) {
  48. $result[] = Carbon::createFromTimestamp($startTime + $second);
  49. }
  50. return $result;
  51. }
  52. return [];
  53. }
  54. public function isValid(string $crontabString): bool
  55. {
  56. 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($crontabString))) {
  57. 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($crontabString))) {
  58. return false;
  59. }
  60. }
  61. return true;
  62. }
  63. /**
  64. * Parse each segment of crontab string.
  65. */
  66. protected function parseSegment(string $string, int $min, int $max, int $start = null)
  67. {
  68. if ($start === null || $start < $min) {
  69. $start = $min;
  70. }
  71. $result = [];
  72. if ($string === '*') {
  73. for ($i = $start; $i <= $max; ++$i) {
  74. $result[] = $i;
  75. }
  76. } elseif (strpos($string, ',') !== false) {
  77. $exploded = explode(',', $string);
  78. foreach ($exploded as $value) {
  79. if (! $this->between((int) $value, (int) ($min > $start ? $min : $start), (int) $max)) {
  80. continue;
  81. }
  82. $result[] = (int) $value;
  83. }
  84. } elseif (strpos($string, '/') !== false) {
  85. $exploded = explode('/', $string);
  86. if (strpos($exploded[0], '-') !== false) {
  87. [$nMin, $nMax] = explode('-', $exploded[0]);
  88. $nMin > $min && $min = (int) $nMin;
  89. $nMax < $max && $max = (int) $nMax;
  90. }
  91. // If the value of start is larger than the value of min, the value of start should equal with the value of min.
  92. $start < $min && $start = $min;
  93. for ($i = $start; $i <= $max;) {
  94. $result[] = $i;
  95. $i += $exploded[1];
  96. }
  97. } elseif ($this->between((int) $string, $min > $start ? $min : $start, $max)) {
  98. $result[] = (int) $string;
  99. }
  100. return $result;
  101. }
  102. /**
  103. * Determire if the $value is between in $min and $max ?
  104. */
  105. private function between(int $value, int $min, int $max): bool
  106. {
  107. return $value >= $min && $value <= $max;
  108. }
  109. /**
  110. * @param null|Carbon|int $startTime
  111. */
  112. private function parseStartTime($startTime): int
  113. {
  114. if ($startTime instanceof Carbon) {
  115. $startTime = $startTime->getTimestamp();
  116. } elseif ($startTime === null) {
  117. $startTime = time();
  118. }
  119. if (! is_numeric($startTime)) {
  120. throw new \InvalidArgumentException("\$startTime have to be a valid unix timestamp ({$startTime} given)");
  121. }
  122. return (int) $startTime;
  123. }
  124. private function parseDate(string $crontabString): array
  125. {
  126. $cron = preg_split('/[\\s]+/i', trim($crontabString));
  127. if (count($cron) == 6) {
  128. $date = [
  129. 'second' => $this->parseSegment($cron[0], 0, 59),
  130. 'minutes' => $this->parseSegment($cron[1], 0, 59),
  131. 'hours' => $this->parseSegment($cron[2], 0, 23),
  132. 'day' => $this->parseSegment($cron[3], 1, 31),
  133. 'month' => $this->parseSegment($cron[4], 1, 12),
  134. 'week' => $this->parseSegment($cron[5], 0, 6),
  135. ];
  136. } else {
  137. $date = [
  138. 'second' => [1 => 0],
  139. 'minutes' => $this->parseSegment($cron[0], 0, 59),
  140. 'hours' => $this->parseSegment($cron[1], 0, 23),
  141. 'day' => $this->parseSegment($cron[2], 1, 31),
  142. 'month' => $this->parseSegment($cron[3], 1, 12),
  143. 'week' => $this->parseSegment($cron[4], 0, 6),
  144. ];
  145. }
  146. return $date;
  147. }
  148. }