8889841ccron-expression/src/Cron/DayOfWeekField.php000064400000005602150514531300014665 0ustar00convertLiterals($value); $currentYear = $date->format('Y'); $currentMonth = $date->format('m'); $lastDayOfMonth = $date->format('t'); // Find out if this is the last specific weekday of the month if (strpos($value, 'L')) { $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L'))); $tdate = clone $date; $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth); while ($tdate->format('w') != $weekday) { $tdateClone = new DateTime(); $tdate = $tdateClone ->setTimezone($tdate->getTimezone()) ->setDate($currentYear, $currentMonth, --$lastDayOfMonth); } return $date->format('j') == $lastDayOfMonth; } // Handle # hash tokens if (strpos($value, '#')) { list($weekday, $nth) = explode('#', $value); // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601 if ($weekday === '0') { $weekday = 7; } // Validate the hash fields if ($weekday < 0 || $weekday > 7) { throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given"); } if ($nth > 5) { throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month'); } // The current weekday must match the targeted weekday to proceed if ($date->format('N') != $weekday) { return false; } $tdate = clone $date; $tdate->setDate($currentYear, $currentMonth, 1); $dayCount = 0; $currentDay = 1; while ($currentDay < $lastDayOfMonth + 1) { if ($tdate->format('N') == $weekday) { if (++$dayCount >= $nth) { break; } } $tdate->setDate($currentYear, $currentMonth, ++$currentDay); } return $date->format('j') == $currentDay; } // Handle day of the week values if (strpos($value, '-')) { $parts = explode('-', $value); if ($parts[0] == '7') { $parts[0] = '0'; } elseif ($parts[1] == '0') { $parts[1] = '7'; } $value = implode('-', $parts); } // Test to see which Sunday to use -- 0 == 7 == Sunday $format = in_array(7, str_split($value)) ? 'N' : 'w'; $fieldValue = $date->format($format); return $this->isSatisfied($fieldValue, $value); } public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('-1 day'); $date->setTime(23, 59, 0); } else { $date->modify('+1 day'); $date->setTime(0, 0, 0); } return $this; } public function validate($value) { $value = $this->convertLiterals($value); foreach (explode(',', $value) as $expr) { if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) { return false; } } return true; } private function convertLiterals($string) { return str_ireplace( array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'), range(0, 6), $string ); } } cron-expression/src/Cron/YearField.php000064400000001164150514531300013746 0ustar00isSatisfied($date->format('Y'), $value); } public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('-1 year'); $date->setDate($date->format('Y'), 12, 31); $date->setTime(23, 59, 0); } else { $date->modify('+1 year'); $date->setDate($date->format('Y'), 1, 1); $date->setTime(0, 0, 0); } return $this; } public function validate($value) { return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); } } cron-expression/src/Cron/MinutesField.php000064400000002647150514531300014501 0ustar00isSatisfied($date->format('i'), $value); } public function increment(DateTime $date, $invert = false, $parts = null) { if (is_null($parts)) { if ($invert) { $date->modify('-1 minute'); } else { $date->modify('+1 minute'); } return $this; } $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts); $minutes = array(); foreach ($parts as $part) { $minutes = array_merge($minutes, $this->getRangeForExpression($part, 59)); } $current_minute = $date->format('i'); $position = $invert ? count($minutes) - 1 : 0; if (count($minutes) > 1) { for ($i = 0; $i < count($minutes) - 1; $i++) { if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) || ($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) { $position = $invert ? $i : $i + 1; break; } } } if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) { $date->modify(($invert ? '-' : '+') . '1 hour'); $date->setTime($date->format('H'), $invert ? 59 : 0); } else { $date->setTime($date->format('H'), $minutes[$position]); } return $this; } public function validate($value) { return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); } } cron-expression/src/Cron/MonthField.php000064400000001373150514531300014135 0ustar00isSatisfied($date->format('m'), $value); } public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('last day of previous month'); $date->setTime(23, 59); } else { $date->modify('first day of next month'); $date->setTime(0, 0); } return $this; } public function validate($value) { return (bool) preg_match('/^[\*,\/\-0-9A-Z]+$/', $value); } } cron-expression/src/Cron/FieldInterface.php000064400000000370150514531300014744 0ustar00 '0 0 1 1 *', '@annually' => '0 0 1 1 *', '@monthly' => '0 0 1 * *', '@weekly' => '0 0 * * 0', '@daily' => '0 0 * * *', '@hourly' => '0 * * * *' ); if (isset($mappings[$expression])) { $expression = $mappings[$expression]; } return new static($expression, $fieldFactory ?: new FieldFactory()); } public static function isValidExpression($expression) { try { self::factory($expression); } catch (InvalidArgumentException $e) { return false; } return true; } public function __construct($expression, FieldFactory $fieldFactory) { $this->fieldFactory = $fieldFactory; $this->setExpression($expression); } public function setExpression($value) { $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY); if (count($this->cronParts) < 5) { throw new InvalidArgumentException( $value . ' is not a valid CRON expression' ); } foreach ($this->cronParts as $position => $part) { $this->setPart($position, $part); } return $this; } public function setPart($position, $value) { if (!$this->fieldFactory->getField($position)->validate($value)) { throw new InvalidArgumentException( 'Invalid CRON field value ' . $value . ' at position ' . $position ); } $this->cronParts[$position] = $value; return $this; } public function setMaxIterationCount($maxIterationCount) { $this->maxIterationCount = $maxIterationCount; return $this; } public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) { return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate); } public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) { return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate); } public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false) { $matches = array(); for ($i = 0; $i < max(0, $total); $i++) { try { $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate); } catch (RuntimeException $e) { break; } } return $matches; } public function getExpression($part = null) { if (null === $part) { return implode(' ', $this->cronParts); } elseif (array_key_exists($part, $this->cronParts)) { return $this->cronParts[$part]; } return null; } public function __toString() { return $this->getExpression(); } public function isDue($currentTime = 'now') { if ('now' === $currentTime) { $currentDate = date('Y-m-d H:i'); $currentTime = strtotime($currentDate); } elseif ($currentTime instanceof DateTime) { $currentDate = clone $currentTime; // Ensure time in 'current' timezone is used $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); $currentDate = $currentDate->format('Y-m-d H:i'); $currentTime = strtotime($currentDate); } elseif ($currentTime instanceof DateTimeImmutable) { $currentDate = DateTime::createFromFormat('U', $currentTime->format('U')); $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); $currentDate = $currentDate->format('Y-m-d H:i'); $currentTime = strtotime($currentDate); } else { $currentTime = new DateTime($currentTime); $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0); $currentDate = $currentTime->format('Y-m-d H:i'); $currentTime = $currentTime->getTimeStamp(); } try { return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime; } catch (Exception $e) { return false; } } protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false) { if ($currentTime instanceof DateTime) { $currentDate = clone $currentTime; } elseif ($currentTime instanceof DateTimeImmutable) { $currentDate = DateTime::createFromFormat('U', $currentTime->format('U')); $currentDate->setTimezone($currentTime->getTimezone()); } else { $currentDate = new DateTime($currentTime ?: 'now'); $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); } $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0); $nextRun = clone $currentDate; $nth = (int) $nth; // We don't have to satisfy * or null fields $parts = array(); $fields = array(); foreach (self::$order as $position) { $part = $this->getExpression($position); if (null === $part || '*' === $part) { continue; } $parts[$position] = $part; $fields[$position] = $this->fieldFactory->getField($position); } // Set a hard limit to bail on an impossible date for ($i = 0; $i < $this->maxIterationCount; $i++) { foreach ($parts as $position => $part) { $satisfied = false; // Get the field object used to validate this part $field = $fields[$position]; // Check if this is singular or a list if (strpos($part, ',') === false) { $satisfied = $field->isSatisfiedBy($nextRun, $part); } else { foreach (array_map('trim', explode(',', $part)) as $listPart) { if ($field->isSatisfiedBy($nextRun, $listPart)) { $satisfied = true; break; } } } // If the field is not satisfied, then start over if (!$satisfied) { $field->increment($nextRun, $invert, $part); continue 2; } } // Skip this match if needed if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) { $this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null); continue; } return $nextRun; } // @codeCoverageIgnoreStart throw new RuntimeException('Impossible CRON expression'); // @codeCoverageIgnoreEnd } } cron-expression/src/Cron/FieldFactory.php000064400000001367150514531300014462 0ustar00fields[$position])) { switch ($position) { case 0: $this->fields[$position] = new MinutesField(); break; case 1: $this->fields[$position] = new HoursField(); break; case 2: $this->fields[$position] = new DayOfMonthField(); break; case 3: $this->fields[$position] = new MonthField(); break; case 4: $this->fields[$position] = new DayOfWeekField(); break; case 5: $this->fields[$position] = new YearField(); break; default: throw new InvalidArgumentException( $position . ' is not a valid position' ); } } return $this->fields[$position]; } } cron-expression/src/Cron/DayOfMonthField.php000064400000006003150514531300015053 0ustar00format('N'); if ($currentWeekday < 6) { return $target; } $lastDayOfMonth = $target->format('t'); foreach (array(-1, 1, -2, 2) as $i) { $adjusted = $targetDay + $i; if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) { $target->setDate($currentYear, $currentMonth, $adjusted); if ($target->format('N') < 6 && $target->format('m') == $currentMonth) { return $target; } } } } public function isSatisfiedBy(DateTime $date, $value) { // ? states that the field value is to be skipped if ($value == '?') { return true; } $fieldValue = $date->format('d'); // Check to see if this is the last day of the month if ($value == 'L') { return $fieldValue == $date->format('t'); } // Check to see if this is the nearest weekday to a particular value if (strpos($value, 'W')) { // Parse the target day $targetDay = substr($value, 0, strpos($value, 'W')); // Find out if the current day is the nearest day of the week return $date->format('j') == self::getNearestWeekday( $date->format('Y'), $date->format('m'), $targetDay )->format('j'); } return $this->isSatisfied($date->format('d'), $value); } public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('previous day'); $date->setTime(23, 59); } else { $date->modify('next day'); $date->setTime(0, 0); } return $this; } public function validate($value) { // Allow wildcards and a single L if ($value === '?' || $value === '*' || $value === 'L') { return true; } // If you only contain numbers and are within 1-31 if ((bool) preg_match('/^\d{1,2}$/', $value) && ($value >= 1 && $value <= 31)) { return true; } // If you have a -, we will deal with each of your chunks if ((bool) preg_match('/-/', $value)) { // We cannot have a range within a list or vice versa if ((bool) preg_match('/,/', $value)) { return false; } $chunks = explode('-', $value); foreach ($chunks as $chunk) { if (!$this->validate($chunk)) { return false; } } return true; } // If you have a comma, we will deal with each value if ((bool) preg_match('/,/', $value)) { // We cannot have a range within a list or vice versa if ((bool) preg_match('/-/', $value)) { return false; } $chunks = explode(',', $value); foreach ($chunks as $chunk) { if (!$this->validate($chunk)) { return false; } } return true; } // If you contain a /, we'll deal with it if ((bool) preg_match('/\//', $value)) { $chunks = explode('/', $value); foreach ($chunks as $chunk) { if (!$this->validate($chunk)) { return false; } } return true; } // If you end in W, make sure that it has a numeric in front of it if ((bool) preg_match('/^\d{1,2}W$/', $value)) { return true; } return false; } } cron-expression/src/Cron/index.php000064400000000006150514531300013203 0ustar00isIncrementsOfRanges($value)) { return $this->isInIncrementsOfRanges($dateValue, $value); } elseif ($this->isRange($value)) { return $this->isInRange($dateValue, $value); } return $value == '*' || $dateValue == $value; } public function isRange($value) { return strpos($value, '-') !== false; } public function isIncrementsOfRanges($value) { return strpos($value, '/') !== false; } public function isInRange($dateValue, $value) { $parts = array_map('trim', explode('-', $value, 2)); return $dateValue >= $parts[0] && $dateValue <= $parts[1]; } public function isInIncrementsOfRanges($dateValue, $value) { $parts = array_map('trim', explode('/', $value, 2)); $stepSize = isset($parts[1]) ? (int) $parts[1] : 0; if ($stepSize === 0) { return false; } if (($parts[0] == '*' || $parts[0] === '0')) { return (int) $dateValue % $stepSize == 0; } $range = explode('-', $parts[0], 2); $offset = $range[0]; $to = isset($range[1]) ? $range[1] : $dateValue; // Ensure that the date value is within the range if ($dateValue < $offset || $dateValue > $to) { return false; } if ($dateValue > $offset && 0 === $stepSize) { return false; } for ($i = $offset; $i <= $to; $i+= $stepSize) { if ($i == $dateValue) { return true; } } return false; } public function getRangeForExpression($expression, $max) { $values = array(); if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) { if (!$this->isIncrementsOfRanges($expression)) { list ($offset, $to) = explode('-', $expression); $stepSize = 1; } else { $range = array_map('trim', explode('/', $expression, 2)); $stepSize = isset($range[1]) ? $range[1] : 0; $range = $range[0]; $range = explode('-', $range, 2); $offset = $range[0]; $to = isset($range[1]) ? $range[1] : $max; } $offset = $offset == '*' ? 0 : $offset; for ($i = $offset; $i <= $to; $i += $stepSize) { $values[] = $i; } sort($values); } else { $values = array($expression); } return $values; } } cron-expression/src/Cron/HoursField.php000064400000003323150514531300014145 0ustar00isSatisfied($date->format('H'), $value); } public function increment(DateTime $date, $invert = false, $parts = null) { // Change timezone to UTC temporarily. This will // allow us to go back or forwards and hour even // if DST will be changed between the hours. if (is_null($parts) || $parts == '*') { $timezone = $date->getTimezone(); $date->setTimezone(new DateTimeZone('UTC')); if ($invert) { $date->modify('-1 hour'); } else { $date->modify('+1 hour'); } $date->setTimezone($timezone); $date->setTime($date->format('H'), $invert ? 59 : 0); return $this; } $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts); $hours = array(); foreach ($parts as $part) { $hours = array_merge($hours, $this->getRangeForExpression($part, 23)); } $current_hour = $date->format('H'); $position = $invert ? count($hours) - 1 : 0; if (count($hours) > 1) { for ($i = 0; $i < count($hours) - 1; $i++) { if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) || ($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) { $position = $invert ? $i : $i + 1; break; } } } $hour = $hours[$position]; if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) { $date->modify(($invert ? '-' : '+') . '1 day'); $date->setTime($invert ? 23 : 0, $invert ? 59 : 0); } else { $date->setTime($hour, $invert ? 59 : 0); } return $this; } public function validate($value) { return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value); } } cron-expression/src/index.php000064400000000006150514531300012302 0ustar00