Search code examples
phparraysloopsrangeincrement

Get month list starting from a given month and ending to another?


Given two integers (each that represent months in a year), I need to produce an array of integers that represents the inclusive range of months between them.

To get the month numbers between December and February, the input is:

$start = 12;
$finish = 2;

The expected output contains three elements: 12, 1, and 2 (because December, January, then February)

I have tried using $arraylist = range($start, $finish);, but when the $start value is greater than the $finish value, then the output is an incorrect descending range like this: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2]

Here are some more input-output examples:

+--------+---------+-----------------------+
| $start | $finish |        $output        |
+--------+---------+-----------------------|
|    3   |    11   | [3,4,5,6,7,8,9,10,11] |
+--------+---------+-----------------------|
|   11   |     3   |     [11,12,1,2,3]     |
+--------+---------+-----------------------|
|    3   |     3   |           [3]         |
+--------+---------+-----------------------+

Solution

  • It can be much less complicated than decrementing everything, using modulus/arithmetic, and bumping numbers into the preferred value.

    • Loop while the two numbers are not the same.
    • If the incremented number is increased to more than 12, reset it to 1.
    • To make the range inclusive, push the finish number into the array.

    Code (Demo)

    $array = [];
    while ($start != $finish) {
        if ($start > 12) {
            $start = 1;
        }
        $array[] = $start++;  // increment AFTER pushing into the array
    }
    $array[] = $finish;
    var_export($array);
    

    I think this is less cryptic to read.

    Inputs/Outputs:

    11, 3: [11, 12, 1, 2, 3]
    
    3, 11: [3, 4, 5, 6, 7, 8, 9, 10, 11]
    
    3, 3: [3]
    

    p.s. The following functional style doesn't feel silly either. There will be either 1 or 3 function calls, but again I think it is pretty easy to read AND there is no loop, no temporary variables, and no incrementing/decrementing.

    Since the volume of data is guaranteed to be small, performance probably doesn't even factor in (no human is going to notice a difference in performance between an techniques on this page). It would be better to select a technique that is readable.

    Code: (Demo)

    $array = $start > $finish
        ? array_merge(range($start, 12), range(1, $finish))
        : range($start, $finish);
    

    p.p.s. A final consideration... If anyone would prefer to handle the month logic with a DateTime technique, this would also be sensible and may open a door to extending the functionality further (if the task should ever need it). The drawback is that this technique has the most overhead because it is instantiating objects to work with.

    To make the output array include the last month, an easy solution is to add 1 to the finish. If the $begin value is less than the $finish value, then we will push the value into next year by adding 12.

    Code: (Demo)

    $begin = 11;
    $finish = 3;
    
    $period = new DatePeriod(
        DateTime::createFromFormat('m', $begin),
        new DateInterval('P1M'),
        DateTime::createFromFormat('m', $finish + ($begin > $finish ? 13 : 1))
    );
    
    foreach ($period as $obj) {
        $array[] = $obj->format('n');
    }
    
    var_export($array);