Friday, December 13, 2013

Calculate next due date

Given a start date and an interval calculate the next due date relative to now().
The biggest issue is when having interval of 1 (or more) full months. For example: adding one month to the date of 2013-01-31 you'll end up with the date 2013-03-03 when you expect to be 2013-02-28.

Here is the function I wrote for solving this problem. You can enhance this to support year or hours or even ice ages :P
<?php
/**
 * Function calculateNextDueDate - given a start date and an interval type and value
 *     calculate next date in the future.
 *
 * @param DateTime $startDate
 * @param string   $intervalType
 * @param integer  $intervalValue
 *
 * @return DateTime
 */
function calculateNextDueDate($startDate, $intervalType, $intervalValue)
{
    $currentDate = new DateTime();
    $currentDay = $currentDate->format('d');
    $currentDate->sub(new DateInterval('P' . ($currentDay - 1) . 'D'));

    $processDate = clone $startDate;
    $dayOfTheMonth = $processDate->format('d');

    $periodString = 'P' . (int)$intervalValue;
    switch ($intervalType) {
        case 'month':
            $periodString .= 'M';
            if (28 < $dayOfTheMonth) {
                $modifyInterval = new DateInterval('P' . ($dayOfTheMonth - 28) . 'D');
                $processDate->sub($modifyInterval);
            }
            break;

        case 'day':
            $periodString .= 'D';
            break;
    }
    $interval = new DateInterval($periodString);

    while ($currentDate->getTimestamp() >= $processDate->getTimestamp()) {
        $processDate = $processDate->add($interval);
    }
    if ($processDate->format('d') < $currentDay) {
        $processDate = $processDate->add($interval);
    }

    if ('month' == $intervalType && 28 < $dayOfTheMonth) {
        $lastDateOfMonth = $processDate->format('t');
        if ($dayOfTheMonth > $lastDateOfMonth) {
            $daysToAdd = $lastDateOfMonth - 28;
        }
        else {
            $daysToAdd = $dayOfTheMonth - 28;
        }
        $processDate->add(new DateInterval('P' . $daysToAdd . 'D'));
    }

    return $processDate;
}

No comments:

Post a Comment