Reputation: 947
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Reputation: 14474
This will return proper timestamp for $X months ahead: mktime(0,0,0,date("m")+$X,date('d'),date("Y"));
Upvotes: 5
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