cronoklee
cronoklee

Reputation: 6702

Calculate the number of months between two dates in PHP?

Without using PHP 5.3's date_diff function (I'm using PHP 5.2.17), is there a simple and accurate way to do this? I am thinking of something like the code below, but I don't know how to account for leap years:

$days = ceil(abs( strtotime('2000-01-25') - strtotime('2010-02-20') ) / 86400);
$months = ???;

I'm trying to work out the number of months old a person is.

Upvotes: 54

Views: 149165

Answers (17)

Eazael
Eazael

Reputation: 104

You can make the calculations manually, for which I created a simple class. You input the start and end dates, and get the amount of months between those two dates.

The formulate converts the days into decimal point values taking into account the amount of days the corresponding month had in that specific year.

Here is the class:

    class TimeHelper
{

    private $years = 0;

    private $months = 0;

    private $days = 0;

    public function getMonthsDiff($start, $end) : float
    {
        $this->resetDifValues();
        $start = new \DateTime($start);
        $end = new \DateTime($end);

        $this->getYears($end, $start);
        $this->getMonths($end, $start);
        $this->getDays($end, $start);

        $totalMonths = ($this->years * 12) + $this->months;
        $totalMonths = floatval($totalMonths) + (floatval($this->days) / floatval($this->getDaysInStartMonth($start)));
        return $totalMonths;
    }

    /**
     * @return void
     */
    private function resetDifValues(): void
    {
        $this->years = 0;
        $this->months = 0;
        $this->days = 0;
    }

    /**
     * @param \DateTime $end
     * @param \DateTime $start
     * @return void
     */
    private function getYears(\DateTime $end, \DateTime $start): void
    {
        $this->years = intval($end->format('Y')) - intval($start->format('Y'));
        if ($this->years < 0) {
            throw new \InvalidArgumentException('The end date must be greater than the start date');
        }
    }

    /**
     * @return void
     */
    private function convertYearToMonths(): void
    {
        if ($this->years <= 0) {
            throw new \InvalidArgumentException('The end date must be greater than the start date');
        }
        $this->years--;
        $this->months += 12;
    }

    /**
     * @param \DateTime $end
     * @param \DateTime $start
     * @return void
     */
    private function getMonths(\DateTime $end, \DateTime $start): void
    {
        $this->months = intval($end->format('m')) - intval($start->format('m'));
        if ($this->months < 0) {
            $this->convertYearToMonths();
        }
    }

    /**
     * @param \DateTime $end
     * @return void
     */
    private function convertMonthInDays(\DateTime $end, \DateTime $start): void
    {
        if ($this->months <= 0) {
            $this->convertYearToMonths();
        }
        $this->months--;
        $daysInMonthBeforeEndDate = $this->getDaysInStartMonth($start);
        $this->days += $daysInMonthBeforeEndDate;
    }

    /**
     * @param \DateTime $end
     * @param \DateTime $start
     * @return void
     */
    private function getDays(\DateTime $end, \DateTime $start): void
    {
        $this->days = intval($end->format('d')) - intval($start->format('d'));
        if ($this->days < 0) {
            $this->convertMonthInDays($end, $start);
        }
    }


    /**
     * @param \DateTime $start
     * @return int
     */
    private function getDaysInStartMonth(\DateTime $start): int
    {
        $monthBeforeEndDate = intval($start->format('m'));
        return cal_days_in_month(CAL_GREGORIAN, $monthBeforeEndDate, $start->format('Y'));
    }

}

To get the result you just have to instantiate the class and call the method getMonthsDiff.

$timeHelper = new TimeHelper();
$monthsDiff = $timeHelper->getMonthsDiff($project->dt_start, $project->dt_end);

This will give you the amount of months in float format like 1.0 or 10.935...

Upvotes: 0

Mukesh Goswami
Mukesh Goswami

Reputation: 19

$d1 = date('Y-m-d', strtotime('2015-02-13'));

$d2 = date('Y-m-d');

$d1 = new DateTime($d1); $d2 = new DateTime($d2);

$interval = $d2->diff($d1);

$interval->m = ($interval->m + (12 * $interval->y));

$months = $interval->format('%m');

Upvotes: 0

Mike Smit
Mike Smit

Reputation: 87

I just want to share the function I wrote. You can modify it to get both months and years by entering the related datetime format to define the modifier aka modify("+1 something").

/**
 * @param DateTimeInterface $start a anything using the DateTime interface
 * @param DateTimeInterface|null $end to calculate the difference to
 * @param string $modifier for example: day or month or year.
 * @return int the count of periods.
 */
public static function elapsedPeriods(
                       DateTimeInterface $start, 
                       DateTimeInterface $end = null, 
                       string            $modifier = '1 month'
): int
{

    // just an addition, in case you just want the periods up untill now
    if ($end === null ) {
        $end = new DateTime();
    }

    // we clone the start, because we dont want to change the actual start
    // (objects are passed by ref by default, so if you forget this you might 
    // mess up your data)
    $cloned_start = clone $start;

    // we create a period counter, starting at zero, because we want to count 
    // periods, assuming the first try, makes one period. 
    // (week, month, year, et cetera) 
    $period_count = 0;
    
    // so while our $cloned_start is smaller to the $end (or now).
    // we will increment the counter
    while ($cloned_start < $end) {
        // first off we increment the count, for the first iteration could end 
        // the cycle
        $period_count++;
        // now we modify the cloned start
        $cloned_start->modify(sprintf("+1 %s", $modifier));
    }

    return $period_count; // return the count
}

cheers

Upvotes: 0

Claude Amadu
Claude Amadu

Reputation: 1

function date_duration($date){
    $date1 = new DateTime($date);
    $date2 = new DateTime();
    $interval = $date1->diff($date2);
    if($interval->y > 0 and $interval->y < 2){
        return $interval->y.' year ago';
    }else if($interval->y > 1){
        return $interval->y.' years ago';
    }else if($interval->m > 0 and $interval->m < 2){
        return $interval->m.' month ago';
    }else if($interval->m > 1){
        return $interval->y.' months ago';
    }else if($interval->d > 1){
        return $interval->d.' days ago';
    }else{
        if($interval->h > 0 and $interval->h < 2){
            return $interval->h.' hour ago';
        }else if($interval->h > 1){
            return $interval->h.' hours ago';
        }else{
            if($interval->i > 0 and $interval->i < 2){
                return $interval->i.' minute ago';
            }else if($interval->i > 1){
                return $interval->i.' minutes ago';
            }else{
                return 'Just now';
            }
        }
    }
}

Returns 11 Months ago, 2 years ago, 5 minutes ago style of date differentiation

Example:

echo date_duration('2021-02-28 14:59:00.00');

Will return '1 Month ago' depending on your current month

Upvotes: 0

Brian Clincy
Brian Clincy

Reputation: 126

My solution was mix of a few answers. I did not want to do a loop especially when I have all the data in the $interval->diff, I just did the math, and the number of months could be negative if in my case so this was my approach.

    /**
     * Function will give you the difference months between two dates
     *
     * @param string $start_date
     * @param string $end_date
     * @return int|null
     */
    public function get_months_between_dates(string $start_date, string $end_date): ?int
    {
        $startDate = $start_date instanceof Datetime ? $start_date : new DateTime($start_date);
        $endDate = $end_date instanceof Datetime ? $end_date : new DateTime($end_date);
        $interval = $startDate->diff($endDate);
        $months = ($interval->y * 12) + $interval->m;
        
       return $startDate > $endDate ? -$months : $months;
        
    }

Upvotes: 1

Evgeny
Evgeny

Reputation: 4010

Here is my solution. It only checks years and months of dates. So, if one date is 31.10.15 and other is 02.11.15 it returns 1 month.

function get_interval_in_month($from, $to) {
    $month_in_year = 12;
    $date_from = getdate(strtotime($from));
    $date_to = getdate(strtotime($to));
    return ($date_to['year'] - $date_from['year']) * $month_in_year -
        ($month_in_year - $date_to['mon']) +
        ($month_in_year - $date_from['mon']);
}

Upvotes: 2

Christopher
Christopher

Reputation: 202

This is how I ended up solving it. I know I'm a little late but I hope this saves someone a lot time and lines of code.
I used DateInterval::format to display a human readable countdown clock in years, months, and days. Check https://www.php.net/manual/en/dateinterval.format.php for the format table to see your options on how to modify your return values. Should give you what you're looking for.

$origin = new DateTime('2020-10-01');
$target = new DateTime('2020-12-25');
$interval = $origin->diff($target);
echo $interval->format('%y years, %m month, %d days until Christmas.');

Outputs: 0 years, 2 month, 24 days

Upvotes: 5

Jonny
Jonny

Reputation: 16298

To calculate the number of CALENDAR MONTHS (as also asked here) between two dates, I usually end up doing something like this. I convert the two dates to strings like "2020-05" and "1994-05", then get their respective result from the below function, then run a subtraction of those results.

/**
 * Will return number of months. For 2020 April, that will be the result of (2020*12+4) = 24244
 * 2020-12 = 24240 + 12 = 24252
 * 2021-01 = 24252 + 01 = 24253
 * @param string $year_month Should be "year-month", like "2020-04" etc.
 */
static private function calculate_month_total($year_month)
{
    $parts = explode('-', $year_month);
    $year = (int)$parts[0];
    $month = (int)$parts[1];
    return $year * 12 + $month;
}

Upvotes: 0

NIKHIL PARIHAR
NIKHIL PARIHAR

Reputation: 522

Here is my solution. It checks both years and months of dates and find difference.

 $date1 = '2000-01-25';
 $date2 = '2010-02-20';
 $d1=new DateTime($date2); 
 $d2=new DateTime($date1);                                  
 $Months = $d2->diff($d1); 
 $howeverManyMonths = (($Months->y) * 12) + ($Months->m);

Upvotes: 34

Bhuvan Arora
Bhuvan Arora

Reputation: 154

$date1 = '2000-01-25';
$date2 = '2010-02-20';

$ts1 = strtotime($date1);
$ts2 = strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

$diff = (($year2 - $year1) * 12) + ($month2 - $month1);

If Month switched from Jan to Feb above code will return you the $diff = 1 But if you want to consider next month only after 30 days then add below lines of code along with above.

$day1 = date('d', $ts1);
$day2 = date('d', $ts2);

if($day2 < $day1){ $diff = $diff - 1; }

Upvotes: 0

gattsbr
gattsbr

Reputation: 3772

I recently needed to calculate age in months ranging from prenatal to 5 years old (60+ months).

Neither of the answers above worked for me. The first one I tried, which is basically a 1 liner for deceze's answer

$bdate = strtotime('2011-11-04'); 
$edate = strtotime('2011-12-03');
$age = ((date('Y',$edate) - date('Y',$bdate)) * 12) + (date('m',$edate) - date('m',$bdate));
. . .

This fails with the set dates, obviously the answer should be 0 as the month mark (2011-12-04) hasn't been reached yet, how ever the code returns 1.

The second method I tried, using Adam's code

$bdate = strtotime('2011-01-03'); 
$edate = strtotime('2011-02-03');
$age = 0;

while (strtotime('+1 MONTH', $bdate) < $edate) {
    $age++;
    $bdate = strtotime('+1 MONTH', $bdate);
}
. . .

This fails and says 0 months, when it should be 1.

What did work for me, is a little expansion of this code. What I used is the following:

$bdate = strtotime('2011-11-04');
$edate = strtotime('2012-01-04');
$age = 0;

if($edate < $bdate) {
    //prenatal
    $age = -1;
} else {
    //born, count months.
    while($bdate < $edate) {
        $age++;
        $bdate = strtotime('+1 MONTH', $bdate);
        if ($bdate > $edate) {
            $age--;
        }
    }
}

Upvotes: 1

Khuat Huu Vinh
Khuat Huu Vinh

Reputation: 31

my function to resolve issue

function diffMonth($from, $to) {

        $fromYear = date("Y", strtotime($from));
        $fromMonth = date("m", strtotime($from));
        $toYear = date("Y", strtotime($to));
        $toMonth = date("m", strtotime($to));
        if ($fromYear == $toYear) {
            return ($toMonth-$fromMonth)+1;
        } else {
            return (12-$fromMonth)+1+$toMonth;
        }

    }

Upvotes: 3

manix
manix

Reputation: 14747

How about this:

$d1 = new DateTime("2009-09-01");
$d2 = new DateTime("2010-09-01");
$months = 0;

$d1->add(new \DateInterval('P1M'));
while ($d1 <= $d2){
    $months ++;
    $d1->add(new \DateInterval('P1M'));
}

print_r($months);

Upvotes: 1

Logan Wayne
Logan Wayne

Reputation: 5991

Follow up on the answer of @deceze (I've upvoted on his answer). Month will still count as a whole even if the day of the first date didn't reached the day of the second date.

Here's my simple solution on including the day:

$ts1=strtotime($date1);
$ts2=strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

$day1 = date('d', $ts1); /* I'VE ADDED THE DAY VARIABLE OF DATE1 AND DATE2 */
$day2 = date('d', $ts2);

$diff = (($year2 - $year1) * 12) + ($month2 - $month1);

/* IF THE DAY2 IS LESS THAN DAY1, IT WILL LESSEN THE $diff VALUE BY ONE */

if($day2<$day1){ $diff=$diff-1; }

The logic is, if the day of the second date is less than the day of the first date, it will reduce the value of $diff variable by one.

Upvotes: 0

pollux1er
pollux1er

Reputation: 5921

This is a simple method I wrote in my class to count the number of months involved into two given dates :

public function nb_mois($date1, $date2)
{
    $begin = new DateTime( $date1 );
    $end = new DateTime( $date2 );
    $end = $end->modify( '+1 month' );

    $interval = DateInterval::createFromDateString('1 month');

    $period = new DatePeriod($begin, $interval, $end);
    $counter = 0;
    foreach($period as $dt) {
        $counter++;
    }

    return $counter;
}

Upvotes: 27

Adam
Adam

Reputation: 1264

Like this:

$date1 = strtotime('2000-01-25');
$date2 = strtotime('2010-02-20');
$months = 0;

while (($date1 = strtotime('+1 MONTH', $date1)) <= $date2)
    $months++;

echo $months;

If you want to include days to, then use this:

$date1 = strtotime('2000-01-25');
$date2 = strtotime('2010-02-20');

$months = 0;

while (strtotime('+1 MONTH', $date1) < $date2) {
    $months++;
    $date1 = strtotime('+1 MONTH', $date1);
}

echo $months, ' month, ', ($date2 - $date1) / (60*60*24), ' days'; // 120 month, 26 days

Upvotes: 4

deceze
deceze

Reputation: 522016

$date1 = '2000-01-25';
$date2 = '2010-02-20';

$ts1 = strtotime($date1);
$ts2 = strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

$diff = (($year2 - $year1) * 12) + ($month2 - $month1);

You may want to include the days somewhere too, depending on whether you mean whole months or not. Hope you get the idea though.

Upvotes: 124

Related Questions