Obtaining the next month in PHP

Over and over again PHP users complain that next month in PHP's date-string parser doesn't go to the next month, but instead skips to the one after next month; like in the following example:

<?php
$d = new DateTime( '2010-01-31' );
$d->modify( 'next month' );
echo $d->format( 'F' ), "\n";
?>

The output of the little script will be March. March obviously doesn't follow January as February is in between. However, the current behavior is correct. The following happens internally:

  • next month increases the month number (originally 1) by one. This makes the date 2010-02-31.

  • The second month (February) only has 28 days in 2010, so PHP auto-corrects this by just continuing to count days from February 1st. You then end up at March 3rd.

  • The formatting strips off the year and day, resulting in the output March.

This can easily be seen when echoing the date with a full date format, which will output March 3rd, 2010:

<?php
echo $d->format( 'F jS, Y' ), "\n";
?>

To obtain the correct behavior, you can use some of PHP 5.3's new functionality that introduces the relative time stanza first day of. This stanza can be used in combination with next month, fifth month or +8 months to go to the first day of the specified month. Instead of next month from the previous example, we use first day of next month here:

<?php
$d = new DateTime( '2010-01-08' );
$d->modify( 'first day of next month' );
echo $d->format( 'F' ), "\n";
?>

This script will correctly output February. The following things happen when PHP processes this first day of next month stanza:

  • next month increases the month number (originally 1) by one. This makes the date 2010-02-31.

  • first day of sets the day number to 1, resulting in the date 2010-02-01.

  • The formatting strips off the year and day, resulting in the output February.

Besides first day of, there is an equivalent last day of to go to the last day of a month. The following example demonstrates this:

<?php
$d = new DateTime( '2010-01-08' );
$d->modify( 'last day of next month' );
echo $d->format( 'F jS, Y' ), "\n";
?>

This outputs February 28th, 2010. Internally the following happens:

datebook-cover.jpg
  • next month increases the month number (originally 1) by one. This makes the date 2010-02-08.

  • last day of increases the month number by one, and sets the day number to 0, resulting in the date 2010-03-00.

  • PHP then auto-corrects the invalid day number 0 by removing one from the month and skipping to the last day of that month, resulting in 2010-02-28.

I hope this clears up some of the behaviour of PHP's Date/Time handling. For more information on Date/Time Programming with PHP, please refer to my book "php|architect's Guide to Date and Time Programming" that is available through Amazon.

Shortlink

This article has a short URL available: http://drck.me/otnmi-php-7q7

Comments

What happens if you are unable to use PHP 5.3? Is there a fix coming for existing version or are individuals left to their own devices?

@Anthony: First of all, no fix is required, as there is no bug. But of course you can do this in other PHP versions in a different way. With PHP 5.2 for example:

<?php
$d = new DateTime( '2010-01-31' );
$d->setDate( $d->format( 'Y' ), $d->format( 'm' ), 1 );
$d->modify( 'next month' );
echo $d->format( 'F' ), "\n";
?>

The third line (with the setDate() on it), uses the current year and month and sets the day to 1 prior to proceeding to the next month with the modify() call.

I beg to differ. I'm not sure what the PHP team has been smoking again, but evidently it's some great stuff.

Semantically speaking, "next month" SHOULD be, literally, the next month. Whatever happened to KISS? Languages implementing humanistic ideas should implement them in a humanistic manner. When I tell my family I'll be coming to see them "next month", there is NO confusion about which month it will be; it will be the NEXT month, regardless of the day. This behavior that you're describing here would be more adequately described as "a month from now".

I love it though. This is another great example of developers being too pedantic for their own good; you know you'll get bugs filed against this from the time it's implemented until it's either corrected or deprecated. Do you realize how many man-hours are going to be wasted (and accordingly, money), not only external to the PHP team but also within the PHP team, due to this 'non-bug'? I think it's almost a crime against programming to commit code that one knows will cause widespread confusion and misunderstanding amongst its users.

@Ken: well, well, looks like you know it all best huh. First some facts:

  1. PHP's next month stanza has always behaved like this.

  2. When I tell my family that I will be there next month I really have to tell them which day of that month.

  3. The relative string support was never meant to conform to exactly what people think what they mean.

The goal of the relative string support is to provide a rich and consistent language. And that's exactly what it is. You might not like it, but you can not argue that the behaviour is not correct. It is correct because it explicitly has been designed like how it is (following some GNU specification).

PHP 5.3 adds a whole new range of relative string stanzas just because you could not always express the date that you wanted. With the new first day of stanza you can now at least get to the date that you expected.

1: Which does not make it any easier for new users.

2: ...

3: Am I the only one that sees the irony in this statement?

Derick, please don't get me wrong here, I'm not trying to insult you (and I'm sorry if I have); rather, I should be thanking you for pointing out this addition to the language and giving background info on it (thank you). After all, I am subscribed via RSS and use xdebug frequently. However, it's things like this that make PHP frustrating.

Ken has a point. The manual for strtotime for example says 'Parse about any English textual datetime description'. The english textual interpretation of 'next month' should be february. Ideally without a day. Since we can't have dates without a day (why can't we by the way? humans are capable of dealing with day less date expressions), I'd say the 'weg van de minste verbazing' as we say in dutch would probably be the last day of the month.

PS. your submit button says 'sumbit' ;-)

I wonder if the people complaining realize that this is not a PHP-specific thing. You should probably read this:

http://www.gnu.org/software/tar/manual/html_node/Relative-items-in-date-strings.html#SEC120

For better or worse, this is the UNIX convention for adding and subtracting time intervals (not just months). Your command line date tool on your UNIX box behaves exactly the same.

SQLite also behaves in exactly the same way:

sqlite> select datetime('2010-01-31', '+1 month');
2010-03-03 00:00:00
sqlite> select datetime('2010-01-31', 'start of month', '+1 month');
2010-02-01 00:00:00

The documentation also explains this quite well <http://www.sqlite.org/lang_datefunc.html>.

Hi all.

@Derick: in the comments you say no fix is required because there is no bug. However, in the post you say 'To obtain the correct behavior', implicitly implying that the default behaviour is wrong. Anyway, I am just being pedantic.

@All: I admit it is confusing at a first sight. A developer, after all, it is a still a human being.

But after thinking a bit about it, anyway, I find the behaviour of "modify('next month')" correct, because what it is doing is take the current date (so the full date, not just the current month) and modifying it by adding a - correct me if I am wrong - solar month to it.

Given that, the problem is actually the nobody should use THAT to calculate the next month given the current date. That function call it is clearly not meant for that. It is meant for increasing the current date by a solar month. Completely different matter.

If it was something like "getNextMonthFromCurrentDate($date)" and it was still behaving like "modify('next month')", well, I would have been complaining myself.

Anyway, to be completely pedantic, if there is anything wrong (which is misleading the developers at a first sight) it is the semantic. That "next month" should be "+1 month" or stuff like that (like 'date' does, as Rasmus correctly pointed out).

But I repeat, even with the wrong semantic, there is no way you are justified in thinking that's the function you should use to get the next month.

Anyhow, for PHP <5.3 users, the page linked by Rasmus helps: "To determine the previous month more reliably, you can ask for the month before the 15th of the current month.".

I am wondering, anyway, speaking numbers, can't we just say

$nextmonth = ($currentmonth % 12) + 1;

?

@Rasmus

Lerdorf? :)

While I admit that many people smarter than myself developed GNU (and PHP), that doesn't change the fact that if you polled 100 non-developers off the street today about what next month is, failing illiteracy you should get a 100% response of February. That is my point, that there shouldn't be an altered reality for programming.

I realize that many of the concepts central to PHP originate from other systems, and yes it does make programming in PHP easier for developers who are acquainted with said systems, but what about for novice programmers unacclimated with said systems?

@Michael

No, that doesn't apply here, and it actually backs up my previous statement that the obeserved behavior is more akin to "a month from now" (aka. "+1 month") than "next month". Also, what happens when you replace 2010-01-31 in your second command with 2010-02-28? I am betting that you do not get 2010-03-01...


So as not to be one to simply critisize without offering a solution, therefore, I think a better (albeit probably not the best) solution would be to simply advance the date to the first day of the next month. If you wanted to add 31 days to a date, there's already 2 means to do that, no? Either "+31 days" or "+1 month"?

I submit that there's no 'easy' answer to this problem though, and that there has been considerable thought already put into it, so I'll leave it at this. I don't think my rambling is really going to change anything :)

@Vincenzo

It's actually not a solar month, a solar month is approximately 30 days (30 days, 10 hours, 29 minutes, 3.8 seconds according to one source), and "next month" seems to use a flat 31 days.

Solar month? 31 days? Didn't you read Derick's explanation?

It simply increments the month. So, today is January 8th. "+1 month" or "next month" is going to give you February 8th. Which in this case is ok. However, if we do this on January 29, 30 or 31 when we increase the month we get a date that doesn't exist in February so we correct it by stepping the appropriate numbers of days into March.

Whether this is the right way of doing it or not is debatable. You can argue both sides of that easily, but at some point someone decided that this was the way it was going to be and everyone has followed that. It may be wrong, in your view, but at least it is then consistently and predictably wrong. Much like spoken languages like English. There are so many things inherently wrong with the language, but once you learn it it feels natural and it works.

Forget the solar month then.

That's not point though. The point is: that function, however it works, shouldn't be used to obtain just the next month, which is a simpler problem. The function manipulate the entire date. You need to know just what the month next to the current one is.

@ken Actually, it does:

sqlite> select datetime('2010-02-28', 'start of month', '+1 month');
2010-03-01 00:00:00

I think SQLite's approach (with separate commands applied in sequence, and numeric date modifiers instead of "next", "prev") is clearer than 5.3's, and produces more predictable behaviour, since there's less suggestion that it will magically do what you mean, where what you mean may actually be ambiguous. Still, as Rasmus says, if you're familiar with other Unix tools and they way they work with relative dates, you'll expect PHP to work the way it does.

This is exactly why I am always a bit nervous when I type a date in natural style. "Will the system understand what I think it will understand?"

I had a wtf-moment when I saw that the next month resulted in the next next month. It would have been a slightly more logical if the inventors of this system decided to top of the remaining dates, so that you have 28 feb instead.

I like the fact Derick followed this odd standard. I've learned that following one wrong standard is better than to have no standard at all. Goed gedaan Derick! :)

Thanks for this post. Everybody who uses this extension should know this subtlety.

1 line php :

$next_month = date("m Y", strtotime('+1 month'));

http://php.net/manual/en/function.strtotime.php

@Laurent: That is exactly what will not work if today would be the 30th or 31st of January.

$d = new DateTime('2010-01-29'); $d->modify( 'first day of next month' ); echo $d->format( 'F jS, Y' );

This wil ouput "March 2nd, 2010" which is incorrect. I found that the following gives the correct output:

echo (date('m') + 1 % 13);

@Richard: first day of is something new in PHP 5.3, where it definitely works:

<?php
$d = new DateTime('2010-01-29');
$d->modify( 'first day of next month' );
echo $d->format( 'F jS, Y' );
?>

gives:

February 1st, 2010



FWIW, the problem here isn't PHP's consistency with the GNU API (I think that's a good thing), but just incomplete documentation: Neither <a href="http://php.net/strtotime">strtotime</a> nor <a href="http://www.php.net/manual/en/datetime.modify.php">DateTime::modify</a> even mention that such a standard exists, let alone where to read it. A very buried <a href="http://www.php.net/manual/en/function.strtotime.php#52684">user comment</a> is not good enough.

FYI, the GNU docs says "next" is just converted to "1" as in "+1 month", so maybe using the term "next" at all should be discouraged as it leads to this kind of ambiguity.

[A] I agree 100% with Ken that the behavior is not what the programmer expects.

[B] Even though the behavior isn't what is expected, "following one wrong standard is better than to have no standard at all". It's ok to admit that the behavior doesn't match expectations and yet is still the best option given the defacto GNU standards.

[C] The lack of documentation is a real problem. Like Ken said, people are going to file bugs against this 'til the end of time as it seems like a clear case of a bug to a programmer when they first encounter the behavior. More important than filed bugs is the bugs this causes in end user programs. This is downright nasty to have happen to any programmer. A bug that only occurs two days a month in an otherwise working program. Ouch. This should be given Warning status in strtotime() in the manual and anywhere else relative time strings are mentioned.

[D] Given the nice addition of "first day of" and "last day of" (thanks!) and the fact that there are alternatives to "next month" and "last month", "next month" and "last month" should be deprecated. If they are used in isolation (in the way that triggers the first behavior you describe but not the second), the user should be given a warning and deprecated notice (whatever the error levels/procedures are normally in PHP for deprecated functionality). I don't think it's an issue at all to deprecate it in PHP when it's not deprecated in libc or other places. The process of deprecating this unintuitive functionality needs to start somewhere, might as well be in PHP. I don't think it should be removed entirely unless it's removed upstream, but an indefinite deprecation seems entirely appropriate.

Awesome! exactly what I needed. thanks.

I'm a bit late to the discussion here, but the question I would have asked had I been developing the date behavior standards was, "What are the use cases."

I can't think of a use case where a programmer would want to turn Jan 30 into Mar 2 via a date function call. And there is a story for why not: that operation matches nothing that happens in our world. Jan 30 has no useful relation to Mar 2. There is, however, a use case for jumping from end-of-month to end-of-month by adding and subtracting "months" as we would in everyday language.

The options, from best to worst, for dealing with YYYY-02-30 seem to be:

  1. Return YYYY-02-28

  2. Throw an error

  3. Return YYYY-03-02

Add Comment

Name:
Email:

Will not be posted. Please leave empty instead of filling in garbage though!
Comment:

Please follow the reStructured Text format. Do not use the comment form to report issues in software, use the relevant issue tracker. I will not answer them here.


All comments are moderated

Life Line