14

I created a function that returns an array containing each month, starting from a supplied carbon date and ending on the current date.

Although the function is doing what it is supposed to do, it looks hideous. Clearly my programming skills arent yet what they're supposed to be. Surely there must be a better way to achieve what i want.

My code looks like this:

    class DateUtilities {
    protected $months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];

    public function getMonthListFromDate(Carbon $date)
    {
        $monthArray = array();
        $today = Carbon::today();

        $currentYear = $today->copy()->format('Y');
        $currentMonth = strtolower($today->copy()->format('F'));

        $startYear = $date->copy()->format('Y');
        $startMonth = strtolower($date->copy()->format('F'));

        for($i = $startYear; $i <= $currentYear; $i ++) {
            foreach($this->months as $monthIndex => $month) {
                if (($monthIndex >= array_search($startMonth, $this->months) && $i == $startYear) ||
                    ($monthIndex <= array_search($currentMonth, $this->months) && $i == $currentYear) ||
                    ($i != $startYear && $i != $currentYear)) {
                    $formattedMonthIndex = ($monthIndex + 1);

                    if($formattedMonthIndex < 10) {
                        $monthArray['0' . $formattedMonthIndex . '-' . $i] = $month . ' ' . $i;
                    } else {
                        $monthArray[$formattedMonthIndex . '-' . $i] = $month . ' ' . $i;
                    }
                }
            }
        }

        return $monthArray;
    }
}

and the result is:

array:25 [▼
  "03-2013" => "march 2013"
  "04-2013" => "april 2013"
  "05-2013" => "may 2013"
  "06-2013" => "june 2013"
  "07-2013" => "july 2013"
  "08-2013" => "august 2013"
  "09-2013" => "september 2013"
  "10-2013" => "october 2013"
  "11-2013" => "november 2013"
  "12-2013" => "december 2013"
  "01-2014" => "january 2014"
  "02-2014" => "february 2014"
  "03-2014" => "march 2014"
  "04-2014" => "april 2014"
  "05-2014" => "may 2014"
  "06-2014" => "june 2014"
  "07-2014" => "july 2014"
  "08-2014" => "august 2014"
  "09-2014" => "september 2014"
  "10-2014" => "october 2014"
  "11-2014" => "november 2014"
  "12-2014" => "december 2014"
  "01-2015" => "january 2015"
  "02-2015" => "february 2015"
  "03-2015" => "march 2015"
]

Can anyone help me improve this code?

EDIT:

After the great tips i ended up with the following:

class DateUtilities {

public function getMonthListFromDate(Carbon $start)
{
    $start = $start->startOfMonth();
    $end   = Carbon::today()->startOfMonth();

    do
    {
        $months[$start->format('m-Y')] = $start->format('F Y');
    } while ($start->addMonth() <= $end);

    return $months;
}

}

Thank you for the help guys!!

vincent
  • 1,243
  • 4
  • 15
  • 29

5 Answers5

22

Late to this party, but worth adding that Carbon already has this covered. Using CarbonPeriod would reduce the whole class to ...

use Carbon\Carbon;
use Carbon\CarbonPeriod;

class DateUtilities
{
    public function getMonthListFromDate(Carbon $start)
    {
        $start->setDay(1);
        foreach (CarbonPeriod::create($start, '1 month', Carbon::today()) as $month) {
            $months[$month->format('m-Y')] = $month->format('F Y');
        }
        return $months;
    }
}
petercoles
  • 1,732
  • 13
  • 20
  • I like this way of doing things. Ran into an issue with it today though; February was not returned as a result since it's the 29th, and since last month was February (which doesn't have a 29th). I was able to resolve that by adding `$start->setDay(1);` as the first line within the function. – Whipenstein Mar 29 '23 at 18:14
  • 1
    Good point. Answer edited to incorporate this. – petercoles Mar 30 '23 at 09:34
15

With DateTime this could be easily achieved.

  1. Create a datetime object for each start and end date
  2. Setup an interval of 1 month
  3. Get a set of date beetween the start date and the end date with 1 month interval

Example :

public function getMonthListFromDate(Carbon $date)
{
    $start    = new DateTime(); // Today date
    $end      = new DateTime($date->toDateTimeString()); // Create a datetime object from your Carbon object
    $interval = DateInterval::createFromDateString('1 month'); // 1 month interval
    $period   = new DatePeriod($start, $interval, $end); // Get a set of date beetween the 2 period

    $months = array();

    foreach ($period as $dt) {
        $months[] = $dt->format("F Y");
    }

    return $months;
}

See it in action : http://3v4l.org/smS3N

BentoumiTech
  • 1,627
  • 14
  • 21
  • Your 3v4l demo is not identical to your posted snippet. I find the use of `->modify('first day of this month')` to be VERY necessary for stability, but you do not mention this adjustment in your answer. Pretty darn related: https://stackoverflow.com/a/18743012/2943403 – mickmackusa Mar 03 '22 at 13:03
8

Use CarbonPeriod class todo same

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

foreach ($period as $dt) {
     echo $dt->format("Y-m") . "<br>\n";
}
Abdo-Host
  • 2,470
  • 34
  • 33
2

Here's an example that's much shorter and simpler. You can adapt it to the conventions of your Carbon class easily:

$start = strtotime('last month', strtotime('2013-03'));
$now = time();

while(($start = strtotime('next month', $start)) <= $now) {
    $result[date('m-Y', $start)] = strtolower(date('F Y', $start));
}
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
2
while ($start->lte($end)) {
            $months[$start->format('m-Y')] = $start->format('F Y');
            $start->addMonth();
        }
        return $months;
Bapon
  • 21
  • 2