TYPO3 API  SVNRelease
class.tx_scheduler_croncmd.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2008-2011 Markus Friedrich (markus.friedrich@dkd.de)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *
00017  *  This script is distributed in the hope that it will be useful,
00018  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  *  GNU General Public License for more details.
00021  *
00022  *  This copyright notice MUST APPEAR in all copies of the script!
00023  ***************************************************************/
00024 
00025 /**
00026  * This class provides calulations for the cron command format.
00027  *
00028  * @author Markus Friedrich <markus.friedrich@dkd.de>
00029  * @author Christian Kuhn <lolli@schwarzbu.ch>
00030  * @package TYPO3
00031  * @subpackage tx_scheduler
00032  */
00033 class tx_scheduler_CronCmd {
00034 
00035     /**
00036      * Normalized sections of the cron command.
00037      * Allowed are comma separated lists of integers and the character '*'
00038      *
00039      *  field          lower and upper bound
00040      *  -----          --------------
00041      *  minute         0-59
00042      *  hour           0-23
00043      *  day of month   1-31
00044      *  month          1-12
00045      *  day of week    1-7
00046      *
00047      * @var array $cronCommandSections
00048      */
00049     protected $cronCommandSections;
00050 
00051     /**
00052      * Timestamp of next execution date.
00053      * This value starts with 'now + 1 minute' if not set externally
00054      * by unit tests. After a call to calculateNextValue() it holds the timestamp of
00055      * the next execution date which matches the cron command restrictions.
00056      */
00057     protected $timestamp;
00058 
00059     /**
00060      * Constructor
00061      *
00062      * @api
00063      * @param string $cronCommand: The cron command can hold any combination documented as valid
00064      *      expression in usual unix like crons like vixiecron. Special commands like @weekly,
00065      *      ranges, steps and three letter month and weekday abbreviations are allowed.
00066      * @param integer $timestamp: optional start time, used in unit tests
00067      * @return void
00068      */
00069     public function __construct($cronCommand, $timestamp = FALSE) {
00070         $cronCommand = tx_scheduler_CronCmd_Normalize::normalize($cronCommand);
00071 
00072             // Explode cron command to sections
00073         $this->cronCommandSections = t3lib_div::trimExplode(' ', $cronCommand);
00074 
00075             // Initialize the values with the starting time
00076             // This takes care that the calculated time is always in the future
00077         if ($timestamp === FALSE) {
00078             $timestamp = strtotime('+1 minute');
00079         } else {
00080             $timestamp += 60;
00081         }
00082         $this->timestamp = $this->roundTimestamp($timestamp);
00083     }
00084 
00085     /**
00086      * Calulates the date of the next execution.
00087      *
00088      * @api
00089      * @param integer $level (Deprecated) Number of the current level, e.g. 2 is the day level
00090      * @return void
00091      */
00092     public function calculateNextValue($level = NULL) {
00093         if (!is_null($level)) {
00094             t3lib_div::deprecationLog('The parameter $level is deprecated since TYPO3 version 4.5.');
00095         }
00096 
00097         $newTimestamp = $this->getTimestamp();
00098 
00099             // Calculate next minute and hour field
00100         $loopCount = 0;
00101         while (TRUE) {
00102             $loopCount ++;
00103                 // If there was no match within two days, cron command is invalid.
00104                 // The second day is needed to catch the summertime leap in some countries.
00105             if ($loopCount > 2880) {
00106                 throw new RuntimeException(
00107                     'Unable to determine next execution timestamp: Hour and minute combination is invalid.',
00108                     1291494126
00109                 );
00110             }
00111             if ($this->minuteAndHourMatchesCronCommand($newTimestamp)) {
00112                 break;
00113             }
00114             $newTimestamp += 60;
00115         }
00116 
00117         $loopCount = 0;
00118         while (TRUE) {
00119             $loopCount ++;
00120                 // A date must match within the next 4 years, this high number makes
00121                 // sure leap year cron command configuration are caught.
00122                 // If the loop runs longer than that, the cron command is invalid.
00123             if ($loopCount > 1464) {
00124                 throw new RuntimeException(
00125                     'Unable to determine next execution timestamp: Day of month, month and day of week combination is invalid.',
00126                     1291501280
00127                 );
00128             }
00129             if ($this->dayMatchesCronCommand($newTimestamp)) {
00130                 break;
00131             }
00132             $newTimestamp += $this->numberOfSecondsInDay($newTimestamp);
00133         }
00134 
00135         $this->timestamp = $newTimestamp;
00136     }
00137 
00138     /*
00139      * Get next timestamp
00140      *
00141      * @api
00142      * @return integer Unix timestamp
00143      */
00144     public function getTimestamp() {
00145         return $this->timestamp;
00146     }
00147 
00148     /**
00149      * Get cron command sections. Array of strings, each containing either
00150      * a list of comma seperated integers or *
00151      *
00152      * @return array command sections:
00153      *  0 => minute
00154      *  1 => hour
00155      *  2 => day of month
00156      *  3 => month
00157      *  4 => day of week
00158      */
00159     public function getCronCommandSections() {
00160         return $this->cronCommandSections;
00161     }
00162 
00163     /**
00164      * Determine if current timestamp matches minute and hour cron command restriction.
00165      *
00166      * @param integer $timestamp to test
00167      * @return boolean TRUE if cron command conditions are met
00168      */
00169     protected function minuteAndHourMatchesCronCommand($timestamp) {
00170         $minute = intval(date('i', $timestamp));
00171         $hour = intval(date('G', $timestamp));
00172 
00173         $commandMatch = FALSE;
00174         if (
00175             $this->isInCommandList($this->cronCommandSections[0], $minute)
00176             && $this->isInCommandList($this->cronCommandSections[1], $hour)
00177         ) {
00178             $commandMatch = TRUE;
00179         }
00180 
00181         return $commandMatch;
00182     }
00183 
00184     /**
00185      * Determine if current timestamp matches day of month, month and day of week
00186      * cron command restriction
00187      *
00188      * @param integer $timestamp to test
00189      * @return boolean TRUE if cron command conditions are met
00190      */
00191     protected function dayMatchesCronCommand($timestamp) {
00192         $dayOfMonth = date('j', $timestamp);
00193         $month = date('n', $timestamp);
00194         $dayOfWeek = date('N', $timestamp);
00195 
00196         $isInDayOfMonth = $this->isInCommandList($this->cronCommandSections[2], $dayOfMonth);
00197         $isInMonth = $this->isInCommandList($this->cronCommandSections[3], $month);
00198         $isInDayOfWeek = $this->isInCommandList($this->cronCommandSections[4], $dayOfWeek);
00199 
00200             // Quote from vixiecron:
00201             // Note: The day of a command's execution can be specified by two fields — day of month, and day of week.
00202             // If both fields are restricted (i.e., aren't  *),  the  command will be run when either field
00203             // matches the current time.  For example, `30 4 1,15 * 5' would cause
00204             // a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday.
00205 
00206         $isDayOfMonthRestricted = (string)$this->cronCommandSections[2] ===  '*' ? FALSE : TRUE;
00207         $isDayOfWeekRestricted = (string)$this->cronCommandSections[4] === '*' ? FALSE : TRUE;
00208 
00209         $commandMatch = FALSE;
00210         if ($isInMonth) {
00211             if (
00212                 ($isInDayOfMonth && $isDayOfMonthRestricted)
00213                 || ($isInDayOfWeek && $isDayOfWeekRestricted)
00214                 || ($isInDayOfMonth && !$isDayOfMonthRestricted && $isInDayOfWeek && !$isDayOfWeekRestricted)
00215             ) {
00216                 $commandMatch = TRUE;
00217             }
00218         }
00219 
00220         return $commandMatch;
00221     }
00222 
00223     /**
00224      * Determine if a given number validates a cron command section. The given cron
00225      * command must be a 'normalized' list with only comma separated integers or '*'
00226      *
00227      * @param string $commandExpression: cron command
00228      * @param integer $numberToMatch: number to look up
00229      * @return boolean TRUE if number is in list
00230      */
00231     protected function isInCommandList($commandExpression, $numberToMatch) {
00232         $inList = FALSE;
00233         if ((string)$commandExpression === '*') {
00234             $inList = TRUE;
00235         } else {
00236             $inList = t3lib_div::inList($commandExpression, $numberToMatch);
00237         }
00238 
00239         return $inList;
00240     }
00241 
00242     /**
00243      * Helper method to calculate number of seconds in a day.
00244      *
00245      * This is not always 86400 (60*60*24) and depends on the timezone:
00246      * Some countries like Germany have a summertime / wintertime switch,
00247      * on every last sunday in march clocks are forwarded by one hour (set from 2:00 to 3:00),
00248      * and on last sunday of october they are set back one hour (from 3:00 to 2:00).
00249      * This shortens and lengthens the length of a day by one hour.
00250      *
00251      * @param integer timestamp
00252      * @return integer Number of seconds of day
00253      */
00254     protected function numberOfSecondsInDay($timestamp) {
00255         $now = mktime(0, 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
00256             // Make sure to be in next day, even if day has 25 hours
00257         $nextDay = $now + 60*60*25;
00258         $nextDay = mktime(0, 0, 0, date('n', $nextDay), date('j', $nextDay), date('Y', $nextDay));
00259 
00260         return ($nextDay - $now);
00261     }
00262 
00263     /**
00264      * Round a timestamp down to full minute.
00265      *
00266      * @param integer timestamp
00267      * @return integer Rounded timestamp
00268      */
00269     protected function roundTimestamp($timestamp) {
00270         return mktime(date('H', $timestamp), date('i', $timestamp), 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
00271     }
00272 
00273     /**
00274      * Returns the first value that is higher than the current value
00275      * from a list of possible values.
00276      *
00277      * @deprecated since 4.5
00278      * @param   mixed   $currentValue: the value to be searched in the list
00279      * @param   array   $listArray: the list of values
00280      * @return  mixed   The value from the list right after the current value
00281      */
00282     public function getNextValue($currentValue, array $listArray) {
00283         t3lib_div::deprecationLog('The method is deprecated since TYPO3 version 4.5.');
00284 
00285         $next_value = false;
00286 
00287         $numValues = count($listArray);
00288         for ($i = 0; $i < $numValues; $i++) {
00289             if ($listArray[$i] > $currentValue) {
00290                 $next_value = $listArray[$i];
00291                 break;
00292             }
00293         }
00294 
00295         return $next_value;
00296     }
00297 
00298     /**
00299      * Returns the timestamp for the value parts in $this->values.
00300      *
00301      * @deprecated since 4.5
00302      * @return integer unix timestamp
00303      */
00304     public function getTstamp() {
00305         t3lib_div::deprecationLog('The method is deprecated since TYPO3 version 4.5.');
00306         return $this->getTimestamp();
00307     }
00308 }
00309 
00310 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php'])) {
00311     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php']);
00312 }