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, я получаю overruns:
Adding 1 month to January 30th, 2011, results in March 2nd, 2011.
Моя спецификация не позволяет мне переполняться в новый месяц.
Какой самый простой способ выполнить это?
Ответы
Ответ 1
Вы можете сравнить день месяца до и после добавления 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
Ответ 2
это работает для меня и дает желаемый результат:
<?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 too high
$day = min($day,date("t",strtotime($year."-".$month."-01")));
$date = $year."-".$month."-".$day;
// 2011-02-28
echo $date;
?>
EDIT:
после прочтения 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 (которые не кажутся желательными результаты для меня), или следует добавить один месяц и оставить день как есть, а не день, который "слишком высок", как мой код? я запутался...
Ответ 3
Насколько я могу судить, эта проблема очень ограничена по объему, поэтому вам, скорее всего, будет лучше всего тестировать один тип ошибок и фиксировать его.
Все, что вы хотите сделать, это убедиться, что добавление "одного месяца" на поздний срок, например, 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,"Y-m-d");
Результаты: 2012-02-29 (да, это был високосный год).
PS: Если вы хотите добавить годы, проблема и симптомы почти идентичны. Опять же, вам просто нужно проверить, достигнет ли день-месяц 1/2/3, а день за днем - 29/30/31. Если это так, вам нужно вернуться к "-X дням", используя date_modify, где X - итоговый день месяца.
Ответ 4
Для всех, кого это касается, я сделал прочный подход к решению этой проблемы.
/**
* @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
Ответ 5
Вы можете использовать этот код. Первая строка принимает дату в любом формате, а последние строки отделяют значение месяца, добавляя месяц и возвращая окончательное значение в формате Y-M-D
.
$year_month = Date("Y-m", strtotime($date));
$year_month_incremented = Date("Y-m", strtotime($year_month . " +1 Month "));
$month_end_dt =strtotime('last day of this month', strtotime($year_month_incremented));
$date = date('Y-m-d', $month_end_dt );