Guillermo
Guillermo

Reputation: 947

Next month, same day in PHP

I have an "event" that needs to be scheduled the same day of every month.

Say you set the start date on the 1st May you should get the next events on the 1st of Jun, 1 Jul etc. The problem comes with a start date on the 31st (the next ones could be 30 or 28 depending on the month).

Considering that there are months with different numbers of days (28, 30, 31) depending on the month itself and the year... what would be an easy way to setup this?

Consider the following (and flawed) nextmonth function:

$events = array()

function nextmonth($date) {
   return $date+(60*60*24*30);
}
$curr = $start;
while($curr < $end) {
    $events[ = $curr;
    $curr = nextmonth($curr);
}

Edited to add: The problem for me is, simply enough, how to solve what the number of days of any given month is and thus get the next corresponding date.

Upvotes: 15

Views: 17672

Answers (18)

Omar
Omar

Reputation: 552

Among all the answers here I found that only @ling actually answers this question, but still his answer is not 100% accurate, it outputs the year part incorrectly as you can see in his answer.

Here is modified version of his code that fixes the year problem ( I also modified it to use DateTime)

Hence that this solution also takes care of leap year, so that is why for year 2020 (which is a leap year) it shows 2020-02-29 where for non leap years such as 2019 it shows 2019-02-28

  /**
   * Adding month in PHP using DateTime
   * class will render the date in a way that
   * we do not desire, for example adding one month
   * to 2018-01-31 will result in 2018-03-03 but
   * we want it to be 2018-02-28 instead.
   *
   * This method ensure that adding months
   * to a date get caculated properly.
   *
   *
   * @param DateTime $startDate
   * @param int $numberOfMonthsToAdd
   *
   * @return DateTime
   */
  function getSameDayNextMonth(DateTime $startDate, $numberOfMonthsToAdd = 1) {
    $startDateDay = (int) $startDate->format('j');
    $startDateMonth = (int) $startDate->format('n');
    $startDateYear = (int) $startDate->format('Y');

    $numberOfYearsToAdd = floor(($startDateMonth + $numberOfMonthsToAdd) / 12);
    if ((($startDateMonth + $numberOfMonthsToAdd) % 12) === 0) {
      $numberOfYearsToAdd--;
    }
    $year = $startDateYear + $numberOfYearsToAdd;

    $month = ($startDateMonth + $numberOfMonthsToAdd) % 12;
    if ($month === 0) {
      $month = 12;
    }
    $month = sprintf('%02s', $month);

    $numberOfDaysInMonth = (new DateTime("$year-$month-01"))->format('t');
    $day = $startDateDay;
    if ($startDateDay > $numberOfDaysInMonth) {
      $day = $numberOfDaysInMonth;
    }
    $day = sprintf('%02s', $day);

    return new DateTime("$year-$month-$day");
  }
  
  
  // Quick Test
  $startDate = new DateTime('2018-01-31');
  for($i=0; $i <= 40; $i++) {
    echo getSameDayNextMonth($startDate, $i)->format('Y-m-d') . "\n";
  }

The output :

2018-01-31
2018-02-28
2018-03-31
2018-04-30
2018-05-31
2018-06-30
2018-07-31
2018-08-31
2018-09-30
2018-10-31
2018-11-30
2018-12-31
2019-01-31
2019-02-28
2019-03-31
2019-04-30
2019-05-31
2019-06-30
2019-07-31
2019-08-31
2019-09-30
2019-10-31
2019-11-30
2019-12-31
2020-01-31
2020-02-29
2020-03-31
2020-04-30
2020-05-31
2020-06-30
2020-07-31
2020-08-31
2020-09-30
2020-10-31
2020-11-30
2020-12-31
2021-01-31
2021-02-28
2021-03-31
2021-04-30
2021-05-31

Upvotes: 5

Mehul Patel
Mehul Patel

Reputation: 1

I created code.that would work perfectly for me.

$start_date ='2023-01-31';
$temp_end_date =date('Y-m-d', strtotime($start_date. "+1 month"));
$sdate_day =date('d',strtotime($start_date));
$edate_day =date('d',strtotime($temp_end_date));
if($sdate_day == $edate_day)
{
$end_date =$temp_end_date;
}
else
{
$end_date =date('Y-m-t', strtotime('-5 days', strtotime($temp_end_date)));
}
echo $end_date;

Upvotes: 0

Dominic Lee
Dominic Lee

Reputation: 21

Here you go~

function getDatePlusMonth($date, $month_count_to_be_plus = 1) {
    $day = (int) $date->format('j');
    $month = (int) $date->format('n');
    $year = (int) $date->format('y');
    
    $year_count_to_be_plus = floor($month_count_to_be_plus / 12);
    $year += $year_count_to_be_plus;
    $month = ($month + $month_count_to_be_plus) % 12;
    $month = $month === 0 ? 12 : $month;
    $day = min($day, cal_days_in_month(CAL_GREGORIAN, $month, $year));
    
    return new DateTime(date('Y-m-d H:i:s', mktime(0, 0, 0, $month, $day, $year)));
}

Test cases for the end day of each month

$start = new DateTime('2023-01-31 00:00:00');

for ($i = 0; $i <= 12; $i++) {
    echo getDatePlusMonth($start, $i)->format('Y-m-d') . PHP_EOL;
}

// Results
// 2023-01-31
// 2023-02-28
// 2023-03-31
// 2023-04-30
// 2023-05-31
// 2023-06-30
// 2023-07-31
// 2023-08-31
// 2023-09-30
// 2023-10-31
// 2023-11-30
// 2023-12-31
// 2024-01-31

Test cases for the end day of Feb. by each year.

$start = new DateTime('2024-02-29 00:00:00');

for ($i = 0; $i <= 8; $i++) {
    echo getDatePlusMonth($start, $i*12)->format('Y-m-d') . PHP_EOL;
}

// Results
// 2024-02-29
// 2025-02-28
// 2026-02-28
// 2027-02-28
// 2028-02-29
// 2029-02-28
// 2030-02-28
// 2031-02-28
// 2032-02-29

Upvotes: 0

AndraeRay
AndraeRay

Reputation: 2498

I created a function called my_strtotime that will handle adding and subtracting months. it's basically a wrapper around strtotime.

It checks if adding or subtracting a month will safely get you the desired month with same day. If not then it does it safely.

When adding months, it adds 15 to get you safely into the next month, then returns the last day of the month. When subtracting months, it takes the first day of this month and subtracts 1.

note: As written this works for +1 month, or -1 month


    function _my_strtotime($offset, $reference_date){

           //$offset is a string such as (+1 month)

       if(strpos($offset, 'month') === false){
         //exit if not addin/subtracing months
         return strtotime($offset, $reference_date);
       }

       $other_months_date = strtotime ( $offset, $reference_date );
       $day_of_other_month = date( 'd', $other_months_date );
       $day_of_reference_month = date('d', $reference_date );

       //check if numerical day is different. If so manipulate.
       if($day_of_reference_month != $day_of_other_month){
         if(strpos($offset, '-') === false){
           // adding months
           return strtotime('last day of this month', strtotime('+15 days', $reference_date));
         } else {
           // subtracing months
           return strtotime('-1 day', strtotime('first day of this month', $reference_date));
         }

       }

       return strtotime($offset, $reference_date);

     }

Test results:


    input=2016-01-31  (+1 month)  output=2016-02-29 //leap year

    input=2017-01-31  (+1 month) output=2017-02-28

    input=2018-05-31 (+1 month) output=2018-06-30

    input=2018-07-31 (-1 month) output=2018-06-30

    input=2016-07-22 (-1 month) output=2016-06-22

    input=2016-07-22 (+1 month) output=2016-08-22

Upvotes: 0

ling
ling

Reputation: 10047

None of the above took care of non existing days (30 february, or 31 april for instance), so here is my function:

<?php
function getSameDayNextMonth($time, $numberMonthsToAdd = 1)
{
    list($year, $month, $day) = explode('-', date("Y-m-d", $time));

    // replace the day by one temporarily (just to make sure it exists for any month
    $numberOfYearsToAdd = floor($numberMonthsToAdd / 12);
    $year += $numberOfYearsToAdd;
    $month = ($month + $numberMonthsToAdd) % 12;
    if (0 === $month) {
        $month = 12;
    }
    $monthFormatted = sprintf('%02s', $month);
    $nbDaysInThisMonth = date("t", strtotime("$year-$monthFormatted-01"));

    if ((int)$day > $nbDaysInThisMonth) {
        $day = $nbDaysInThisMonth;
    }
    $day = sprintf('%02s', $day);
    return strtotime("$year-$monthFormatted-$day");
}


$time = strtotime("2017-10-31");
for ($i = 0; $i <= 15; $i++) {
    $_time = getSameDayNextMonth($time, $i);
    echo date("Y-m-d", $_time) . '<br>';
}

/**
 * 2017-10-31
 * 2017-11-30
 * 2017-12-31
 * 2017-01-31
 * 2017-02-28
 * 2017-03-31
 * 2017-04-30
 * 2017-05-31
 * 2017-06-30
 * 2017-07-31
 * 2017-08-31
 * 2017-09-30
 * 2018-10-31
 * 2018-11-30
 * 2018-12-31
 * 2018-01-31
 */

Upvotes: 1

Tigran Babajanyan
Tigran Babajanyan

Reputation: 2025

This is what I use

This is a timestamp of current month date

$month_timestamp = strtotime(date('Y-m', $create_timestamp));

Current day

$current_day = date('d');

And this is next month same day in format "Y-m-d"

$next_month_first = date('Y-m-' . $current_day, strtotime('next month', $month_timestamp));

Upvotes: 0

Marcelo Aymone
Marcelo Aymone

Reputation: 291

Datetime OOP Style

<?php
//Start Date -> 18-09-2015
$expiry = new DateTime();
for($i=1;$i<5;$i++){
   $expiry->modify('+1 Month');
   echo $expiry->format('d-m-Y');
   echo '<br>';
}
?>

//18-10-2015
//18-11-2015
//18-12-2015
//18-01-2016

Upvotes: 0

user2503896
user2503896

Reputation:

I also had same problem but when i tried above solutions they could not work perfectly for me, I tried on my end and came up with new solution which is:

$startDate = date("Y-m-d");
$month = date("m",strtotime($startDate));
$nextmonth = date("m",strtotime("$startDate +1 month"));
if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1))
{
    $nextDate = date( 't.m.Y',strtotime("$startDate +1 week"));
}else
{
    $nextDate = date("Y-m-d",strtotime("$startDate +1 month"));
}
echo $nextSameDate = date("Y",$nextDate).'-'.date("m",$nextDate).'-'.date("d",$startDate);

Upvotes: 0

F&#225;bio Paiva
F&#225;bio Paiva

Reputation: 569

Simple

public function adicionarMesMantendoDia($date, $months, $format = "Y-m-d"){
        $date = \DateTime::createFromFormat($format, $date);

        for($i=0;$i < $months; $i++){
            $date->modify('+ ' . date("t", $date->getTimestamp())  . ' day');
        }
        return $date;
}

Upvotes: 1

Kai
Kai

Reputation: 31

Try this. reday is day to refill(int - day of month). If less than today, it will give reday of this month but not much than no. of this month days. If more than today give reday of next month same condition. I use this for calculate "Next refill" of monthly refill point. return is dd/mm/yyyy

function nextrefill($reday) {
    if (date("j") < $reday) {
        $numd = date("t");
        if ($reday > $numd) {
            return str_pad($numd, 2, "0", STR_PAD_LEFT).date("/m/Y");
        } else {
            return str_pad($reday, 2, "0", STR_PAD_LEFT).date("/m/Y");
        }
    } else {
        $nextm = date('m', strtotime('first day of next month'));
        $nextmy = date('Y', strtotime('first day of next month'));
        $numd = cal_days_in_month(CAL_GREGORIAN, $nextm, $nextmy);
        if ($reday > $numd) {
            return str_pad($numd, 2, "0", STR_PAD_LEFT)."/".$nextm."/".$nextmy;
        } else {
            return str_pad($reday, 2, "0", STR_PAD_LEFT)."/".$nextm."/".$nextmy;
        }
    }
}

for more direct to point (Edit)

function nextmonthday($day) {
        $next_month = date('m', strtotime('first day of next month'));
        $year_of_next_month = date('Y', strtotime('first day of next month'));
        $no_of_day_in_next_month = cal_days_in_month(CAL_GREGORIAN, $nextm, $nextmy);
        if ($day > $no_of_day_in_next_month){
            return str_pad($no_of_day_in_next_month, 2, "0", STR_PAD_LEFT)."/".$next_month."/".$year_of_next_month;
        } else {
            return str_pad($day, 2, "0", STR_PAD_LEFT)."/".$next_month."/".$year_of_next_month;
        }
}

Upvotes: 3

zzz
zzz

Reputation: 13

I know this is an old post but thought to give a different approach.

So instead of trying to figure out the days in a month (which is somewhat complicated), one can find out the next month using the current month easily, e.g.:

date("m", strtotime($current."+1 month"));

Then get the day of the current month using date("d"), and concat with the next month from the code above.

Upvotes: 1

newenglander
newenglander

Reputation: 2049

No one's mentioned this alternative, though the result is the same as the others, and probably not what the OP is looking for:

$today = new DateTime();
$today->modify("+1 month");
echo $today->format("d.m.Y");

Why not then just store month and day separately and increment month by one, and then use PHP's checkdate function to validate the date, decreasing the day by one until the date is valid?

Upvotes: 2

Sam Heller
Sam Heller

Reputation: 41

Tried this as a lark, and it actually works

strtotime('last day next month')

So :

$today_next_month = strtotime('this day next month');
$last_day_next_month = strtotime('last day next month');
if (date('d', $today_next_month) < date('d', $last_day_next_month)){
    $date = date('m-d-Y', $last_day_next_month); 
} else {
    $date = date('m-d-Y', $today_next_month);
}
echo "And the winner is : $date<br/>";

Upvotes: 2

Brian Fisher
Brian Fisher

Reputation: 24009

How about this function:

    function getNextMonthN($date, $n = 1) {
      $newDate = strtotime('+' . $n . ' months', $date);
      if (date('j', $date) !== (date('j', $newDate))) {
        $newDate = mktime(0, 0, 0, date('n', $newDate), 0, date('Y', $newDate));
      }
      return $newDate;  
    }

There is another solution on the php.net manual site under the strtotime entry in the comments.

Upvotes: 0

Ionuț G. Stan
Ionuț G. Stan

Reputation: 179199

Update:

This will give you the number of days in given month:

echo date('t', $timestamp);

See: date()

Old answer:

I'm not sure about the algorithm you're thinking of but I believe this will help you:

echo date('d-M-y', strtotime('next month'));

Upvotes: 9

mikegreenberg
mikegreenberg

Reputation: 1421

I recommend reading the following comment on php.net. strtotime() (php.net)

Edit: The next answer gives the "summary" of the link I posted for those not able to decipher the content shown there.

Upvotes: 2

Thinker
Thinker

Reputation: 14474

This will return proper timestamp for $X months ahead: mktime(0,0,0,date("m")+$X,date('d'),date("Y"));

Upvotes: 5

St. John Johnson
St. John Johnson

Reputation: 6660

Since months are so varied in size, wouldn't the best way to set the next month be something like: this day, next month except if this day doesn't exist next month.

Example:

June 5, 2009, next month would be July 5, 2009
August 31, 2009, next month would be September 30, 2009

or simply, strtotime("+1 month")

Upvotes: 1

Related Questions