diff --git a/src/Cronner/Cronner.php b/src/Cronner/Cronner.php index da66aff..c161f22 100644 --- a/src/Cronner/Cronner.php +++ b/src/Cronner/Cronner.php @@ -179,6 +179,8 @@ public function addTasks($tasks) : self /** * Runs all cron tasks. + * + * @param DateTime $now */ public function run(DateTime $now = NULL) { @@ -190,28 +192,7 @@ public function run(DateTime $now = NULL) } foreach ($this->tasks as $task) { - try { - $name = $task->getName(); - if ($task->shouldBeRun($now)) { - if ($this->criticalSection->enter($name)) { - $this->onTaskBegin($this, $task); - $task($now); - $this->onTaskFinished($this, $task); - $this->criticalSection->leave($name); - } - } - } catch (Exception $e) { - $this->onTaskError($this, $e, $task); - $name = $task->getName(); - if ($this->criticalSection->isEntered($name)) { - $this->criticalSection->leave($name); - } - if ($e instanceof RuntimeException) { - throw $e; // Throw exception if it is Cronner Runtime exception - } elseif ($this->skipFailedTask === FALSE) { - throw $e; // Throw exception if failed task should not be skipped - } - } + $this->executeTask($task, $now); } } @@ -242,4 +223,49 @@ private function createIdFromObject($tasks) : string return sha1(get_class($tasks)); } + /** + * @param Task $task + * @param DateTime $now + * @param bool $forceRun + * @throws Exception + */ + private function executeTask(Task $task, DateTime $now, $forceRun = FALSE) + { + try { + $name = $task->getName(); + if ($task->shouldBeRun($now) || $forceRun) { + if ($this->criticalSection->enter($name)) { + $this->onTaskBegin($this, $task); + $task($now); + $this->onTaskFinished($this, $task); + $this->criticalSection->leave($name); + } + } + } catch (Exception $e) { + $this->onTaskError($this, $e, $task); + $name = $task->getName(); + if ($this->criticalSection->isEntered($name)) { + $this->criticalSection->leave($name); + } + if ($e instanceof RuntimeException) { + throw $e; // Throw exception if it is Cronner Runtime exception + } elseif ($this->skipFailedTask === FALSE) { + throw $e; // Throw exception if failed task should not be skipped + } + } + } + + /** + * @param Task $task + * @param bool $forceRun + */ + public function runTask(Task $task, $forceRun = FALSE) + { + if ($this->maxExecutionTime !== NULL) { + set_time_limit($this->maxExecutionTime); + } + + $this->executeTask($task, new DateTime(), $forceRun); + } + } diff --git a/src/Cronner/Tasks/Task.php b/src/Cronner/Tasks/Task.php index 188379a..f27c7cd 100644 --- a/src/Cronner/Tasks/Task.php +++ b/src/Cronner/Tasks/Task.php @@ -9,6 +9,7 @@ use Nette\Reflection\Method; use ReflectionClass; use stekycz\Cronner\ITimestampStorage; +use stekycz\Cronner\Tasks\Parameters; final class Task { @@ -110,4 +111,95 @@ private function getParameters() : Parameters return $this->parameters; } + /** + * @return DateTime|null + */ + public function getLastRun() + { + $this->timestampStorage->setTaskName($this->getName()); + return $this->timestampStorage->loadLastRunTime(); + } + + /** + * @param DateTime|null $startDate + * @return DateTime + */ + public function getNextRun(DateTime $startDate = NULL) : DateTime + { + $startDate = is_null($startDate) ? new DateTime() : clone $startDate; + + $days = [ + 1 => 'Mon', + 2 => 'Tue', + 3 => 'Wed', + 4 => 'Thu', + 5 => 'Fri', + 6 => 'Sat', + 7 => 'Sun', + ]; + + $lastRun = $this->getLastRun(); + + $parameters = Parameters::parseParameters($this->getMethodReflection()); + + if (is_null($parameters[Parameters::PERIOD]) && is_null($parameters[Parameters::DAYS]) && is_null($parameters[Parameters::TIME])) { + return $startDate; + } + + if(is_null($parameters[Parameters::DAYS])) { + $parameters[Parameters::DAYS] = $days; + } + + if(is_null($parameters[Parameters::TIME])) { + $parameters[Parameters::TIME][] = [ + 'from' => '00:00', + 'to' => NULL, + ]; + } + + $nextRun = $startDate; + + if (!is_null($parameters[Parameters::PERIOD]) && !is_null($lastRun)) { + $nextRun = $lastRun->modify(sprintf('+ %s', $parameters[Parameters::PERIOD])); + $nextRun = $nextRun < $startDate ? $startDate : $nextRun; + } + + $time = $nextRun->format('H:i'); + $day = $nextRun->format('N'); + $seconds = $nextRun->format('s'); + + $nextTimes = array_filter( + $parameters[Parameters::TIME], + function ($definedTimes) use ($nextRun) { + return $definedTimes['to'] === NULL || sprintf('%s:00', $definedTimes['to']) >= $nextRun->format('H:i:s'); + }); + + if (in_array($nextRun->format('D'), $parameters[Parameters::DAYS]) && !empty($nextTimes)) { + $nextTime = reset($nextTimes)['from']; + + if($nextTime <= $time) { + $nextTime = $time; + } else { + $seconds = 0; + } + + $timeParts = explode(':', $nextTime); + $nextRun->setTime((int) $timeParts[0], (int) $timeParts[1], (int) $seconds); + + } else { + $day++; + $day = $day > 7 ? 1 : $day; + + while(!in_array($days[$day], $parameters[Parameters::DAYS])) { + $day = ($day + 1) > 7 ? 0 : $day; + $day++; + } + + $nextRun->modify(sprintf('next %s', $days[$day])); + $timeParts = explode(':', reset($parameters[Parameters::TIME])['from']); + $nextRun->setTime((int) $timeParts[0], (int) $timeParts[1], 0); + } + + return $nextRun; + } } diff --git a/tests/CronnerTests/Tasks/Task.phpt b/tests/CronnerTests/Tasks/Task.phpt index b09c734..e39e860 100644 --- a/tests/CronnerTests/Tasks/Task.phpt +++ b/tests/CronnerTests/Tasks/Task.phpt @@ -95,6 +95,99 @@ class TaskTest extends \TestCase Assert::true($task->shouldBeRun(new Nette\Utils\DateTime('2014-08-15 09:17:00'))); } + /** + * @dataProvider dataProviderNextRun + * @param string $methodName + * @param string $now + * @param string|null $lastRunTime + * @param string $nextRunTime + */ + public function testNextRun(string $methodName, string $now, string $lastRunTime = NULL, string $nextRunTime) + { + $now = new \DateTime($now); + $nextRunTime = new \DateTime($nextRunTime); + + $method = (new \Nette\Reflection\ClassType($this->object))->getMethod($methodName); + + $timestampStorage = Mockery::mock(ITimestampStorage::class); + $timestampStorage->shouldReceive("loadLastRunTime")->atLeast(1)->andReturnUsing(function() use ($lastRunTime) { + return $lastRunTime ? \DateTime::createFromFormat('Y-m-d H:i:s', $lastRunTime) : NULL; + }); + + $timestampStorage->shouldReceive("setTaskName")->atLeast(1); + + $task = new Task($this->object, $method, $timestampStorage); + + Assert::equal($nextRunTime, $task->getNextRun($now)); + Assert::same(TRUE, $task->shouldBeRun($nextRunTime)); + } + + public function dataProviderNextRun() : array + { + return [ + // Test 01 + ['test01', '2013-02-01 12:00:00', NULL, '2013-02-01 12:00:00'], + ['test01', '2013-02-01 12:00:00', '2013-02-01 11:59:59', '2013-02-01 12:04:59'], + ['test01', '2013-02-01 12:00:00', '2013-02-01 12:00:00', '2013-02-01 12:05:00'], + ['test01', '2013-02-01 12:10:00', '2013-02-01 12:06:55', '2013-02-01 12:11:55'], + ['test01', '2013-02-02 15:20:23', '2012-02-01 12:06:55', '2013-02-02 15:20:23'], + // Test 02 + ['test02', '2013-02-01 12:00:00', NULL, '2013-02-01 15:00:00'], + ['test02', '2013-02-01 09:00:00', NULL, '2013-02-01 09:00:00'], + ['test02', '2013-02-01 09:00:38', '2013-02-01 09:00:00', '2013-02-01 10:00:00'], + ['test02', '2013-02-01 09:01:38', '2013-02-01 09:00:01', '2013-02-01 15:00:00'], + ['test02', '2013-02-01 14:01:38', NULL, '2013-02-01 15:00:00'], + ['test02', '2013-02-01 14:01:38', '2013-02-01 09:00:01', '2013-02-01 15:00:00'], + ['test02', '2013-02-01 15:01:38', '2013-02-01 09:00:01', '2013-02-01 15:01:38'], + ['test02', '2013-02-01 15:01:38', '2013-02-01 15:00:00', '2013-02-01 16:00:00'], + ['test02', '2013-02-01 15:01:38', '2013-02-01 15:00:01', '2013-02-04 09:00:00'], + ['test02', '2013-02-04 14:01:38', '2013-02-04 09:00:01', '2013-02-04 15:00:00'], + ['test02', '2013-02-04 15:01:38', '2013-02-04 09:00:01', '2013-02-04 15:01:38'], + ['test02', '2013-02-04 15:01:38', '2013-02-04 15:00:00', '2013-02-04 16:00:00'], + ['test02', '2013-02-04 15:01:38', '2013-02-04 15:00:01', '2013-02-06 09:00:00'], + ['test02', '2013-02-04 15:01:38', NULL, '2013-02-04 15:01:38'], + ['test02', '2013-02-05 14:01:38', '2013-02-04 09:00:01', '2013-02-06 09:00:00'], + ['test02', '2013-02-05 06:01:38', '2013-02-04 09:00:01', '2013-02-06 09:00:00'], + ['test02', '2013-02-05 9:00:00', '2013-02-04 15:00:00', '2013-02-06 09:00:00'], + ['test02', '2013-02-05 15:01:38', '2013-02-04 15:00:01', '2013-02-06 09:00:00'], + ['test02', '2013-02-05 15:01:38', NULL, '2013-02-06 09:00:00'], + ['test02', '2013-02-05 9:00:00', NULL, '2013-02-06 09:00:00'], + // Test 03 + ['test03', '2013-02-01 09:00:00', NULL, '2013-02-01 09:00:00'], + ['test03', '2013-02-01 09:00:38', '2013-02-01 09:00:00', '2013-02-01 09:17:00'], + ['test03', '2013-02-01 09:01:38', '2013-02-01 09:00:01', '2013-02-01 09:17:01'], + ['test03', '2013-02-01 10:44:38', '2013-02-01 09:00:01', '2013-02-01 10:44:38'], + ['test03', '2013-02-01 10:45:00', '2013-02-01 09:00:01', '2013-02-01 10:45:00'], + ['test03', '2013-02-01 10:45:01', '2013-02-01 09:00:01', '2013-02-04 09:00:00'], + ['test03', '2013-02-01 12:00:00', NULL, '2013-02-04 09:00:00'], + ['test03', '2013-02-01 12:00:00', '2013-02-01 09:00:01', '2013-02-04 09:00:00'], + ['test03', '2013-02-03 09:00:00', '2013-02-01 09:00:01', '2013-02-04 09:00:00'], + ['test03', '2013-02-05 09:01:38', NULL, '2013-02-05 09:01:38'], + ['test03', '2013-02-05 09:01:38', '2013-02-01 09:00:01', '2013-02-05 09:01:38'], + ['test03', '2013-02-05 09:18:38', '2013-02-05 09:00:00', '2013-02-05 09:18:38'], + ['test03', '2013-02-05 10:45:00', NULL, '2013-02-05 10:45:00'], + ['test03', '2013-02-05 10:45:01', NULL, '2013-02-06 09:00:00'], + ['test03', '2013-02-05 10:45:00', '2013-02-05 10:28:59', '2013-02-06 09:00:00'], + ['test03', '2013-02-05 10:45:00', '2013-02-05 10:28:00', '2013-02-05 10:45:00'], + ['test03', '2013-02-05 10:45:00', '2013-02-05 10:28:05', '2013-02-06 09:00:00'], + // Test 04 + ['test04', '2013-02-01 09:00:00', NULL, '2013-02-02 00:00:00'], + ['test04', '2013-02-01 09:00:38', '2013-02-01 09:00:00', '2013-02-02 09:00:00'], + ['test04', '2013-02-01 09:00:38', '2013-02-01 09:00:38', '2013-02-02 09:00:38'], + ['test04', '2013-02-01 09:00:38', '2012-02-01 09:00:38', '2013-02-02 00:00:00'], + ['test04', '2013-02-02 09:01:38', NULL, '2013-02-02 09:01:38'], + ['test04', '2013-02-02 09:01:38', '2013-02-01 00:01:38', '2013-02-02 09:01:38'], + ['test04', '2013-02-02 00:01:38', '2013-02-01 00:02:38', '2013-02-02 00:02:38'], + ['test04', '2013-02-02 09:01:38', '2013-02-02 00:01:38', '2013-02-03 00:01:38'], + ['test04', '2013-02-03 00:01:38', '2013-02-02 00:01:38', '2013-02-03 00:01:38'], + ['test04', '2013-02-03 00:01:37', '2013-02-02 00:01:38', '2013-02-03 00:01:38'], + ['test04', '2013-02-03 10:44:38', '2013-02-03 00:00:01', '2013-02-09 00:00:00'], + ['test04', '2013-02-03 10:44:38', '2013-02-03 00:26:01', '2013-02-09 00:00:00'], + ['test04', '2013-02-04 00:00:00', NULL, '2013-02-09 00:00:00'], + ['test04', '2013-02-06 23:46:27', NULL, '2013-02-09 00:00:00'], + ]; + } + } run(new TaskTest());