PHP: добавление месяцев к дате, не превышающих последний день месяца

Глядя на создание функции, которая будет делать это в PHP.

Мне нужно добавить несколько месяцев к дате, но не превышать последний день месяца.

Например:

Add 1 month to January (1-28th), 2011, should produce February (1-28th), 2011. Add 1 month to January 30th, 2011, should produce February 28th, 2011. Add 3 months to January 31st, 2011, should produce April 30th, 2011. Add 13 months to January 30th, 2011, should produce February 29th, 2012. Add 1 month to October 31st, 2011, should produce November 30th, 2011. 

Если я использую добавление даты в PHP, я получаю перерасход:

 Adding 1 month to January 30th, 2011, results in March 2nd, 2011. 

Моя спецификация не позволяет мне переполняться в новый месяц.

Какой самый простой способ выполнить это?

Вы можете сравнить день месяца до и после добавления 1 месяца. Если это не одно и то же, вы превысили следующий месяц.

 function add($date_str, $months) { $date = new DateTime($date_str); // We extract the day of the month as $start_day $start_day = $date->format('j'); // We add 1 month to the given date $date->modify("+{$months} month"); // We extract the day of the month again so we can compare $end_day = $date->format('j'); if ($start_day != $end_day) { // The day of the month isn't the same anymore, so we correct the date $date->modify('last day of last month'); } return $date; } $result = add('2011-01-28', 1); // 2011-02-28 $result = add('2011-01-31', 3); // 2011-04-30 $result = add('2011-01-30', 13); // 2012-02-29 $result = add('2011-10-31', 1); // 2011-11-30 $result = add('2011-12-30', 1); // 2011-02-28 

это, похоже, работает для меня и дает желаемый результат:

 <?php $date = "2011-01-30"; list($year,$month,$day) = explode("-",$date); // add month here $month++; // to avoid a month-wrap, set the day to the number of days of the new month if it's too high $day = min($day,date("t",strtotime($year."-".$month."-01"))); $date = $year."-".$month."-".$day; // 2011-02-28 echo $date; ?> 

РЕДАКТИРОВАТЬ:
после прочтения Crend Kings comemnt, я думаю, нам нужна дополнительная информация здесь. Каков желаемый результат в следующих случаях:

 2011-01-30 > 2011-02-28 2011-01-28 > 2011-02-28 or 2011-02-26 ? 2011-02-01 > 2011-03-01 or 2011-03-03 ? 

на словах: должен ли метод добавить количество дней следующего месяца, что делает Crend King и что дает результаты, такие как 2011-02-26 и 2011-03-03 (которые не кажутся мне желаемыми результатами ) или следует добавить один месяц и оставить день как есть, а не день, который «слишком высок», как мой код? Я смущен…

Насколько я могу судить, эта проблема очень ограничена по объему, поэтому вам, скорее всего, будет лучше всего, протестировав один тип ошибок и исправив его.

Все, что вы хотите сделать, это убедиться, что добавление «одного месяца» на поздний срок, такой как 29, 30 или 31, не подталкивает вас вперед к 1, 2 или 3 следующему следующему месяцу.

Способ date_modify () работает (используя его на примерной дате «2012-01-31» со строкой типа «+1 месяц»), заключается в том, что она сначала увеличивает номер месяца на 1, а затем находит 31-й день с самого начала этого месяца. Вот почему он просыпается до 3 марта.

Если это не то, что вам нужно, все, что вам нужно сделать, это снова использовать date_modify (), теперь он говорит, чтобы он вернулся несколько дней (3 дня в этом примере). Поскольку вы хотите вернуться только к последнему дню предыдущего месяца, количество дней, в которые вы хотите вернуться, всегда совпадает с днем ​​в вашей ошибочной дате.

Осталось только убедиться, что вы не применяете эту коррекцию, когда она не нужна, например, когда PHP будет улучшаться в будущем. Это относительно легко, поскольку объем возможных проблемных ситуаций очень ограничен.

  • (1) Проблема возникает только при добавлении месяцев к датам 29, 30 или 31
  • (2) Когда проблема возникает, итоговая дата всегда 1, 2 или 3.

Мой код ниже добавляет «+1 месяц», проверяет, не привело ли это к тому, что день-месяц сильно изменился с чего-то высокого на что-то низкое и изменил дату, если это так.

 //Create the date, store its day-of-month, and add X months $myDateTimeISO = "2012-01-31"; $addThese = 1; $myDateTime = new DateTime($myDateTimeISO); $myDayOfMonth = date_format($myDateTime,'j'); date_modify($myDateTime,"+$addThese months"); //Find out if the day-of-month has dropped $myNewDayOfMonth = date_format($myDateTime,'j'); if ($myDayOfMonth > 28 && $myNewDayOfMonth < 4){ //If so, fix by going back the number of days that have spilled over date_modify($myDateTime,"-$myNewDayOfMonth days"); } echo date_format($myDateTime,"Ymd"); 

Результаты: 2012-02-29 (да, это был високосный год).

PS: Если вы хотите добавить годы, проблема и симптомы почти идентичны. Опять же, вам просто нужно проверить, достигнет ли день-месяц 1/2/3, а день за днем ​​- 29/30/31. Если это так, вам нужно вернуться в «-X дней», используя date_modify, где X – итоговый день месяца.

Для всех, кого это интересует, я сделал прочный подход, чтобы решить эту проблему

 /** * @var \DateInterval */ private $remainder; /** * @param \DateTimeImmutable $date * @param string $modifier * @return \DateTimeImmutable */ private function nextInterval(\DateTimeImmutable $date, $modifier) { $dayNumber = (int)$date->format('j'); $next = $date->modify($modifier); if (!is_null($this->remainder)) { $next = $next->add($this->remainder); $dayNumber += $this->remainder->days; $this->remainder = null; } // This should in general only apply to months which do not have the same daynumber in that month after adding if ($dayNumber !== (int)$next->format('j')) { $n = $next->modify('last day of last month'); $this->remainder = $n->diff($next); $next = $n; } return $next; } 

Результаты:

 2014-11-30 2014-12-30 2015-01-30 2015-02-28 2015-03-30 2015-04-30 2015-05-30 

а также

 2015-12-30 2016-01-30 2016-02-29 2016-03-30 2016-04-30