PHP DateTime::modify adding and subtracting months

Viewed 122341

I've been working a lot with the DateTime class and recently ran into what I thought was a bug when adding months. After a bit of research, it appears that it wasn't a bug, but instead working as intended. According to the documentation found here:

Example #2 Beware when adding or subtracting months

<?php
$date = new DateTime('2000-12-31');

$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";

$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output:
2001-01-31
2001-03-03

Can anyone justify why this isn't considered a bug?

Furthermore does anyone have any elegant solutions to correct the issue and make it so +1 month will work as expected instead of as intended?

20 Answers

The accepted answer already explains why this is not a but, and some other answers pose a neat solution with php expressions like first day of the +2 months. The problem with those expressions is that they are not autocompleted.

The solution is quite simple though. First, you should find useful abstractions that reflect your problem space. In this case, it's an ISO8601DateTime. Second, there should be multiple implementations that can bring a desired textual representation. For example, Today, Tomorrow, The first day of this month, Future -- all represent a specific implementation of ISO8601DateTime concept.

So, in your case, an implementation you need is TheFirstDayOfNMonthsLater. It's easy to find just by looking at the subclasses llist in any IDE. Here's the code:

$start = new DateTimeParsedFromISO8601String('2000-12-31');
$firstDayOfOneMonthLater = new TheFirstDayOfNMonthsLater($start, 1);
$firstDayOfTwoMonthsLater = new TheFirstDayOfNMonthsLater($start, 2);
var_dump($start->value()); // 2000-12-31T00:00:00+00:00
var_dump($firstDayOfOneMonthLater->value()); // 2001-01-01T00:00:00+00:00
var_dump($firstDayOfTwoMonthsLater->value()); // 2001-02-01T00:00:00+00:00

The same thing with the last days of a month. For more examples of this approach, read this.

$current_date = new DateTime('now');
$after_3_months = $current_date->add(\DateInterval::createFromDateString('+3 months'));

For days:

$after_3_days = $current_date->add(\DateInterval::createFromDateString('+3 days'));

Important:

The method add() of DateTime class modify the object value so after calling add() on a DateTime Object it returns the new date object and also it modify the object it self.

Related