Why does PHP date('m', strtotime('-1 months')) not work correctly for today? 07/31
Asked Answered
N

5

7

I have a script that gets the current and last month in PHP like so:

$currentMonth = date('m');
//Expected:07
//Result:07
$lastMonth = date('m', strtotime('-1 months'));
//Expected:06
//Result:07

Today happens to be the 31 or end of the month of July. Is this result to be expected from PHP?

When using -31 days the result is as expected:

$lastMonth = date('m', strtotime('-31 days'));
//Expected:06
//Result:06
Nadaba answered 31/7, 2015 at 16:11 Comment(6)
month-math is always risky. what IS a month? "today in ($curmonth-1)"?, what happens if it's march 31st, do you want february 31st, or feb 28? feb 29?Lopes
Looks like those aren't the only days -1 month breaks on: codepad.viper-7.com/E4gP0W The 31st of a month don't surprise me, but the 29th and 30th of March do.Incogitable
Always amazed when I find someone still not using github.com/briannesbitt/Carbon to do dates in PHP.Ambiguous
@Ambiguous Carbon reacts the same in this case github.com/briannesbitt/Carbon/issues/37Nadaba
@Nadaba I'm still surprised though. So much easier.Ambiguous
Not just php, but also gnu gnu.org/software/tar/manual/html_node/…Shaquitashara
S
2

Here's a cleaner test case that doesn't expire:

<?php
$origin = mktime(18, 0, 0, 7, 31, 2015);
var_dump( date('r', $origin), date('r', strtotime('-1 months', $origin)) );
string(31) "Fri, 31 Jul 2015 18:00:00 +0200" 
string(31) "Wed, 01 Jul 2015 18:00:00 +0200"

I'm pretty sure it's a documentation issue, because the manual clearly states this (emphasis mine):

Relative month values are calculated based on the length of months that they pass through. An example would be "+2 month 2011-11-30", which would produce "2012-01-30". This is due to November being 30 days in length, and December being 31 days in length, producing a total of 61 days.

... and it's wrong.

PHP bug tracker has tons of dupes about this. They're all closed as not a bug. Here's a relevant comment from 2009 that explains it:

I agree that this is an annoying behaviour.

Also, the implementation is problematic. Basically if you use '+1 month' it takes the month number, adds 1 and parses result as a new date.

If you use '+1 month' on the first of the month, it sets the date to the next first of the month.

This behaviour gives the impression, that php considers the length of a month, which is not true.

But if you use '+1 month' on the last day of a month, the result is unexpected as 2009-05-31 becomes 2009-06-31 which is an invalid date and then interpreted as 2009-07-01.

This should at least be mentioned in the documentation.

Schwartz answered 31/7, 2015 at 17:12 Comment(0)
V
2

You can do this way

$d = new DateTime(); 
$currentMonth = $d->format('m');
//Expected:07
//Result:07
print $currentMonth;

$d->modify('first day of previous month');
print "<br/>";
$lastMonth = $d->format('m'); 
//Expected:06
//Result:06
print $lastMonth;

DEMO: http://codepad.viper-7.com/kokWi8

Veneaux answered 31/7, 2015 at 16:25 Comment(0)
N
2

This is a issue with PHP's date-string parser. See here: http://derickrethans.nl/obtaining-the-next-month-in-php.html

@Mr. Llama made a script showing what other dates this issue effects:http://codepad.viper-7.com/E4gP0W

The solution I went with:

//Date:07/31/15
$currentMonth = date('m');
//Result:07
$lastMonth = date('m', strtotime('first day of -1 months'));
//Result:06
Nadaba answered 31/7, 2015 at 16:43 Comment(1)
Great answer to a perplexing problem.Homerus
S
2

Here's a cleaner test case that doesn't expire:

<?php
$origin = mktime(18, 0, 0, 7, 31, 2015);
var_dump( date('r', $origin), date('r', strtotime('-1 months', $origin)) );
string(31) "Fri, 31 Jul 2015 18:00:00 +0200" 
string(31) "Wed, 01 Jul 2015 18:00:00 +0200"

I'm pretty sure it's a documentation issue, because the manual clearly states this (emphasis mine):

Relative month values are calculated based on the length of months that they pass through. An example would be "+2 month 2011-11-30", which would produce "2012-01-30". This is due to November being 30 days in length, and December being 31 days in length, producing a total of 61 days.

... and it's wrong.

PHP bug tracker has tons of dupes about this. They're all closed as not a bug. Here's a relevant comment from 2009 that explains it:

I agree that this is an annoying behaviour.

Also, the implementation is problematic. Basically if you use '+1 month' it takes the month number, adds 1 and parses result as a new date.

If you use '+1 month' on the first of the month, it sets the date to the next first of the month.

This behaviour gives the impression, that php considers the length of a month, which is not true.

But if you use '+1 month' on the last day of a month, the result is unexpected as 2009-05-31 becomes 2009-06-31 which is an invalid date and then interpreted as 2009-07-01.

This should at least be mentioned in the documentation.

Schwartz answered 31/7, 2015 at 17:12 Comment(0)
C
1

-1 month is interpreted as "same day of month, last month". If this day does not exist, the date overflows into the next month. Actually the result is the same as strtotime("31.6.2015") - try it!

Cleland answered 31/7, 2015 at 18:22 Comment(0)
B
-1

There is "s" in excess in month. It should be like this:

$lastMonth = date('m', strtotime('-1 month'));
Barthol answered 31/7, 2015 at 16:15 Comment(2)
makes no difference. PHP isn't a grammar nazi and 1 months and 1 month are identical as far as it's concerned.Lopes
Indeed, the extra s is allowed/optional. The documentation confirms that.Equate

© 2022 - 2024 — McMap. All rights reserved.