0

I've just discovered a problem with the usually used method to sum months to a PHP Date. If you search on google or this forum, you usually find somethings like these:

$date = strtotime(date("Y-m-d", strtotime($date)) . " +1 month");

or

$months = DateInterval::createFromDateString('1 month');
$dateInDateTime->add($months);

Both approach are not correct, in my opinion.

For example in my code I have to increment 3 times the month of a starting date beginning with last day of April and return the last day of that months. So my code generates this results:

  • 2017-04-30
  • 2017-05-31
  • 2017-07-31

The second time the script add +1 month to date, goes from 2017-05-31 to 2017-07-01 because 31-05 + 30 days is over the last day of JUNE.

What Im expecting is 06-30 because you are summing MONTHS not DAYS and if you have an overflow, the code has to correct it, not me. This is a common error that explode when you manage February or December (due to change of year).

Im expecting a script that increment month. So if I have 2017-03-23 and sum +1 month, I get 2017-04-23 and if I sum +1 month to 2017-03-31 I got 2017-04-30.

So. Pay attention when using this functions.

MrMime
  • 665
  • 4
  • 10
  • 24
  • 1
    Take a look at this topic https://stackoverflow.com/questions/1686724/how-to-find-the-last-day-of-the-month-from-date you may be able to use it in case the first date is an end of the month date – RST Jun 09 '17 at 16:16
  • I am not sure this message was about last day of the month, but simply adding a month to a date relatively – Nicolas D Jun 09 '17 at 16:24
  • Take it from me, if you combine these scenarios the right way you can solve the issue. But hey, it is not my problem :) – RST Jun 09 '17 at 16:25
  • I agree! we are more in a philosophical issue that a coding issue anyway :) – Nicolas D Jun 09 '17 at 16:27

3 Answers3

0

I think you are trying something dangerous.

What s going on for february? if you want all the time to change only month number it will break for latest days of this month, same for months with 30 days instead of 31...

You have to think about your approach in another way, because changing the month alone won't make an existing date sometimes.

+30 days seems to be the best thing to do

Nicolas D
  • 1,182
  • 18
  • 40
  • Not downvoting but, how is this an answer? Please remove and post as comment. – RST Jun 09 '17 at 16:17
  • well there is no question, so how are we supposed to answer to something? I could downvote the post but it seems still good to reply to it for the community no? – Nicolas D Jun 09 '17 at 16:20
0

What Im expecting is 06-30 because you are summing MONTHS not DAYS and if you have an overflow, the code has to correct it, not me.

PHP corrects it, indeed. It never returns 31st of June as such a date doesn't exist. It corrects it to 1st of July.

What you apparently expect is that when you add 1 month to the last day of a month to get the last day of the next month. But this doesn't make any sense.

What should strtotime('2017-06-30 +1 month') return?

2017-07-31, because you are adding 1 month to the last day of June or 2017-07-30 because you are adding 1 month to the 30th day of June?

The times runs forward, counting the days from the end of the month is not natural. Sometimes it's useful but not that many times. And there always is a better solution: subtract 1 day from the first day of the next month. This way you don't have to do any correction or care about months with different number of days or even about leap years.

axiac
  • 68,258
  • 9
  • 99
  • 134
  • Hi. If you sum +1 month to 2017-06-30 Im expecting 2017-07-30 and if you sum +1 month to 2017-02-28 im expecting 2017-03-28. The script has to check: if (arriving month has the date) return the date; Else return the nearest date of arrivalMonth; – MrMime Jun 12 '17 at 07:40
  • Well, if you add 1 month to `2017-05-31` you get `2017-06-31` (which normalizes to `2017-07-01`) because it is **the 31st day of May**. If you want to add 1 month to **the last day of May** then count backwards. The last day of May is one day before the first day of June. Add 1 month to `2017-06-01` to get the first day of July then subtract 1 day to get the last day of June. As easy as that. – axiac Jun 12 '17 at 09:01
  • If you had a look at my script, Im using this simple if: $arrivalMonthDays = cal_days_in_month(CAL_GREGORIAN, $cMonth, $cYear); if ($cDay >= $arrivalMonthDays) $cDay = $arrivalMonthDays; So IF cDay (the arrivalDay) is greater than the max days for that month (the calc mange 29 febbruary issue too), cDay will became the last day of that month, without overflow to next month. – MrMime Jun 12 '17 at 12:49
-1

This is the function I wrote:

  //it accept negative month value
public static function realAddMonthsToDate($month,$dateToModify, 
    $dateFormatInput = DEFAULT_SQL_DATE_FORMAT, $dateFormatOutput = DEFAULT_SQL_DATE_FORMAT)
    {
        $currentDate = DateTime::createFromFormat($dateFormatInput, $dateToModify);
        $cDay = $currentDate->format('d');
        $cMonth = $currentDate->format('m');
        $cYear  = $currentDate->format('Y');
        $monthRest = $month;
        $yearOffset = 0;
        if ($month > 12)
        {
            $yearOffset = floor($month / 12);
            $monthRest = $month - ($yearOffset * 12);
        }

        $cMonth += $monthRest;
        if ($cMonth > 12) {
            $cMonth = $cMonth - 12;
            $cYear += 1;
        }
        if ($cMonth <= 0)
        {
            $cMonth = 12 + $cMonth;
            $cYear -= 1;
        }
        $cYear += $yearOffset; 

        $arrivalMonthDays = cal_days_in_month(CAL_GREGORIAN, $cMonth, $cYear);
        if ($cDay >= $arrivalMonthDays) $cDay = $arrivalMonthDays;
        $newDate = new DateTime($cYear.'-'.$cMonth.'-'.$cDay);
        return $newDate->format($dateFormatOutput);
    }
MrMime
  • 665
  • 4
  • 10
  • 24