Skip to content

PHP implementation of cron

I recently needed to create an application that creates recurring calendar events. After thinking about this for a while, I decided that doing something similar to unix cron was the solution.

This is the unix cron format:

* * * * * command to be executed
- - - - -
| | | | |
| | | | +----- day of week (0 - 6) (Sunday=0)
| | | +------- month (1 - 12)
| | +--------- day of month (1 - 31)
| +----------- hour (0 - 23)
+------------- min (0 - 59)

My app does not need to worry about min or hour. I only need to account for day of the month, month and day of the week.
I allow the user to specify the recurring dates in a way that is similar to how cron accepts ranges.
The dates can be either a single date, multiple dates separated by commas, a range of dates using the minus sign, or * for every.

Here is the form I use for submission:
recurring task manager

Here is the code that handles the possible range of inputs:

// day
if (isset($_POST['taskDay']) && ($_POST['taskDay'] != '') && ($_POST['taskDay'] != '*')) {
  $_POST['taskDay'] = str_replace(' ', '', $_POST['taskDay']);
  if (strpos($_POST['taskDay'], ',')) { // specific days
    $days = explode(',',$_POST['taskDay']);
  } else if (strpos($_POST['taskDay'], '-')) { // day range
    $x = explode('-',$_POST['taskDay']);
    for ($i = $x[0]; $i <= $x[1]; $i++) {
      $days[] = $i;
    }
  } else if ( ($_POST['taskDay'] > 0) && ($_POST['taskDay'] < 32) ) {
    $days[] = $_POST['taskDay'];
  }
} else {
  $day = '*'; // $_POST['taskDay'] was left blank, set it to *
}

// month
if (isset($_POST['taskMonth']) && ($_POST['taskMonth'] != '') && ($_POST['taskMonth'] != '*')) {
  $_POST['taskMonth'] = str_replace(' ', '', $_POST['taskMonth']);
  if (strpos($_POST['taskMonth'], ',')) { // specific months
            $months = explode(',',$_POST['taskMonth']);
  } elseif (strpos($_POST['taskMonth'], '-')) { // month range
            $x = explode('-',$_POST['taskMonth']);
            for ($i = $x[0]; $i <= $x[1]; $i++) {
              $months[] = $i;
            }
  } else if ( ($_POST['taskMonth'] > 0) && ($_POST['taskMonth'] < 13) ) {
            $months[] = $_POST['taskMonth'];
  }
} else {
  $month = '*'; // $_POST['taskMonth'] was left blank, set it to *
}

// week
if (isset($_POST['taskWeek']) && ($_POST['taskWeek'] != '') && ($_POST['taskWeek'] != '*')) {
  $_POST['taskWeek'] = str_replace(' ', '', $_POST['taskWeek']);
  if (strpos($_POST['taskWeek'], ',')) { // specific days of week
    $weeks = explode(',',$_POST['taskWeek']);
  } elseif (strpos($_POST['taskWeek'], '-')) { // days of the week range
    $x = explode('-',$_POST['taskWeek']);
    for ($i = $x[0]; $i <= $x[1]; $i++) {
      $weeks[] = $i;
    }
  } else if ( ($_POST['taskWeek'] >= 0) && ($_POST['taskWeek'] < 8) ) {
    $weeks[] = $_POST['taskWeek'];
  }
} else {
  $week = '*'; // $_POST['taskWeek'] was left blank, set it to *
}

So what we end up with is the possibility for either a * entity for $day, $month, $week or an array of dates.
Now the fun begins. I'm note sure if this way is the most efficient way of checking the dates. I am satisfied with this code for my first iteration. But even as I am writing this post, I have been modifying the code, so I expect to have revised this multiple times before I put it into production.

// hard coded start date for this example
$start = '02/01/2014';
// transform date to an array
$x = explode('/', $start);

// hard coded the end date as 2 years from start date
$end = ++$x[2] . "-12-31";

// how many days between start and end
$datetime1 = new DateTime($start);
$datetime2 = new DateTime($end);
$interval = $datetime1->diff($datetime2);
$max = $interval->format('%a');

for ($i = 0; $i < $max; $i++) {
  $string = "+" . $i . " day";
  $new = date("Y-m-d", strtotime($string, strtotime($start)));
  $day_of_week = date('N', strtotime($new)); // numeric day of week: Sunday = 0
  $x = explode('-', $new);
  if ($day == '*') {
    if ($month == '*') {
      if ($week == '*') {
        print "\n" . $new . "\n";
      } else {
        foreach ($weeks as $key=>$val) {
          if ($day_of_week == $val) {
            print "\n" . $new . "\n";
          }
        }
      }
    } else {
      if ($week == '*') {
        foreach ($months as $key=>$val) {
          if ($x[1] == $val) {
            print "\n" . $new . "\n";
          }
        }
      } else {
        foreach ($months as $key=>$val) {
          if ($x[1] == $val) {
            foreach ($weeks as $k=>$v) {
              if ($day_of_week == $v) {
                print "\n" . $new . "\n";
              }
            }
          }
        }
      }
    }
  } else {
    foreach ($days as $key=>$val) {
      if ($val == $x[2]) {
        if ($month == '*') {
          if ($week == '*') {
            print "\n" . $new . "\n";
          } else {
            foreach ($weeks as $k=>$v) {
              if ($day_of_week == $v) {
                print "\n" . $new . "\n";
              }
            }
          }
        } else {
          foreach ($months as $k1=>$v1) {
            if ($x[1] == $v1) {
              if ($week == '*') {
                print "\n" . $new . "\n";
              } else {
                foreach ($weeks as $k2=>$v2) {
                  if ($day_of_week == $v2) {
                    print "\n" . $new . "\n";
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

So now, if you execute this code you will get a print out of every date between the start date and two years in the future. Not very exciting. So lets play around with some of the hard coded day of month, month, and day of week variables.

First, lets see what happens if we set the recurrence to be the first day of the month every June and December

$day = '1';
$days = array();
$days[] = '1'
$month = '6,12';
$months = array();
$months[] = '6';
$months[] = '12';
$week = '*';
$weeks = array();

Notice that I manually added a $days and $months array element.
When you execute this code, you will get a print out like this:

2014-06-01

2014-12-01

2015-06-01

2015-12-01

One more example, what if we wanted to do something on the first day of the month, but only if that day was a Monday?

$day = '1';
$days[] = '1';
$month = '*';
$week = '1';
$weeks = array();
$weeks[] = '1'; // numeric day of week: Sunday = 0

Notice that I set a day array element and a weeks array element for this example. And here is the output:

2014-09-01

2014-12-01

2015-06-01

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*