123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- <?php
- /**
- * @package Grav\Common\Scheduler
- * @author Originally based on jqCron by Arnaud Buathier <arnaud@arnapou.net> modified for Grav integration
- * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Common\Scheduler;
- /*
- * Usage examples :
- * ----------------
- *
- * $cron = new Cron('10-30/5 12 * * *');
- *
- * var_dump($cron->getMinutes());
- * // array(5) {
- * // [0]=> int(10)
- * // [1]=> int(15)
- * // [2]=> int(20)
- * // [3]=> int(25)
- * // [4]=> int(30)
- * // }
- *
- * var_dump($cron->getText('fr'));
- * // string(32) "Chaque jour à 12:10,15,20,25,30"
- *
- * var_dump($cron->getText('en'));
- * // string(30) "Every day at 12:10,15,20,25,30"
- *
- * var_dump($cron->getType());
- * // string(3) "day"
- *
- * var_dump($cron->getCronHours());
- * // string(2) "12"
- *
- * var_dump($cron->matchExact(new \DateTime('2012-07-01 13:25:10')));
- * // bool(false)
- *
- * var_dump($cron->matchExact(new \DateTime('2012-07-01 12:15:20')));
- * // bool(true)
- *
- * var_dump($cron->matchWithMargin(new \DateTime('2012-07-01 12:32:50'), -3, 5));
- * // bool(true)
- */
- class Cron
- {
- public const TYPE_UNDEFINED = '';
- public const TYPE_MINUTE = 'minute';
- public const TYPE_HOUR = 'hour';
- public const TYPE_DAY = 'day';
- public const TYPE_WEEK = 'week';
- public const TYPE_MONTH = 'month';
- public const TYPE_YEAR = 'year';
- /**
- *
- * @var array
- */
- protected $texts = [
- 'fr' => [
- 'empty' => '-tout-',
- 'name_minute' => 'minute',
- 'name_hour' => 'heure',
- 'name_day' => 'jour',
- 'name_week' => 'semaine',
- 'name_month' => 'mois',
- 'name_year' => 'année',
- 'text_period' => 'Chaque %s',
- 'text_mins' => 'à %s minutes',
- 'text_time' => 'à %s:%s',
- 'text_dow' => 'le %s',
- 'text_month' => 'de %s',
- 'text_dom' => 'le %s',
- 'weekdays' => ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'],
- 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
- ],
- 'en' => [
- 'empty' => '-all-',
- 'name_minute' => 'minute',
- 'name_hour' => 'hour',
- 'name_day' => 'day',
- 'name_week' => 'week',
- 'name_month' => 'month',
- 'name_year' => 'year',
- 'text_period' => 'Every %s',
- 'text_mins' => 'at %s minutes past the hour',
- 'text_time' => 'at %s:%s',
- 'text_dow' => 'on %s',
- 'text_month' => 'of %s',
- 'text_dom' => 'on the %s',
- 'weekdays' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
- 'months' => ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'],
- ],
- ];
- /**
- * min hour dom month dow
- * @var string
- */
- protected $cron = '';
- /**
- *
- * @var array
- */
- protected $minutes = [];
- /**
- *
- * @var array
- */
- protected $hours = [];
- /**
- *
- * @var array
- */
- protected $months = [];
- /**
- * 0-7 : sunday, monday, ... saturday, sunday
- * @var array
- */
- protected $dow = [];
- /**
- *
- * @var array
- */
- protected $dom = [];
- /**
- *
- * @param string|null $cron
- */
- public function __construct($cron = null)
- {
- if (null !== $cron) {
- $this->setCron($cron);
- }
- }
- /**
- *
- * @return string
- */
- public function getCron()
- {
- return implode(' ', [
- $this->getCronMinutes(),
- $this->getCronHours(),
- $this->getCronDaysOfMonth(),
- $this->getCronMonths(),
- $this->getCronDaysOfWeek(),
- ]);
- }
- /**
- *
- * @param string $lang 'fr' or 'en'
- * @return string
- */
- public function getText($lang)
- {
- // check lang
- if (!isset($this->texts[$lang])) {
- return $this->getCron();
- }
- $texts = $this->texts[$lang];
- // check type
- $type = $this->getType();
- if ($type === self::TYPE_UNDEFINED) {
- return $this->getCron();
- }
- // init
- $elements = [];
- $elements[] = sprintf($texts['text_period'], $texts['name_' . $type]);
- // hour
- if ($type === self::TYPE_HOUR) {
- $elements[] = sprintf($texts['text_mins'], $this->getCronMinutes());
- }
- // week
- if ($type === self::TYPE_WEEK) {
- $dow = $this->getCronDaysOfWeek();
- foreach ($texts['weekdays'] as $i => $wd) {
- $dow = str_replace((string) ($i + 1), $wd, $dow);
- }
- $elements[] = sprintf($texts['text_dow'], $dow);
- }
- // month + year
- if (\in_array($type, [self::TYPE_MONTH, self::TYPE_YEAR], true)) {
- $elements[] = sprintf($texts['text_dom'], $this->getCronDaysOfMonth());
- }
- // year
- if ($type === self::TYPE_YEAR) {
- $months = $this->getCronMonths();
- for ($i = count($texts['months']) - 1; $i >= 0; $i--) {
- $months = str_replace((string) ($i + 1), $texts['months'][$i], $months);
- }
- $elements[] = sprintf($texts['text_month'], $months);
- }
- // day + week + month + year
- if (\in_array($type, [self::TYPE_DAY, self::TYPE_WEEK, self::TYPE_MONTH, self::TYPE_YEAR], true)) {
- $elements[] = sprintf($texts['text_time'], $this->getCronHours(), $this->getCronMinutes());
- }
- return str_replace('*', $texts['empty'], implode(' ', $elements));
- }
- /**
- *
- * @return string
- */
- public function getType()
- {
- $mask = preg_replace('/[^\* ]/', '-', $this->getCron());
- $mask = preg_replace('/-+/', '-', $mask);
- $mask = preg_replace('/[^-\*]/', '', $mask);
- if ($mask === '*****') {
- return self::TYPE_MINUTE;
- }
- if ($mask === '-****') {
- return self::TYPE_HOUR;
- }
- if (substr($mask, -3) === '***') {
- return self::TYPE_DAY;
- }
- if (substr($mask, -3) === '-**') {
- return self::TYPE_MONTH;
- }
- if (substr($mask, -3) === '**-') {
- return self::TYPE_WEEK;
- }
- if (substr($mask, -2) === '-*') {
- return self::TYPE_YEAR;
- }
- return self::TYPE_UNDEFINED;
- }
- /**
- *
- * @param string $cron
- * @return Cron
- */
- public function setCron($cron)
- {
- // sanitize
- $cron = trim($cron);
- $cron = preg_replace('/\s+/', ' ', $cron);
- // explode
- $elements = explode(' ', $cron);
- if (\count($elements) !== 5) {
- throw new \RuntimeException('Bad number of elements');
- }
- $this->cron = $cron;
- $this->setMinutes($elements[0]);
- $this->setHours($elements[1]);
- $this->setDaysOfMonth($elements[2]);
- $this->setMonths($elements[3]);
- $this->setDaysOfWeek($elements[4]);
- return $this;
- }
- /**
- *
- * @return string
- */
- public function getCronMinutes()
- {
- return $this->arrayToCron($this->minutes);
- }
- /**
- *
- * @return string
- */
- public function getCronHours()
- {
- return $this->arrayToCron($this->hours);
- }
- /**
- *
- * @return string
- */
- public function getCronDaysOfMonth()
- {
- return $this->arrayToCron($this->dom);
- }
- /**
- *
- * @return string
- */
- public function getCronMonths()
- {
- return $this->arrayToCron($this->months);
- }
- /**
- *
- * @return string
- */
- public function getCronDaysOfWeek()
- {
- return $this->arrayToCron($this->dow);
- }
- /**
- *
- * @return array
- */
- public function getMinutes()
- {
- return $this->minutes;
- }
- /**
- *
- * @return array
- */
- public function getHours()
- {
- return $this->hours;
- }
- /**
- *
- * @return array
- */
- public function getDaysOfMonth()
- {
- return $this->dom;
- }
- /**
- *
- * @return array
- */
- public function getMonths()
- {
- return $this->months;
- }
- /**
- *
- * @return array
- */
- public function getDaysOfWeek()
- {
- return $this->dow;
- }
- /**
- *
- * @param string|array $minutes
- * @return Cron
- */
- public function setMinutes($minutes)
- {
- $this->minutes = $this->cronToArray($minutes, 0, 59);
- return $this;
- }
- /**
- *
- * @param string|array $hours
- * @return Cron
- */
- public function setHours($hours)
- {
- $this->hours = $this->cronToArray($hours, 0, 23);
- return $this;
- }
- /**
- *
- * @param string|array $months
- * @return Cron
- */
- public function setMonths($months)
- {
- $this->months = $this->cronToArray($months, 1, 12);
- return $this;
- }
- /**
- *
- * @param string|array $dow
- * @return Cron
- */
- public function setDaysOfWeek($dow)
- {
- $this->dow = $this->cronToArray($dow, 0, 7);
- return $this;
- }
- /**
- *
- * @param string|array $dom
- * @return Cron
- */
- public function setDaysOfMonth($dom)
- {
- $this->dom = $this->cronToArray($dom, 1, 31);
- return $this;
- }
- /**
- *
- * @param mixed $date
- * @param int $min
- * @param int $hour
- * @param int $day
- * @param int $month
- * @param int $weekday
- * @return \DateTime
- */
- protected function parseDate($date, &$min, &$hour, &$day, &$month, &$weekday)
- {
- if (is_numeric($date) && (int)$date == $date) {
- $date = new \DateTime('@' . $date);
- }
- elseif (is_string($date)) {
- $date = new \DateTime('@' . strtotime($date));
- }
- if ($date instanceof \DateTime) {
- $min = (int)$date->format('i');
- $hour = (int)$date->format('H');
- $day = (int)$date->format('d');
- $month = (int)$date->format('m');
- $weekday = (int)$date->format('w'); // 0-6
- }
- else {
- throw new \RuntimeException('Date format not supported');
- }
- return new \DateTime($date->format('Y-m-d H:i:sP'));
- }
- /**
- *
- * @param int|string|\DateTime $date
- */
- public function matchExact($date)
- {
- $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
- return
- (empty($this->minutes) || \in_array($min, $this->minutes, true)) &&
- (empty($this->hours) || \in_array($hour, $this->hours, true)) &&
- (empty($this->dom) || \in_array($day, $this->dom, true)) &&
- (empty($this->months) || \in_array($month, $this->months, true)) &&
- (empty($this->dow) || \in_array($weekday, $this->dow, true) || ($weekday == 0 && \in_array(7, $this->dow, true)) || ($weekday == 7 && \in_array(0, $this->dow, true))
- );
- }
- /**
- *
- * @param int|string|\DateTime $date
- * @param int $minuteBefore
- * @param int $minuteAfter
- */
- public function matchWithMargin($date, $minuteBefore = 0, $minuteAfter = 0)
- {
- if ($minuteBefore > 0) {
- throw new \RuntimeException('MinuteBefore parameter cannot be positive !');
- }
- if ($minuteAfter < 0) {
- throw new \RuntimeException('MinuteAfter parameter cannot be negative !');
- }
- $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
- $interval = new \DateInterval('PT1M'); // 1 min
- if ($minuteBefore !== 0) {
- $date->sub(new \DateInterval('PT' . abs($minuteBefore) . 'M'));
- }
- $n = $minuteAfter - $minuteBefore + 1;
- for ($i = 0; $i < $n; $i++) {
- if ($this->matchExact($date)) {
- return true;
- }
- $date->add($interval);
- }
- return false;
- }
- /**
- *
- * @param array $array
- * @return string
- */
- protected function arrayToCron($array)
- {
- $n = \count($array);
- if (!\is_array($array) || $n === 0) {
- return '*';
- }
- $cron = [$array[0]];
- $s = $c = $array[0];
- for ($i = 1; $i < $n; $i++) {
- if ($array[$i] == $c + 1) {
- $c = $array[$i];
- $cron[\count($cron) - 1] = $s . '-' . $c;
- }
- else {
- $s = $c = $array[$i];
- $cron[] = $c;
- }
- }
- return implode(',', $cron);
- }
- /**
- *
- * @param array|string $string
- * @param int $min
- * @param int $max
- * @return array
- */
- protected function cronToArray($string, $min, $max)
- {
- $array = [];
- if (\is_array($string)) {
- foreach ($string as $val) {
- if (is_numeric($val) && (int)$val == $val && $val >= $min && $val <= $max) {
- $array[] = (int)$val;
- }
- }
- } elseif ($string !== '*') {
- while ($string !== '') {
- // test "*/n" expression
- if (preg_match('/^\*\/([0-9]+),?/', $string, $m)) {
- for ($i = max(0, $min); $i <= min(59, $max); $i += $m[1]) {
- $array[] = (int)$i;
- }
- $string = substr($string, strlen($m[0]));
- continue;
- }
- // test "a-b/n" expression
- if (preg_match('/^([0-9]+)-([0-9]+)\/([0-9]+),?/', $string, $m)) {
- for ($i = max($m[1], $min); $i <= min($m[2], $max); $i += $m[3]) {
- $array[] = (int)$i;
- }
- $string = substr($string, strlen($m[0]));
- continue;
- }
- // test "a-b" expression
- if (preg_match('/^([0-9]+)-([0-9]+),?/', $string, $m)) {
- for ($i = max($m[1], $min); $i <= min($m[2], $max); $i++) {
- $array[] = (int)$i;
- }
- $string = substr($string, strlen($m[0]));
- continue;
- }
- // test "c" expression
- if (preg_match('/^([0-9]+),?/', $string, $m)) {
- if ($m[1] >= $min && $m[1] <= $max) {
- $array[] = (int)$m[1];
- }
- $string = substr($string, strlen($m[0]));
- continue;
- }
- // something goes wrong in the expression
- return [];
- }
- }
- sort($array);
- return $array;
- }
- }
|