- URL: https://www.laruence.com/en/2018/07/31/3207.html
- Please include attribution when republishing.
People often get thoroughly confused when combining strtotime with -1 month, +1 month, or next month, and then conclude that the function is a bit unreliable and breaks at the drop of a hat. They get nervous whenever they use it...
Sure enough, just now someone asked me on Weibo again:
Laruence, today is 2018-07-31. Running this code:
date("Y-m-d",strtotime("-1 month"))why is the output 2018-07-01?
Alright — although this question looks baffling, in terms of the internal logic it is actually "correct":
Let's simulate date's internal handling of this kind of thing:
- 1. First apply -1 month: the current date is 07-31, so minus one month gives 06-31.
- 2. Then normalize the date: because June has no 31st, just like 2:60 equals 3:00, June 31 becomes July 1.
Isn't the logic quite "clear"? We can also verify the second step by hand, for example:
var_dump(date("Y-m-d", strtotime("2017-06-31")));
// outputs 2017-07-01
That is, whenever the last day of a 30- vs 31-day month is involved, this confusion can arise. We can easily check other months to confirm the conclusion:
var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2017-03-31"))));
// outputs 2017-03-03
var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2017-08-31"))));
// outputs 2017-10-01
var_dump(date("Y-m-d", strtotime("next month", strtotime("2017-01-31"))));
// outputs 2017-03-03
var_dump(date("Y-m-d", strtotime("last month", strtotime("2017-03-31"))));
// outputs 2017-03-03
So what do we do?
Starting from PHP 5.3, date added a set of correction phrases to clear this up: "first day of" and "last day of". That is, you can pin things down so that date doesn't automatically "normalize":
var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2017-03-31"))));
// outputs 2017-02-28
var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime("2017-08-31"))));
//// outputs 2017-09-01
var_dump(date("Y-m-d", strtotime("first day of next month", strtotime("2017-01-31"))));
//// outputs 2017-02-01
var_dump(date("Y-m-d", strtotime("last day of last month", strtotime("2017-03-31"))));
//// outputs 2017-02-28
And if you're on a version before 5.3 (is anyone still using those?), you can use something like mktime to ignore the day-of-month entirely — for example, pin everything to the 1st of each month — though that's not as elegant as just using first day.
Now that you understand the internal principle, aren't you a lot less nervous? 🙂
Be First to Comment