* .---------------- minute (0 - 59) * | .------------- hour (0 - 23) * | | .---------- day of month (1 - 31) * | | | .------- month (1 - 12) * | | | | .----- day of week (0 - 6) * | | | | | * * * * * * * * * Each segment can contain values, ranges and intervals. A range is always written as "value1-value2" and * intervals as "value1/value2". * * Of course each segment can contain multiple values seperated by commas. * * Some valid examples: * *
	 * 1,2,3,4,5
	 * 1-5
	 * 10-20/*
	 * Jan,Feb,Oct
	 * Monday-Friday
	 * 1-10,15,20,40-50/2
	 * 
* * The current version of the parser understands all weekdays and month names in german and english! * * Usually you won't need to call this class directly. * * Copyright (c) 2010 Christian Land / tagdocs.de * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @author Christian Land * @package tinyCron * @subpackage tinyCronEntry * @copyright Copyright (c) 2010, Christian Land / tagdocs.de * @version v0.0.1 beta */ class tdCronEntry { /** * The parsed cron-expression. * @var mixed */ static private $cron = array(); /** * Ranges. * @var mixed */ static private $ranges = array( IDX_MINUTE => array( 'min' => 0, 'max' => 59 ), // Minutes IDX_HOUR => array( 'min' => 0, 'max' => 23 ), // Hours IDX_DAY => array( 'min' => 1, 'max' => 31 ), // Days IDX_MONTH => array( 'min' => 1, 'max' => 12 ), // Months IDX_WEEKDAY => array( 'min' => 0, 'max' => 7 ) // Weekdays ); /** * Named intervals. * @var mixed */ static private $intervals = array( '@yearly' => '0 0 1 1 *', '@annualy' => '0 0 1 1 *', '@monthly' => '0 0 1 * *', '@weekly' => '0 0 * * 0', '@midnight' => '0 0 * * *', '@daily' => '0 0 * * *', '@hourly' => '0 * * * *' ); /** * Possible keywords for months/weekdays. * @var mixed */ static private $keywords = array( IDX_MONTH => array( '/(january|januar|jan)/i' => 1, '/(february|februar|feb)/i' => 2, '/(march|maerz|märz|mar|mae|mär)/i' => 3, '/(april|apr)/i' => 4, '/(may|mai)/i' => 5, '/(june|juni|jun)/i' => 6, '/(july|juli|jul)/i' => 7, '/(august|aug)/i' => 8, '/(september|sep)/i' => 9, '/(october|oktober|okt|oct)/i' => 10, '/(november|nov)/i' => 11, '/(december|dezember|dec|dez)/i' => 12 ), IDX_WEEKDAY => array( '/(sunday|sonntag|sun|son|su|so)/i' => 0, '/(monday|montag|mon|mo)/i' => 1, '/(tuesday|dienstag|die|tue|tu|di)/i' => 2, '/(wednesdays|mittwoch|mit|wed|we|mi)/i' => 3, '/(thursday|donnerstag|don|thu|th|do)/i' => 4, '/(friday|freitag|fre|fri|fr)/i' => 5, '/(saturday|samstag|sam|sat|sa)/i' => 6 ) ); /** * parseExpression() analyses crontab-expressions like "* * 1,2,3 * mon,tue" and returns an array * containing all values. If it can't be parsed, an exception is thrown. * * @access public * @param string $expression The cron-expression to parse. * @return mixed */ static public function parse($expression) { // Convert named expressions if neccessary if (substr($expression,0,1) == '@') { $expression = strtr($expression, self::$intervals); if (substr($expression,0,1) == '@') { // Oops... unknown named interval!?!! throw new Exception('Unknown named interval ['.$expression.']', 10000); } } // Next basic check... do we have 5 segments? $cron = explode(' ',$expression); if (count($cron) <> 5) { // No... we haven't... throw new Exception('Wrong number of segments in expression. Expected: 5, Found: '.count($cron), 10001); } else { // Yup, 5 segments... lets see if we can work with them foreach ($cron as $idx=>$segment) { try { $dummy[$idx] = self::expandSegment($idx, $segment); } catch (Exception $e) { throw $e; } } } return $dummy; } /** * expandSegment() analyses a single segment * * @access public * @param void * @return void */ static private function expandSegment($idx, $segment) { // Store original segment for later use $osegment = $segment; // Replace months/weekdays like "January", "February", etc. with numbers if (isset(self::$keywords[$idx])) { $segment = preg_replace( array_keys(self::$keywords[$idx]), array_values(self::$keywords[$idx]), $segment ); } // Replace wildcards if (substr($segment,0,1) == '*') { $segment = preg_replace('/^\*(\/\d+)?$/i', self::$ranges[$idx]['min'].'-'.self::$ranges[$idx]['max'].'$1', $segment); } // Make sure that nothing unparsed is left :) $dummy = preg_replace('/[0-9\-\/\,]/','',$segment); if (!empty($dummy)) { // Ohoh.... thats not good :-) throw new Exception('Failed to parse segment: '.$osegment, 10002); } // At this point our string should be OK - lets convert it to an array $result = array(); $atoms = explode(',',$segment); foreach ($atoms as $curatom) { $result = array_merge($result, self::parseAtom($curatom)); } // Get rid of duplicates and sort the array $result = array_unique($result); sort($result); // Check for invalid values if ($idx == IDX_WEEKDAY) { if (end($result) == 7) { if (reset($result) <> 0) { array_unshift($result, 0); } array_pop($result); } } foreach ($result as $key=>$value) { if (($value < self::$ranges[$idx]['min']) || ($value > self::$ranges[$idx]['max'])) { throw new Exception('Failed to parse segment, invalid value ['.$value.']: '.$osegment, 10003); } } return $result; } /** * parseAtom() analyses a single segment * * @access public * @param string $atom The segment to parse * @return array */ static private function parseAtom($atom) { $expanded = array(); if (preg_match('/^(\d+)-(\d+)(\/(\d+))?/i', $atom, $matches)) { $low = $matches[1]; $high = $matches[2]; if ($low > $high) { list($low,$high) = array($high,$low); } $step = isset($matches[4]) ? $matches[4] : 1; for($i = $low; $i <= $high; $i += $step) { $expanded[] = (int)$i; } } else { $expanded[] = (int)$atom; } $expanded2 = array_unique($expanded); return $expanded; } }