Jason
Jason

Reputation: 587

PHP Date diff with a difference

Please bear with me as I try to explain my predicament. I need to somehow get the difference between two dates by reversing a function I have to add months and years?

Problem The date add function provided with PHP >= 5.3 does not add dates in the manner I require. Example: +3 Months to 30Nov = 2Mar

Solution I use the function below (code ref 2) to produce the results I need. Example: +3 Months to 30Nov = 28Feb

However when using the below (code ref 1) to calculate the difference it does so based on the addition function provide with PHP >= 5.3 in that I get 2 instead of 3 months difference between 30Nov and 28Feb.

If anyone could help come up with an accurate date diff based on the code ref 2 logic I, and I'm sure others in the same boat would be very grateful.

<< CODE REF 1 >>

<?php
$datetime1 = new DateTime('2000-11-30');
$datetime2 = new DateTime('2001-02-28');
$interval = $datetime1->diff($datetime2);
echo $interval->format('%m'); // 2

$datetime1 = new DateTime('2000-11-30');
$datetime2 = new DateTime('2001-03-02');
$interval = $datetime1->diff($datetime2);
echo $interval->format('%m'); // 3
?>

<< CODE REF 2 >>

<?php       
$datetime = new DateTime('2000-11-30');
$y = 0;
$m = 3;
$d = 0;
if ($d>0){
    $datetime->add(new DateInterval('P'.$d.'D'));
}
if ($d<0){
    $datetime->sub(new DateInterval('P'.$d.'D'));
}
if ($y!=0){
    $init=clone $datetime;
       $modifier=$y.' years';
       $datetime->modify($modifier);
       while ($datetime->format('m')!=$init->format('m')){
        $datetime->modify('-1 day');
    }
}
if ($m!=0){
    $init=clone $datetime;
    $modifier=$m.' months';
    $back_modifier =-$m.' months';
    $datetime->modify($modifier);
    $back_to_init= clone $datetime;
    $back_to_init->modify($back_modifier);
    while ($init->format('m')!=$back_to_init->format('m')){
        $datetime->modify('-1 day');
        $back_to_init= clone $datetime;
        $back_to_init->modify($back_modifier);   
    }
}
echo $datetime->format('Y-m-d'); // 2001-02-28
?>

SOLUTION FOUND

By changing the way we use the original function we instead find out the number of years and months as desired, many thanks to all the helpful suggestions. The reason for y=1 and m=4 is because the year starts at one and the month starts at one, otherwise it would be 0 and 3 as originally requested if starting at zero.

<?php
function date_yr_mth($date1='2000-11-30',$date2='2001-02-28'){
    $y1 = date("Y", strtotime($date1));
    $m1 = date("n", strtotime($date1));
    $d1 = date("j", strtotime($date1));
    $y2 = date("Y", strtotime($date2));
    $m2 = date("n", strtotime($date2));
    $d2 = date("j", strtotime($date2));
    $t2 = date("t", strtotime($date2));
    $cm_diff = $m2-$m1;
    $cy_diff = $y2-$y1;
    if ($d2>=$d1){
        $add_mth1 = 1;
    }else{
        $add_mth1 = 0;
    }
    $add_mth2 = 12*$cy_diff+$cm_diff;
    if ($d2==$t2 && $d2<$d1){
        $add_mth3 = 1;
    }else{
        $add_mth3 = 0;
    }
    $total_mths = $add_mth1+$add_mth2+$add_mth3;
    $arr = array();
    $arr['y'] = floor(($total_mths-1)/12)+1;
    $arr['m'] = $total_mths-($arr['y']-1)*12;
    print_r($arr);
    // [y] => 1
    // [m] => 4
}
?>

Upvotes: 2

Views: 1565

Answers (2)

vascowhite
vascowhite

Reputation: 18430

The way to approach this is to extend DateTime and overide the add() and sub() methods to behave as you want them to. That, after all is one of the advantages of OOP.

The way to get your desired behaviours is to set the day of the month to the 1st before doing calling add() or sub() and then restoring the original or highest possible day afterwards.

My first attempt is below, not thoroughly tested, but adding 1 month to 31st Jan gave 28th Feb, which, I believe is your desired behaviour:-

class MyDateTime extends \DateTime
{
    public function add($interval)
    {
        $oldDay = (int)$this->format('d');
        $this->setDate((int)$this->format('Y'), (int)$this->format('m'), 1);
        parent::add($interval);
        $maxDay = (int)$this->format('t');
        if($oldDay > $maxDay){
            $this->setDate((int)$this->format('Y'), (int)$this->format('m'), $maxDay);
        } else {
            $this->setDate((int)$this->format('Y'), (int)$this->format('m'), $oldDay);
        }
        return $this;
    }

    public function sub($interval)
    {
        $oldDay = (int)$this->format('d');
        $this->setDate((int)$this->format('Y'), (int)$this->format('m'), 1);
        parent::sub($interval);
        $maxDay = (int)$this->format('t');
        if($oldDay > $maxDay){
            $this->setDate((int)$this->format('Y'), (int)$this->format('m'), $maxDay);
        } else {
            $this->setDate((int)$this->format('Y'), (int)$this->format('m'), $oldDay);
        }
        return $this;
    }

    public function diff($dateTime2, $absolute = false)
    {
        if((int)$this->format('t') > (int)$dateTime2->format('t')){
            $this->setDate((int)$this->format('Y'), (int)$this->format('m'), (int)$dateTime2->format('t'));
        }
        if((int)$this->format('t') < (int)$dateTime2->format('t')){
            $dateTime2->setDate((int)$dateTime2->format('Y'), (int)$dateTime2->format('m'), (int)$this->format('t'));
        }
        return parent::diff($dateTime2, $absolute);
    }
}

Here is a working example using your exampe dates

Here is an example using the diff() method as you can see it gives a 3 month difference. You can also see that adding the resulting DateInterval to the original date results in the second date.

The sub() method may require a bit more thought, but I don't have time just now. I'll take a more thorough look if I get a few spare minutes later.

Upvotes: 2

Martin Tale
Martin Tale

Reputation: 907

This way you would get the absolute difference between months without taking in account the day.

 <?php
function getMonthDiff($firstMonth, $secondMonth)
{
    $firstMonth  = $firstMonth->format("Y") * 12 + $firstMonth->format("m");
    $secondMonth = $secondMonth->format("Y") * 12 + $secondMonth->format("m");

    return abs($firstMonth - $secondMonth);
}


$datetime1 = new DateTime('2000-11-30');
$datetime2 = new DateTime('2001-02-28');
echo getMonthDiff($datetime1, $datetime2);
echo "<br />";
$datetime1 = new DateTime('2000-11-30');
$datetime2 = new DateTime('2001-03-02');
echo getMonthDiff($datetime1, $datetime2);

Upvotes: 1

Related Questions