68

I'm trying to list all months between two dates.

For example; start date is: 2010-12-02 and last date is: 2012-05-06

I want to list something like this:

2010-12
2011-01
2011-02
2011-03
2011-04
.
.
.
2012-04
2012-05

This is what I have tried and it is not working at all:

    $year_min = 2010;
    $year_max = 2012;
    $month_min = 12;
    $month_max = 5;
    for($y=$year_min; $y<=$year_max; $y++)
    {
        for($m=$month_min; $m<=$month_max; $m++)
        {
            $period[] = $y.$m;
        }
    }
R. Arnone
  • 423
  • 4
  • 15
Pooya
  • 1,508
  • 3
  • 17
  • 38

6 Answers6

232

PHP 5.3

$start    = new DateTime('2010-12-02');
$start->modify('first day of this month');
$end      = new DateTime('2012-05-06');
$end->modify('first day of next month');
$interval = DateInterval::createFromDateString('1 month');
$period   = new DatePeriod($start, $interval, $end);

foreach ($period as $dt) {
    echo $dt->format("Y-m") . "<br>\n";
}

See it in action

PHP 5.4 or newer

$start    = (new DateTime('2010-12-02'))->modify('first day of this month');
$end      = (new DateTime('2012-05-06'))->modify('first day of next month');
$interval = DateInterval::createFromDateString('1 month');
$period   = new DatePeriod($start, $interval, $end);

foreach ($period as $dt) {
    echo $dt->format("Y-m") . "<br>\n";
}

The part where we modify the start and end dates to the first of the month is important. If we didn't, and the current day higher then the last day in February (i.e. 28 in non-leap years, 29 in leap years) this would skip February.

John Conde
  • 217,595
  • 99
  • 455
  • 496
  • 1
    Isn't there a mistake in version for PHP 5.4 or newer? In version below 5.4 there is `$end->modify('first day of next month');` and above 5.4 is `->modify('first day of this month');` – Krewetka Jul 24 '15 at 13:31
  • Neat solution, what about reversed order? Switching `$start` with `$end` does nothing... – Martin Nov 10 '16 at 12:16
  • 1
    @Martin Just put the results in an array and reverse it – John Conde Nov 10 '16 at 12:54
  • swap new DatePeriod($end, $interval, $start) and array_reverse(iterator_to_array($period)) – Adler Dias Jul 21 '17 at 14:40
  • `echo(iterator_count($period));` – John Conde Nov 17 '17 at 21:15
  • Not an issue for this question (since it only specifies dates), but I've ran into the issue that it adds an additional month at the end when the `$start` time is smaller than the `$end` time. Using `$start->setTime(0,0);` and `$end->setTime(0,0);` seems to fix it. – user2375667 Jul 19 '19 at 23:43
  • this is the best way to solve if between dates too close, example `var date1 = "2022-09-29"; var date2 = "2022-10-10";` – Bekti Galan Oct 13 '22 at 09:14
13
function getMonthsInRange($startDate, $endDate)
{
    $months = array();

    while (strtotime($startDate) <= strtotime($endDate)) {
        $months[] = array(
            'year' => date('Y', strtotime($startDate)),
            'month' => date('m', strtotime($startDate)),
        );

        // Set date to 1 so that new month is returned as the month changes.
        $startDate = date('01 M Y', strtotime($startDate . '+ 1 month'));
    }

    return $months;
}
linktoahref
  • 7,812
  • 3
  • 29
  • 51
sulayman
  • 137
  • 2
  • 7
10

You must make a difference between two months of the same year and two months of different years.

$year_min = substr($row['contractStart'], 0, 4);
$year_max = substr($row['contractEnd'], 0, 4);
$month_min = substr($row['contractStart'], 5, 2);
$month_min = substr($row['contractEnd'], 5, 2);
$period = array();
try {
  if ($year_min > $year_max)
    throw new Exception();
  else if ($year_min == $year_max)
    if ($month_min > $month_max)
      throw new Exception();
    for ($month = $month_min; $month <= $month_max; $month++) {
      $period[] = $month . '-' . $year;
    }
  else {
    for ($month = $month_min; $month <= 12; $month++) {
      $period[] = $month . '-' . $year_min;
    }
    for ($year = $year_min + 1; $year < $year_max; $year++) {
      for ($month = $month_min; $month <= $month_max; $month++) {
        $period[] = $month . '-' . $year;
      }
    }
    for ($month = 1; $month <= $month_max; $month++) {
      $period[] = $month . '-' . $year_max;
    }
  }
  implode("<br />\r\n", $period);
}
catch (Exception $e) {
  echo 'Start date occurs after end date.'
}

That's for the hard way. Now there is a quick and easy way that is already given as an answer which I recommend you to choose.

Mehravish Temkar
  • 4,275
  • 3
  • 25
  • 44
SteeveDroz
  • 6,006
  • 6
  • 33
  • 65
5

This was my solution since DateTime is not available in my server environment.

$a = "2007-01-01";
$b = "2008-02-15";

$i = date("Ym", strtotime($a));
while($i <= date("Ym", strtotime($b))){
    echo $i."\n";
    if(substr($i, 4, 2) == "12")
        $i = (date("Y", strtotime($i."01")) + 1)."01";
    else
        $i++;
}

Try it out: http://3v4l.org/BZOmb

CptAJ
  • 1,156
  • 1
  • 13
  • 20
2

In Laravel,

$period = \Carbon\CarbonPeriod::create('2017-06-28', '1 month', '2019-06-01');

foreach ($period as $dt) {
     echo $dt->format("Y-m") . "<br>\n";
}
Chandan Sharma
  • 2,321
  • 22
  • 22
0

October 2021 Update
If you have dates selected by the user, here's a solution

$from = date('Y-m-d', strtotime($_POST['from']));
$to = date('Y-m-d', strtotime($_POST['to']));

$counter = 1;
$max_date = strtotime($to);
$current_date = strtotime($from);
$dates = [];
$months = [];
$loop = true;
while($loop) {
    if(strtotime(date('Y-m-d',$current_date)." +".$counter."days") >= $max_date) $loop = false;
    else {
        $current_date = strtotime(date('Y-m-d', $current_date)." +".$counter."days");
        $date = date('Y-m-d', $current_date);
        $dates[] = $date;
        $months[] = date('Y-m', $current_date);
        $counter++;
    }
}
$months = array_unique($months);
echo '<pre>';
print_r($dates);
echo '<br>';
print_r($months);
echo '</pre>';
Peter
  • 626
  • 6
  • 6
  • Even if you have dates selected by the user, you should still be using datetime objects. This answer is too convoluted and is missing its educational explanation. – mickmackusa Mar 03 '22 at 13:10