Initial repo created
[timetracker.git] / WEB-INF / lib / tdcron / class.tdcron.entry.php
1 <?php
2
3         /**
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.
6          *
7          * The parser is quite powerful and understands pretty much everything you will ever find in a Cron-Expression.
8          *
9          * A Cron-Expression consists of 5 segments:
10          *
11          * <pre>
12          *  .---------------- minute (0 - 59)
13          *  |   .------------- hour (0 - 23)
14          *  |   |   .---------- day of month (1 - 31)
15          *  |   |   |   .------- month (1 - 12)
16          *  |   |   |   |  .----- day of week (0 - 6)
17          *  |   |   |   |  |
18          *  *   *   *   *  *
19          * </pre>
20          *
21          * Each segment can contain values, ranges and intervals. A range is always written as "value1-value2" and
22          * intervals as "value1/value2".
23          *
24          * Of course each segment can contain multiple values seperated by commas.
25          *
26          * Some valid examples:
27          *
28          * <pre>
29          * 1,2,3,4,5
30          * 1-5
31          * 10-20/*
32          * Jan,Feb,Oct
33          * Monday-Friday
34          * 1-10,15,20,40-50/2
35          * </pre>
36          *
37          * The current version of the parser understands all weekdays and month names in german and english!
38          *
39          * Usually you won't need to call this class directly.
40          *
41          * Copyright (c) 2010 Christian Land / tagdocs.de
42          *
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:
48          *
49          * The above copyright notice and this permission notice shall be included in all copies or substantial
50          * portions of the Software.
51          *
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.
57          *
58          * @author      Christian Land <devel@tagdocs.de>
59          * @package     tinyCron
60          * @subpackage  tinyCronEntry
61          * @copyright   Copyright (c) 2010, Christian Land / tagdocs.de
62          * @version     v0.0.1 beta
63          */
64
65         class tdCronEntry {
66
67                 /**
68                  * The parsed cron-expression.
69                  * @var mixed
70                  */
71                 static private $cron            = array();
72
73                 /**
74                  * Ranges.
75                  * @var mixed
76                  */
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
88                                                 );
89
90                 /**
91                  * Named intervals.
92                  * @var mixed
93                  */
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 * * * *'
102                                                         );
103
104
105                 /**
106                  * Possible keywords for months/weekdays.
107                  * @var mixed
108                  */
109                 static private $keywords        = array(
110                                                         IDX_MONTH       => 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,
115                                                                                 '/(may|mai)/i'                                  => 5,
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
123                                                                                 ),
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
132                                                                                 )
133                                                         );
134
135                 /**
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.
138                  *
139                  * @access              public
140                  * @param               string          $expression     The cron-expression to parse.
141                  * @return              mixed
142                  */
143
144                 static public function parse($expression) {
145
146                         // Convert named expressions if neccessary
147
148                         if (substr($expression,0,1) == '@') {
149
150                                 $expression     = strtr($expression, self::$intervals);
151
152                                 if (substr($expression,0,1) == '@') {
153
154                                         // Oops... unknown named interval!?!!
155                                         throw new Exception('Unknown named interval ['.$expression.']', 10000);
156
157                                 }
158
159                         }
160
161                         // Next basic check... do we have 5 segments?
162
163                         $cron   = explode(' ',$expression);
164
165                         if (count($cron) <> 5) {
166
167                                 // No... we haven't...
168                                 throw new Exception('Wrong number of segments in expression. Expected: 5, Found: '.count($cron), 10001);
169
170                         } else {
171
172                                 // Yup, 5 segments... lets see if we can work with them
173
174                                 foreach ($cron as $idx=>$segment) {
175
176                                         try {
177
178                                                 $dummy[$idx]    = self::expandSegment($idx, $segment);
179
180                                         } catch (Exception $e) {
181
182                                                 throw $e;
183
184                                         }
185
186                                 }
187
188                         }
189
190                         return $dummy;
191
192                 }
193
194                 /**
195                  * expandSegment() analyses a single segment
196                  *
197                  * @access              public
198                  * @param               void
199                  * @return              void
200                  */
201
202                 static private function expandSegment($idx, $segment) {
203
204                         // Store original segment for later use
205
206                         $osegment       = $segment;
207
208                         // Replace months/weekdays like "January", "February", etc. with numbers
209
210                         if (isset(self::$keywords[$idx])) {
211
212                                 $segment        = preg_replace(
213                                                                 array_keys(self::$keywords[$idx]),
214                                                                 array_values(self::$keywords[$idx]),
215                                                                 $segment
216                                                                 );
217
218                         }
219
220                         // Replace wildcards
221
222                         if (substr($segment,0,1) == '*') {
223
224                                 $segment        = preg_replace('/^\*(\/\d+)?$/i',
225                                                                 self::$ranges[$idx]['min'].'-'.self::$ranges[$idx]['max'].'$1',
226                                                                 $segment);
227
228                         }
229
230                         // Make sure that nothing unparsed is left :)
231
232                         $dummy          = preg_replace('/[0-9\-\/\,]/','',$segment);
233
234                         if (!empty($dummy)) {
235
236                                 // Ohoh.... thats not good :-)
237                                 throw new Exception('Failed to parse segment: '.$osegment, 10002);
238
239                         }
240
241                         // At this point our string should be OK - lets convert it to an array
242
243                         $result         = array();
244                         $atoms          = explode(',',$segment);
245
246                         foreach ($atoms as $curatom) {
247
248                                 $result = array_merge($result, self::parseAtom($curatom));
249
250                         }
251
252                         // Get rid of duplicates and sort the array
253
254                         $result         = array_unique($result);
255                         sort($result);
256
257                         // Check for invalid values
258
259                         if ($idx == IDX_WEEKDAY) {
260
261                                 if (end($result) == 7) {
262
263                                         if (reset($result) <> 0) {
264                                                 array_unshift($result, 0);
265                                         }
266
267                                         array_pop($result);
268
269                                 }
270
271                         }
272
273                         foreach ($result as $key=>$value) {
274
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);
277                                 }
278
279                         }
280
281                         return $result;
282
283                 }
284
285                 /**
286                  * parseAtom() analyses a single segment
287                  *
288                  * @access              public
289                  * @param               string          $atom           The segment to parse
290                  * @return              array
291                  */
292
293                 static private function parseAtom($atom) {
294
295                         $expanded       = array();
296
297                         if (preg_match('/^(\d+)-(\d+)(\/(\d+))?/i', $atom, $matches)) {
298
299                                 $low    = $matches[1];
300                                 $high   = $matches[2];
301
302                                 if ($low > $high) {
303                                         list($low,$high)        = array($high,$low);
304                                 }
305
306                                 $step   = isset($matches[4]) ? $matches[4] : 1;
307
308                                 for($i = $low; $i <= $high; $i += $step) {
309                                         $expanded[]     = (int)$i;
310                                 }
311
312                         } else {
313
314                                 $expanded[]     = (int)$atom;
315
316                         }
317
318                         $expanded2      = array_unique($expanded);
319
320                         return $expanded;
321
322                 }
323
324         }