14

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.

Simon East
  • 55,742
  • 17
  • 139
  • 133
Guillermo
  • 927
  • 3
  • 10
  • 23
  • please format the code better, thanks! – JasonV Jun 05 '09 at 22:05
  • 1
    I'm not a PHP dev so just adding some context, Outlook defaults to using the last day of the month if the date specified isn't a valid date for that month. Do you need to take into account weekends and holidays? – Venr Jun 05 '09 at 22:07
  • There's http://uk.php.net/cal_days_in_month or date('t') - but I think the actual problem is what to do when a user makes an event on the 31st, something entirely dependant on what the events are.. – dbr Jun 06 '09 at 00:55
  • This guy has a good answer: http://www.midwesternmac.com/blogs/jeff-geerling/php-calculating-monthly. Not sure how good it is performance-wise, but does what you're describing - hope it can help any others that find this page. – Erebus Jan 24 '14 at 19:53

18 Answers18

9

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'));
Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
5

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

Thinker
  • 14,234
  • 9
  • 40
  • 55
  • +1 This seems more natural-- break up the date and add stuff to the components to get what you want. – Kekoa Jun 05 '09 at 22:27
  • +1, I tested this and it works fine when the current month plus x equals a number greater than 12. It increments the year correctly. Also if the day number doesn't exist in the month it will move to the next month with the extra days. – gradbot Jun 06 '09 at 03:16
  • 1
    just tested your test case "2009-01-30" +1 month and this also returns 2009-3-2 – gradbot Jun 06 '09 at 03:20
4

A lot of time passed but in case someone is searching for this, Among all the answers above I found that only @ling answer actually answer this question, but still his answer was not 100% accurate, it output the year part incorrectly as you can see in his result above.

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

  /**
   * 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
Omar
  • 542
  • 1
  • 6
  • 27
3

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;
        }
}
Kai
  • 31
  • 2
2

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?

newenglander
  • 2,019
  • 24
  • 55
2

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.

mikegreenberg
  • 1,411
  • 1
  • 12
  • 19
  • Looks interesting.. if you would post this here, someone else will get the chance to get his answer here by looking here.. – Guillermo Jun 05 '09 at 22:13
1

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.

Mihai Iorga
  • 39,330
  • 16
  • 106
  • 107
zzz
  • 13
  • 3
1

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;
}
Fábio Paiva
  • 569
  • 4
  • 7
1

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/>";
Sam Heller
  • 31
  • 5
1

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
 */
ling
  • 9,545
  • 4
  • 52
  • 49
  • This is the only answer that actually answer the question as far as I can see, though it has a problem in the year part, I fixed it in my answer. – Omar Apr 18 '18 at 01:09
1

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")

St. John Johnson
  • 6,590
  • 7
  • 35
  • 56
0

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);
0

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
Marcelo Aymone
  • 293
  • 3
  • 19
0

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));
Tigran Babajanyan
  • 1,967
  • 1
  • 22
  • 41
0

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

AndraeRay
  • 2,348
  • 2
  • 12
  • 9
0

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

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.

Brian Fisher
  • 23,519
  • 15
  • 78
  • 82