4 * tinyCronEntry is part of tdCron. Its a class to parse Cron-Expressions like "1-45 1,2,3 1-30/5 January,February Mon,Tue"
5 * and convert it to an easily useable format.
7 * The parser is quite powerful and understands pretty much everything you will ever find in a Cron-Expression.
9 * A Cron-Expression consists of 5 segments:
12 * .---------------- minute (0 - 59)
13 * | .------------- hour (0 - 23)
14 * | | .---------- day of month (1 - 31)
15 * | | | .------- month (1 - 12)
16 * | | | | .----- day of week (0 - 6)
21 * Each segment can contain values, ranges and intervals. A range is always written as "value1-value2" and
22 * intervals as "value1/value2".
24 * Of course each segment can contain multiple values seperated by commas.
26 * Some valid examples:
37 * The current version of the parser understands all weekdays and month names in german and english!
39 * Usually you won't need to call this class directly.
41 * Copyright (c) 2010 Christian Land / tagdocs.de
43 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
44 * associated documentation files (the "Software"), to deal in the Software without restriction,
45 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
46 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
47 * subject to the following conditions:
49 * The above copyright notice and this permission notice shall be included in all copies or substantial
50 * portions of the Software.
52 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
53 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
54 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
55 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
56 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
58 * @author Christian Land <devel@tagdocs.de>
60 * @subpackage tinyCronEntry
61 * @copyright Copyright (c) 2010, Christian Land / tagdocs.de
62 * @version v0.0.1 beta
68 * The parsed cron-expression.
71 static private $cron = array();
77 static private $ranges = array(
78 IDX_MINUTE => array( 'min' => 0,
79 'max' => 59 ), // Minutes
80 IDX_HOUR => array( 'min' => 0,
81 'max' => 23 ), // Hours
82 IDX_DAY => array( 'min' => 1,
83 'max' => 31 ), // Days
84 IDX_MONTH => array( 'min' => 1,
85 'max' => 12 ), // Months
86 IDX_WEEKDAY => array( 'min' => 0,
87 'max' => 7 ) // Weekdays
94 static private $intervals = array(
95 '@yearly' => '0 0 1 1 *',
96 '@annualy' => '0 0 1 1 *',
97 '@monthly' => '0 0 1 * *',
98 '@weekly' => '0 0 * * 0',
99 '@midnight' => '0 0 * * *',
100 '@daily' => '0 0 * * *',
101 '@hourly' => '0 * * * *'
106 * Possible keywords for months/weekdays.
109 static private $keywords = array(
111 '/(january|januar|jan)/i' => 1,
112 '/(february|februar|feb)/i' => 2,
113 '/(march|maerz|märz|mar|mae|mär)/i' => 3,
114 '/(april|apr)/i' => 4,
116 '/(june|juni|jun)/i' => 6,
117 '/(july|juli|jul)/i' => 7,
118 '/(august|aug)/i' => 8,
119 '/(september|sep)/i' => 9,
120 '/(october|oktober|okt|oct)/i' => 10,
121 '/(november|nov)/i' => 11,
122 '/(december|dezember|dec|dez)/i' => 12
124 IDX_WEEKDAY => array(
125 '/(sunday|sonntag|sun|son|su|so)/i' => 0,
126 '/(monday|montag|mon|mo)/i' => 1,
127 '/(tuesday|dienstag|die|tue|tu|di)/i' => 2,
128 '/(wednesdays|mittwoch|mit|wed|we|mi)/i' => 3,
129 '/(thursday|donnerstag|don|thu|th|do)/i' => 4,
130 '/(friday|freitag|fre|fri|fr)/i' => 5,
131 '/(saturday|samstag|sam|sat|sa)/i' => 6
136 * parseExpression() analyses crontab-expressions like "* * 1,2,3 * mon,tue" and returns an array
137 * containing all values. If it can't be parsed, an exception is thrown.
140 * @param string $expression The cron-expression to parse.
144 static public function parse($expression) {
146 // Convert named expressions if neccessary
148 if (substr($expression,0,1) == '@') {
150 $expression = strtr($expression, self::$intervals);
152 if (substr($expression,0,1) == '@') {
154 // Oops... unknown named interval!?!!
155 throw new Exception('Unknown named interval ['.$expression.']', 10000);
161 // Next basic check... do we have 5 segments?
163 $cron = explode(' ',$expression);
165 if (count($cron) <> 5) {
167 // No... we haven't...
168 throw new Exception('Wrong number of segments in expression. Expected: 5, Found: '.count($cron), 10001);
172 // Yup, 5 segments... lets see if we can work with them
174 foreach ($cron as $idx=>$segment) {
178 $dummy[$idx] = self::expandSegment($idx, $segment);
180 } catch (Exception $e) {
195 * expandSegment() analyses a single segment
202 static private function expandSegment($idx, $segment) {
204 // Store original segment for later use
206 $osegment = $segment;
208 // Replace months/weekdays like "January", "February", etc. with numbers
210 if (isset(self::$keywords[$idx])) {
212 $segment = preg_replace(
213 array_keys(self::$keywords[$idx]),
214 array_values(self::$keywords[$idx]),
222 if (substr($segment,0,1) == '*') {
224 $segment = preg_replace('/^\*(\/\d+)?$/i',
225 self::$ranges[$idx]['min'].'-'.self::$ranges[$idx]['max'].'$1',
230 // Make sure that nothing unparsed is left :)
232 $dummy = preg_replace('/[0-9\-\/\,]/','',$segment);
234 if (!empty($dummy)) {
236 // Ohoh.... thats not good :-)
237 throw new Exception('Failed to parse segment: '.$osegment, 10002);
241 // At this point our string should be OK - lets convert it to an array
244 $atoms = explode(',',$segment);
246 foreach ($atoms as $curatom) {
248 $result = array_merge($result, self::parseAtom($curatom));
252 // Get rid of duplicates and sort the array
254 $result = array_unique($result);
257 // Check for invalid values
259 if ($idx == IDX_WEEKDAY) {
261 if (end($result) == 7) {
263 if (reset($result) <> 0) {
264 array_unshift($result, 0);
273 foreach ($result as $key=>$value) {
275 if (($value < self::$ranges[$idx]['min']) || ($value > self::$ranges[$idx]['max'])) {
276 throw new Exception('Failed to parse segment, invalid value ['.$value.']: '.$osegment, 10003);
286 * parseAtom() analyses a single segment
289 * @param string $atom The segment to parse
293 static private function parseAtom($atom) {
297 if (preg_match('/^(\d+)-(\d+)(\/(\d+))?/i', $atom, $matches)) {
303 list($low,$high) = array($high,$low);
306 $step = isset($matches[4]) ? $matches[4] : 1;
308 for($i = $low; $i <= $high; $i += $step) {
309 $expanded[] = (int)$i;
314 $expanded[] = (int)$atom;
318 $expanded2 = array_unique($expanded);