PHP Classes

File: YACronParser.php

Recommend this page to a friend!
  Classes of Brett Dutton  >  Yet Another PHP Cron Parser  >  YACronParser.php  >  Download  
File: YACronParser.php
Role: Class source
Content type: text/plain
Description: All inclusive Class plus the test cases at the end
Class: Yet Another PHP Cron Parser
Parse a cron string and get the last run time
Author: By
Last change: Minor bug fix - Incorrect class name in tests
Date: 6 years ago
Size: 13,319 bytes
 

Contents

Class file image Download
<?php // namespace Jackbooted\Cron; /** * @copyright Confidential and copyright (c) 2016 Jackbooted Software. All rights reserved. * * Written by Brett Dutton of Jackbooted Software * brett at brettdutton dot com * * This software is written and distributed under the GNU General Public * License which means that its source code is freely-distributed and * available to the general public. */ /* * There are a few Cron Parser classes (about 3) * All were way to big for what was needed. This is an attempt to simplify the process * Not the most optimised * * For this to be useful need to keep track of the last time that the command was run. * This means that you can use a construct like this * * foreach ( self::getCronTabList () as $sheduleItem ) { * * $storedLastRunTime = strtotime( ( $sheduleItem->lastRun == '' ) ? $sheduleItem->start : $sheduleItem->lastRun ); * $previousCalculatedRunTime = CronParser::lastRun( $sheduleItem->cron ); * * // This looks at when the item had run. If the stored value is less than * // the calculated value means that we have past a run period. So need to run * if ( $storedLastRunTime < $previousCalculatedRunTime ) { * * // Update the run time to now * $sheduleItem->lastRun = strftime ( '%Y-%m-%d %H:%M', $previousCalculatedRunTime ); * $sheduleItem->save (); * * // Do the work to run the cron job here * // .................. * } * } * * Cron Definition # * * * * * # - - - - - # | | | | | # | | | | | # | | | | +----- day of week (0 - 6) (0 to 6 are Sunday to Saturday, or use names; 7 is Sunday, the same as 0) # | | | +---------- month (1 - 12) # | | +--------------- day of month (1 - 31) # | +-------------------- hour (0 - 23) # +------------------------- min (0 - 59) * * See the code at the end of this class to see some examples * */ class YACronParser { /** * Gets the last run date before now * @param String $cronString * @return number time in seconds that the last cron task ran */ public static function lastRun ( $cronString ) { $originalString = $cronString; $mappings = [ '@yearly' => '0 0 1 1 *', '@annually' => '0 0 1 1 *', '@monthly' => '0 0 1 * *', '@weekly' => '0 0 * * 0', '@daily' => '0 0 * * *', '@hourly' => '0 * * * *' ]; if ( isset ( $mappings[$cronString] ) ) { $cronString = $mappings[$cronString]; } // Reduces white spaces $cronString = preg_replace( '/[\s]{2,}/', ' ', $cronString); // recognise the really simple case if ( $cronString == '* * * * *' ) { $calcTime = ( (int)( time() / 60 ) ) * 60; return $calcTime; } // Get rid of names $cronString = strtolower( $cronString ); $cronString = str_replace ( [ 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' ], [ '0', '1', '2', '3', '4', '5', '6' ], $cronString ); // Get the parts if not 5 return every minute $cronParts = explode( ' ' , $cronString ); if ( count ( $cronParts ) != 5 ) { $calcTime = ( (int)( time() / 60 ) ) * 60; return $calcTime; } // evaluate the valid ranges for all of the parts $cronParts[0] = self::cronPartToRange( $cronParts[0], range( 0, 59 ) ); // Minutes $cronParts[1] = self::cronPartToRange( $cronParts[1], range( 0, 23 ) ); // Hours $cronParts[2] = self::cronPartToRange( $cronParts[2], range( 1, 31 ) ); // Day of Month $cronParts[3] = self::cronPartToRange( $cronParts[3], range( 1, 12 ) ); // Month of the year $cronParts[4] = self::cronPartToRange( $cronParts[4], range( 0, 6 ) ); // Day of the week $cronParts[5] = [ (int)date( 'Y' ) - 2, (int)date( 'Y' ) - 1, (int)date( 'Y' ) ]; // only go back 2 years // ******* TODO **************** // The code below is usually commented out. // Do not need this in production. So when you are done, remove echo '<br/>' . "\n" . 'Original: ' . $originalString . ' Optimised: ' . $cronString . '<br/>' . "\n"; $cols = [ 'min', 'hrs', 'day or month', 'month', 'day of week', 'year']; foreach ( $cronParts as $idx => $part ) { echo $idx . ' - ' . $cols[$idx] . ' - [' . join( ', ', $part ) . ']<br/>' . "\n"; } // Find the index for the last run based on current time $correctParts = [ 0, 0, 0, 0, 0, 0 ]; $numDaysOfWeek = count ( $cronParts[4] ); $now = time(); // search down the to the last run for ( $correctParts[5]=count( $cronParts[5] ) - 1; $correctParts[5]>=0; $correctParts[5]-- ) { for ( $correctParts[3]=count( $cronParts[3] ) - 1; $correctParts[3]>=0; $correctParts[3]-- ) { for ( $correctParts[2]=count( $cronParts[2] ) - 1; $correctParts[2]>=0; $correctParts[2]-- ) { // If this is not a valid day then skip it if ( $numDaysOfWeek != 7 && ! in_array ( self::getDayOfWeek( $cronParts, $correctParts ), $cronParts[4] ) ) { continue; } // Calc hour and minute for ( $correctParts[1]=count( $cronParts[1] ) - 1; $correctParts[1]>=0; $correctParts[1]-- ) { for ( $correctParts[0]=count( $cronParts[0] ) - 1; $correctParts[0]>=0; $correctParts[0]-- ) { $calcTime = self::calcTime( $cronParts, $correctParts ); if ( $calcTime < $now ) { return $calcTime; } } } } } } // Made it to here then return stanadrd time return ( (int)( time() / 60 ) ) * 60; } /** * Calculates the time based on the pointers into the valid range of array. * * @param array $cronParts array of arrays containing the valid ranges based on cron string * @param array $correctParts pointers into the array that we are testing * @return number The time based on the valid cron pieces */ private static function calcTime ( $cronParts, $correctParts ) { $tim = mktime ( $cronParts[1][$correctParts[1]], $cronParts[0][$correctParts[0]], 0, $cronParts[3][$correctParts[3]], $cronParts[2][$correctParts[2]], $cronParts[5][$correctParts[5]] ); //echo '$cronParts - [' . join( ', ', $cronParts ) . ']<br/>' . "\n"; //echo '$correctParts - [' . join( ', ', $correctParts ) . ']<br/>' . "\n"; //echo date ( 'Y-m-d H:i', $tim ) . '<br/>' . "\n"; return $tim; } /** * Calculates the day of the week based on the ranges and pointers into the current * @param unknown $cronParts * @param unknown $correctParts * @return number day of week 0-6 */ private static function getDayOfWeek ( $cronParts, $correctParts ) { $tim = mktime ( 0, 0, 0, $cronParts[3][$correctParts[3]], $cronParts[2][$correctParts[2]], $cronParts[5][$correctParts[5]] ); return (int)date( 'w', $tim ); } /** * Takes the cron component and the range of valid numbers and reduces the list. * This method calls itself recursively until all the elements are evaluated * * @param type $part the part of the cron string * @param type $fullRange. For the position the $fullRange variable contains all the elements that would be valid. * e.g. minutes would be range( 0, 59 ) * @return type */ private static function cronPartToRange ( $part, $fullRange ) { // simple digit so just return if ( preg_match ( '/^[0-9]+$/', $part ) ) { return [ (int)$part ]; } // If there are commas then multiple components. // get all the components and then merge, sort and uniq else if ( strpos( $part, ',' ) !== false ) { $validValues = []; foreach ( explode( ',', $part ) as $bit ) { $validValues = array_merge( $validValues, self::cronPartToRange( $bit, $fullRange ) ); } $validValues = array_unique ( $validValues ); sort ( $validValues ); return $validValues; } // This is the starting point and the divisor e.g. // */5 = start at 0 and select every 5th element so 0, 5, 10 .... // or 2/3 = start at second element and then every 3rd element so 2, 5, // LHS could be a range like 30-50 else if ( strpos( $part, '/' ) !== false ) { //Split to LHS and RHS $bits = explode( '/', $part ); // recursivley get the range of LHS $lhsRange = self::cronPartToRange( $bits[0], $fullRange ); // If there is no elements then some failure and should not happen default to all if ( count ( $lhsRange ) == 0 ) { $lhsRange = $fullRange; } // If this has one element then it must be starting point for the // so something like 5/15 => 5, 20, 35, 50 // Take off the full range of elements till you get to the matching one else if ( count ( $lhsRange ) == 1 ) { while ( count ( $fullRange ) > 0 && $fullRange[0] != $lhsRange[0] ) { array_shift( $fullRange ); } $lhsRange = $fullRange; } $rhsElement = (int)$bits[1]; // Identify the positions that match the RHS. Step through the array and see if the indexes // modulus rhs is 0 $validValues = []; foreach ( $lhsRange as $idx => $num ) { if ( ( $idx % $rhsElement ) == 0 ) $validValues[] = $num; } return $validValues; } // Must be a range, so split LHS and RHS to get the valid range else if ( strpos( $part, '-' ) !== false ) { $bits = explode( '-', $part ); return range ( (int)$bits[0], (int)$bits[1] ); } // Default to full range else { return $fullRange; } } } // ******* TODO **************** // The code below is usually commented out. // Do not need this in production. So when you are done, remove date_default_timezone_set ( 'Australia/Brisbane' ); echo 'Simple case - no calcs - LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '* * * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '5/15 0 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0/15 * * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 2 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '* 11-20 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0/15 * * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '* 12-21 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 2 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '1-59/2 14-23 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0-58/2 14-23 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '* * * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '* 22-23 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '1-2 22-23 * * Monday-Wednesday' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '1,2,3,4,10-20 11-20 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 */4 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 0/4 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 2/3 * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '30-50/3 * * * *' ) ) . '<br/>' . "\n"; echo 'LastRun Date: ' . date ( 'Y-m-d H:i', YACronParser::lastRun( '0 0 31 * *' ) ) . '<br/>' . "\n";