4

I failed to find a proper solution to this issue. As you see in Example #3 in the PHP documentation, they state that one must beware when adding months using the DateInterval in DateTime::add.

There's not really any explanation for why the method's behavior is as such and what I can do to avoid this, which I find to be an error at first sight.

Anyone have some insight into this?

Charles
  • 50,943
  • 13
  • 104
  • 142
Ronni Egeriis Persson
  • 2,209
  • 1
  • 21
  • 43
  • Probably very similar to http://stackoverflow.com/questions/7119777/php-strotime-1-month-behaviour – Mike B Feb 14 '12 at 18:42
  • *(reference)* http://www.gnu.org/software/tar/manual/html_node/Relative-items-in-date-strings.html#SEC120 – Gordon Feb 14 '12 at 18:54
  • 1
    @MikeB It's different from strtotime since it's a new implementation. I was looking for a solution equal to what strtotime gives me, yet smarter. The OO DateTime class would be preferable. – Ronni Egeriis Persson Feb 14 '12 at 21:43

2 Answers2

7

The issue is that each month can have a different number of days in them. The question is what you're doing when you want to increment a date by 1 month. Per the PHP documentation if you're on January 31st (or 30th) and you add 1 month, what is the expected behavior?

February only has 29 days in it. Do you want to be set to the last day of the month? You're generally safer incrementing by a set number of days if that's what you're looking for, or a static date based on the current date. Without knowing what you're trying to accomplish when you increment your month, it's tough to say how to watch for an error.

EDIT:
As someone mentions in the similar post commented by Mike B above, you probably want to do something where you (in pseudocode):

 1) Use cal_days_in_month() for the next month and save that number to a variable x
 2) If x >= current billing DOB, increment and be done
 3) DateTime::modify('last day')  (havent used this before but something along these lines) to set the date to the last date of the next month (set it to the 1st of the next month, then last day?)

Worth noting is that if you use the variable here as the new billing value, you'll wipe out your original value. I would save an extra DB value that's "first billing date" or just "billing_day_of_month" or something, and use that to figure out the day of month that you should be looking at

DaOgre
  • 2,080
  • 16
  • 25
  • You know, this is very obvious. What I wanted to achieve is for my customers to pay monthly, thus I need to increment their subscriptions expiration date with 1 month. However it's quite obviously a bad implementation and confusing for customer, thus I need to figure out another solution. – Ronni Egeriis Persson Feb 14 '12 at 21:47
  • 1
    There are many paradoxes here I think. The solution I came up with is that if a user signs up on the 29th, 30th or 31st of any month, they will be billed for 3, 2 or 1 day and their subscription day would be set to the 28th each month. I choose this solution, rather than setting each customers billing day to the 1st, because the server which will process the payments shouldn't be busy only one day a month. – Ronni Egeriis Persson Feb 15 '12 at 17:28
  • Would it better to just increment by 30 days rather than mess around with months? We can then avoid this situation. – CMCDragonkai Dec 02 '13 at 05:05
0

If your goal is to strictly increment by user-friendly months (thus, 3 months from January 21st should be April 21st), with the exception that shorter membership months get shortened (thus, 1 month from January 31st is February 28th/29th), then you only need to go back a few days if you crossed over into the next month:

function addMonths($date,$months) {
  $orig_day = $date->format("d");
  $date->modify("+".$months." months");
  while ($date->format("d")<$orig_day && $date->format("d")<5)
    $date->modify("-1 day");
}

$d = new DateTime("2000-01-31");
addMonths($d,1);
echo $d->format("Y-m-d"); // 2000-02-29