#native_company# #native_desc#
#native_cta#

Recurring Date Checker

By Cullen Linn
on May 6, 2008

Version: 1.0

Type: Function

Category: Calendars/Dates

License: GNU General Public License

Description: This function lets you check whether an date event should recur on a specified date. This is handy for calendar, billing and reminder applications.

/**
 * Returns whether or not a an item with the specified information should recur / repeat.
 * 
 * Example 1:
 *   You have an event, such as a birthday that repeats each year and would like to know
 *   if today is the day it should repeat on.
 *   
 *   //$bday = '2008-10-29', $today = '2008-10-29'
 *   recursOn($today, $bday, 'year', 1, null, true) //returns true
 *
 * Example 2:
 *   You have a collection of invoices which may need to be reproduced on a
 *   recurring basis, such as a monthly subscription. The following snippet
 *   will look through a collection of invoices which recur every month
 *   on the Xth day of the month and insert any invoices that should recur
 *   today into an array (shouldRecur) for additional processing.
 *
 *   $shouldRecur = array();
 *   foreach ($invoices as $invoice) {
 *      if (recursOn(date('Y-m-d'), $invoice->date, 'month', 1, null, true)) {
 *          $shouldRecur[] = $invoice;
 *      }
 *   }
 * 
 * @param $testDate a string date in the format YYYY-MM-DD that indicates the date that should be tested for recurrence
 * @param $originalDate a string date in the format YYYY-MM-DD that indicates the original date of the event
 * @param $measure the measure of the recurrence. Acceptable values are 'day', 'week', 'month', and 'year'
 * @param $frequency the number of days/weeks/months/years for the recurrence frequency, i.e., every 1 day, every 2 months, etc.
 * @param $endDate the date on which the recurrence should end
 * @param $byDate true if the recurrence should take place by date, e.g., the 15th (for month measure) or
 * March 12th (for year measure), or by its day, such as the second Tuesday of the month (for month measure)
 * or 345th day of the year (for year measure). This field does not matter for day or week measure.
*/
public function recursOn($testDate, $originalDate, $measure, $frequency, $endDate = null, $byDate = true)
{
    if (!in_array($measure, array('day', 'week', 'month', 'year'))) {
        throw new Exception('recursOn: Unsupported measure: '.$measure);
    }

    if ($frequency <= 0) {
        throw new Exception('recursOn: frequency must be greater than 0');
    }

    $testFormat = '^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}( [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}){0,1}$';
    if (!ereg($testFormat, $testDate)) {
        throw new Exception('recursOn: testDate must be in the format YYYY-MM-DD, not '.$testDate);
    }

    if (!ereg($testFormat, $originalDate)) {
        throw new Exception('recursOn: originalDate must be in the format YYYY-MM-DD, not '.$originalDate);
    }

    if ($endDate && !ereg($testFormat, $endDate)) {
        throw new Exception('recursOn: endDate must be in the format YYYY-MM-DD, not '.$endDate);
    }

    $stestDate = strtotime($testDate);
    $soriginalDate = strtotime($originalDate);
    $sendDate = strtotime($endDate);

    // if we are past the recurrence end date for this item
    if ($endDate && $endDate != '0000-00-00 00:00:00' && $sendDate < $stestDate) {
        return false;
    }

    // if the event should not start yet
    if ($stestDate < $soriginalDate) {
        return false;
    }

    switch ($measure) {
        case 'day':
            //if the number of days that has passed is an even number based on the frequency
            return (date('z', $stestDate) - date('z', $soriginalDate)) % $frequency == 0;
        case 'week':
            //check to see if this is the correct week
            if ((date('W', $stestDate) - date('W', $soriginalDate)) % $frequency == 0) {
                // are we on the correct day?
                return date('w', $stestDate) == date('w', $soriginalDate);
            }
            break;
        case 'month':
            //check to see if this is the correct month
            if ((date('n', $stestDate) - date('n', $soriginalDate)) % $frequency == 0) {
                // are we on the correct day?
                if ($byDate) {
                    // it is the Xth of the month
                    return date('j', $stestDate) == date('j', $soriginalDate);
                }

                // it is something like the second Tuesday, etc., so we get the number of
                // the week within the month, then check that along with the day of the week
                $oweekno = ceil((double)date('j', $soriginalDate) / 7);
                $sweekno = ceil((double)date('j', $stestDate) / 7);

                return $oweekno == $sweekno && date('w', $soriginalDate) == date('w', $stestDate);
            }
            break;
        case 'year':
            // check to see if this is the correct month
            if ((date('Y', $stestDate) - date('Y', $soriginalDate)) % $frequency == 0) {
                // check to see if this is the correct date
                if ($byDate) {
                    return date('m-d', $soriginalDate) == date('m-d', $stestDate);
                }

                // is it the same day of the year?
                return date('z', $soriginalDate) == date('z', $stestDate);
            }
            break;
    }

    return false;
} //end recursOn