PHP: добавление месяцев к дате, не превышая последний день месяца - PullRequest
29 голосов
/ 23 апреля 2011

Хотите создать функцию, которая будет делать это в 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.

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

Какой самый простой способ сделать это?

Ответы [ 4 ]

40 голосов
/ 23 апреля 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
3 голосов
/ 31 мая 2012

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

Все, что вы хотите сделать, - это убедиться, что добавление «одного месяца» к более поздней дате, такой как 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 - итоговый день месяца.

3 голосов
/ 23 апреля 2011

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

<?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;
?>

EDIT:
после прочтения коммента Crend Kings, я думаю, нам нужно больше информации здесь. Каков желаемый результат в следующих случаях:

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 (которые не кажутся желаемыми результаты для меня) или это должно добавить один месяц и оставить день как есть, а не день, который "слишком высок", как мой код? я в замешательстве ...

0 голосов
/ 11 мая 2017

Для всех, кто заинтересовался, я выработал твердый подход к решению этой проблемы

/**
 * @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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...