Reputation: 1802
Looking to create a function that will do this in PHP.
I need to add a number of months to a date, but not exceed the last day of the month in doing so.
For example:
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.
If I use date addition in PHP, I get overruns:
Adding 1 month to January 30th, 2011, results in March 2nd, 2011.
My specification doesn't allow me to overrun into a new month.
What's the easiest method to accomplish this?
Upvotes: 38
Views: 23969
Reputation: 156
// I have updated the logics for geting last date of month
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;
}
Upvotes: 0
Reputation: 1
You can also use this code.
<?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');
Upvotes: 0
Reputation: 11
You can use this code. The first line accepts a Date in any format, and the later lines split off the month value, adding a month and returning the final value in Y-M-D
format.
$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 );
Upvotes: 1
Reputation: 476
For anyone interested I made a solid approach as to deal with this issue
/**
* @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;
}
Results:
2014-11-30
2014-12-30
2015-01-30
2015-02-28
2015-03-30
2015-04-30
2015-05-30
and
2015-12-30
2016-01-30
2016-02-29
2016-03-30
2016-04-30
Upvotes: 0
Reputation: 4274
You can compare the day of the month before and after you add 1 month. If it's not the same, you exceeded the next month.
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
Upvotes: 47
Reputation: 7904
As far as I can tell, this problem is very limited in scope, so you're likely to be best off by testing for one type of error, and fixing it.
All you want to do is make sure that adding "one month" to a late date like the 29th, 30th or 31st does not push you forward to the 1st, 2nd or 3rd of the next-next month.
The way date_modify() works (using it on an example date "2012-01-31" with a string like "+1 months"), is that it first increases the month number by 1, then finds the 31st day from the start of that month. This is why it spills over to March 3rd.
When this is not what you desired, all you have to do is use date_modify() again, now telling it to go back a few days (3 days in this example). Since you only want to go back to the last day of the previous month, the number of days you will want to go back is always the same as the day-of-month in your faulty date.
All that remains is to make sure you don't apply this correction when it is not needed, like when PHP were to improves in future. This is relatively easy, because the scope of the possible problem situations is very limited.
My code below adds "+1 month", checks if that has caused the day-of-month to change wildly from something high to something low, and adjusts the date if that's the case.
//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");
Results in: 2012-02-29 (yes, this was a leap year).
PS: If you want to add years, the problem and the symptoms are nearly identical. Again, you just need to to check if the day-of-month resulting is 1/2/3 and the day-of-month going in is 29/30/31. If so, you need to go back "-X days" using date_modify, where X is the resulting day-of-month.
Upvotes: 3
Reputation: 51807
this seems to work for me and gives yor desired result:
<?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:
after reading Crend Kings comemnt, i think we need more information here. whats the desired result in the following cases:
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 ?
in words: should the method add the number of days of the next month, wich is what Crend King does and what gives results like 2011-02-26 and 2011-03-03 (wich doesn't seem like the desired results to me) or should this add one month and leave the day as is, instead of a day thats "too high" like my code does? i'm confused...
Upvotes: 3