HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpModerate

Calculate a month later than current month (without rolling over to next month)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withoutlaterthanrollingnextcalculatecurrentmonthover

Problem

I needed a script for working out billing dates over a period of time. The date should be the same day every month however if the billing date starts on a date that doesn't exist in another month, then it should be on the last day of that month. This rules out the DateTimeInterval Object, because that causes months to 'rollover' (31/01 becomes 03/03 etc.) which is unacceptable for my use case.

Try as I might, I couldn't find anything in-built to accommodate for this, so I wrote the following, however it seems like a lot of code for what I'm trying to achieve so I was wondering if anyone could suggest a better / in-built way?

$replaceVars = array(
    'effectivedate' => '30-9-2014',
    'vatpc'         => 0.15,
    'paymentamount' => 150.00,
    'duration'      => 12
);

$effectivedateObj = new DateTime($replaceVars['effectivedate']);

$iD = array(
    'd' => $effectivedateObj->format('d'),
    'm' => $effectivedateObj->format('m'),
    'y' => $effectivedateObj->format('Y')
);

$payments = '';

for ($i=0; $i  $iD['d'],
        'm' => (($iD['m'] + $i) > 12) ? (($iD['m'] + $i) % 12) : $iD['m'] + $i,
        'y' => (($iD['m'] + $i) > 12 ? $iD['y'] + 1 : $iD['y'])
    );

    if($newDate['d'] > cal_days_in_month(CAL_GREGORIAN, $newDate['m'], $newDate['y']))
    {
        $newDate['d'] = cal_days_in_month(CAL_GREGORIAN, $newDate['m'], $newDate['y']);
    }

    $paymentString = '';

    $paymentString .= 'Payment Number '.($i+1).'';
    $paymentString .= ''. $newDate['d'] .'/'. $newDate['m'] .'/'. $newDate['y'] .'';
    $paymentString .= '£'. round(( $replaceVars['paymentamount'] + ( $replaceVars['paymentamount'] * $replaceVars['vatpc'] )), 2) .'';

    $paymentString .= '';

    $payments .= $paymentString;

    echo $paymentString . '';
}


The top array and the lines that build the string are just for testing, but it still seems like a lot of code?

Also I have no idea how necessary my parenthesis are when creating ternary statements, and I do feel a bit ne

Solution

I'm inclined to keep it simple:

$start = new DateTime('30-Jan-2014', new DateTimeZone("America/Toronto"));
$end = clone $start;
$end->modify('+1 month');
while (($start->format('m')+1)%12 != $end->format('m')%12) {
    $end->modify('-1 day');
}
echo $end->format('d-M-Y');


This just checks to see if the month is the expected one, and if not, backs up one day at a time until it is. The advantage is that it's short, clear, and will work even in leap years (try 2004 to verify that).

Edit: It's also easy to make this into a user-defined function:

function addMonth($begin) 
{
    $end = clone $begin;
    $end->modify('+1 month');
    while (($begin->format('m')+1)%12 != $end->format('m')%12) {
        $end->modify('-1 day');
    }
    return $end;
}

Code Snippets

$start = new DateTime('30-Jan-2014', new DateTimeZone("America/Toronto"));
$end = clone $start;
$end->modify('+1 month');
while (($start->format('m')+1)%12 != $end->format('m')%12) {
    $end->modify('-1 day');
}
echo $end->format('d-M-Y');
function addMonth($begin) 
{
    $end = clone $begin;
    $end->modify('+1 month');
    while (($begin->format('m')+1)%12 != $end->format('m')%12) {
        $end->modify('-1 day');
    }
    return $end;
}

Context

StackExchange Code Review Q#46047, answer score: 10

Revisions (0)

No revisions yet.