PHP: Добавление месяцев к дате, не превышая при этом последнего дня месяца

#php #date

#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:

Вы можете сравнить день месяца до и после добавления 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
  

Комментарии:

1. как насчет високосных лет? что произойдет, если кто-то захочет добавить месяц с первого дня месяца? если я прав: добавление месяца к 01 января даст 30 января; добавление месяца к 01 февраля даст 03 марта с вашим кодом — не уверен, нужно ли это, но если да, то это не кажется правильным.

2. Високосные годы можно проверить с помощью checkdate . Поэтому нужны только 2 разных массива.

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

Комментарии:

1. Как насчет 28 февраля — 31 марта? Что, если с 28 февраля по 30 апреля?

2. Я не думаю, что это часть спецификации … я думаю, нам нужно больше примеров, чтобы понять это, пожалуйста, смотрите мою правку для получения дополнительной информации

3. Если OP нужен просто месяц 1 (например, add(Feb 28th) получается 28 марта), я думаю, что ваш ответ верен для интервала в 1 месяц.

4. @oezi это не сработает, если дата была в декабре месяце. Например: если дата была 2011-12-30 , это дает 2011-13-30

Ответ №3:

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

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

Способ, которым работает 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 amp;amp; $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:

Вы можете использовать этот код. Первая строка принимает дату в любом формате, а последующие строки разделяют значение месяца, добавляя месяц и возвращая окончательное значение в 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 );
  

Комментарии:

1. Если вы установите начальную дату в $date = ‘2020-01-01’, результатом будет 2020-02-29?

Ответ №5:

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

 /**
 * @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
  

Ответ №6:

Вы также можете использовать этот код.

 <?php
$monthToAdd = 1;

$d1 = DateTime::createFromFormat('Y-m-d H:i:s', '2011-01-30 15:57:57');

$year = $d1->format('Y');
$month = $d1->format('n');
$day = $d1->format('d');

$year  = floor($monthToAdd/12);
$monthToAdd = $monthToAdd%12;
$month  = $monthToAdd;
if($month > 12) {
    $year   ;
    $month = $month % 12;
    if($month === 0)
        $month = 12;
}

if(!checkdate($month, $day, $year)) {
    $d2 = DateTime::createFromFormat('Y-n-j', $year.'-'.$month.'-1');
    $d2->modify('last day of');
}else {
    $d2 = DateTime::createFromFormat('Y-n-d', $year.'-'.$month.'-'.$day);
}
$d2->setTime($d1->format('H'), $d1->format('i'), $d1->format('s'));
echo $d2->format('Y-m-d H:i:s');
  

Ответ №7:

// Я обновил логику для получения последней даты месяца

 function addMonthGetLastDate($date_str, $months)
{
    
   
    list($year,$month,$day) = explode("-",$date_str);
    // add month here
    $month  = ($months-1);
    if($month>12)
    {
        $month = $month - 12;
        $year  = 1;
    }
    // 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 = date('Y-m-t', strtotime($year."-".$month."-".$day));
    
    return $date;
}