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);