Given: date of December 31, 2004. Task: determine the date, which was three months ago. In MySQL we think so:

SELECT DATE_SUB('2004-12-31', INTERVAL 3 MONTH) 

We get 2004-09-30. This date suits us and we want to get it at the PHP level:

 $maxDate = '2004-12-31'; $timestamp = ; echo date('Ym-d', strtotime($maxDate . ' - 3 month')); 

We get 2004-10-01. It is logical, but not quite what we expect.

We try differently:

 $Date = new DateTime('2004-12-31'); $Date->sub(DateInterval::createFromDateString('3 months')); echo $Date->format('Ym-d'); 

Again, we get October 1.

How, without a garden, in PHP to get the expected date?

  • Write your function is necessary. The authors of php and mysql in each month of the year by 30.4375 days;) - Visman
  • It's easier to determine for yourself how many days in a month (for example, 30) and to proceed from this .. Either write your function - Alexey Shimansky
  • The most interesting thing is that PostgreSQL also returns "2004-06-30" - ilyaplot
  • @ Alexey Shimansky easier? Yes, this is the most difficult option - ilyaplot
  • That is, you want, if in the target month there are fewer days than the current date, take the last day of that month. This is just one if - splash58

3 answers 3

 $Date = new DateTime('2004-12-31'); $shift = -3; // сохраним день $day = $Date->format('d'); // первый день целевого месяца $Date->modify('first day of this month')->modify(($shift > 0 ? '+':'') . $shift . ' months'); // если наш день больше числа дней в месяце, возьмем последний $day = $day > $Date->format('t') ? $Date->format('t') : $day; // готово echo $Date->modify('+' . $day-1 . ' days')->format('c'); 
  • As for me, it is so doubtful that May 29/30/31 minus 3 months is all February 28 - Alexey Shimansky
  • For 2005-01-01 we will get 2004-10-31, for 2004-12-31 we will also get 2004-10-31. I think my method is more correct. Took 3 months - ilyaplot
  • @ Alexey Shimansky, This is to the logic of the question. For example, counting the days of loan repayment is also not transferred to another month - splash58
  • @ilyaplot and did not try to run the code? eval.in/919640 - splash58

This solution will suit?

 echo findDate('2004-12-31', -3); // 2004-09-29 function findDate($start_date, $months) { $start_date_object = new DateTime($start_date); $date_interval = findInterval($months, $start_date_object); $end_date_object = $start_date_object->add($date_interval); $end_date_object->sub(new DateInterval('P1D')); return $end_date_object->format('Ym-d'); } function findInterval($n_months, DateTime $start_date_object) { $date_of_last_day_next_month = new DateTime($start_date_object->format('Ym-d')); $date_of_last_day_next_month->modify('last day of +'.$n_months.' month'); if($start_date_object->format('d') > $date_of_last_day_next_month->format('d')) { return $start_date_object->diff($date_of_last_day_next_month); } else { return new DateInterval('P'.$n_months.'M'); } } 
  • We expect that 2004-09-30 - ilyaplot

It looks like PHP has a bug. http://php.net/manual/ru/datetime.modify.php#119082

 function subMonths(\Datetime $dateTime, int $months) { if ($invert = $months < 0) { $months *= -1; } for ($i=0; $i<$months; $i++) { $daysOfMonth = cal_days_in_month(CAL_GREGORIAN, $dateTime->format('m'), $dateTime->format('Y')); $dateTime->modify(($invert ? '-' : '+') . $daysOfMonth . ' day'); } return $dateTime; } $dateTime = new \DateTime('2004-12-31'); echo subMonths($dateTime, -3)->format('Ym-d'); // 2004-09-30 
  • No no no. Here you just subtracted a few hours from the date (or added - depending on the time zone). If you try to count from 2004-12-30, you get 2004-09-29. - Denis Khvorostin
  • If from 2004-12-31 we get 2004-09-30, then it is logical to get for 2004-12-30 the date 2004-09-29 - ilyaplot
  • It is logical, but not that. - Denis Khvorostin
  • @DenisKhvorostin In general, in the comments on the official website mentioned the problem php.net/manual/ru/datetime.modify.php#119082 - ilyaplot
  • Yes, I saw that comment. The problem is clear, it is not clear how to solve it with a little blood. - Denis Khvorostin