Search code examples
phparraysdatetimerangerepeat

Duplicate rows of a 2d array to represent each date within the start and end date values of the row


I have the following array called $exhibitions:

Array
(
    [0] => Array
        (
            [exhibition_title] => Picasso
            [venue_name] => Gallery 1
            [room_name] => Room 4
            [start_date] => 2023-11-15
            [end_date] => 2023-12-10
            [room_id] => 261
            [exhibition_id] => 3540
            [venue_order] => 2
        )

    [1] => Array
        (
            [exhibition_title] => Monet
            [venue_name] => Gallery 4
            [room_name] => Room 2
            [start_date] => 2023-10-01
            [end_date] => 2023-11-13
            [room_id] => 274
            [exhibition_id] => 8417
            [venue_order] => 1
        )
)

What I need to do is use the start_date and end_date to create a range of dates each exhibition is open, and then for each of those dates essentially duplicate the rest of the information in the existing sub-array with the individual date appended to the end of the sub-array. My desired output would be:

Array
(
    [0] => Array
        (
            [exhibition_title] => Picasso
            [venue_name] => Gallery 1
            [room_name] => Room 4
            [start_date] => 2023-11-15
            [end_date] => 2023-12-10
            [room_id] => 261
            [exhibition_id] => 3540
            [venue_order] => 2
            [date] => 2023-11-15
        )
    [1] => Array
        (
            [exhibition_title] => Picasso
            [venue_name] => Gallery 1
            [room_name] => Room 4
            [start_date] => 2023-11-15
            [end_date] => 2023-12-10
            [room_id] => 261
            [exhibition_id] => 3540
            [venue_order] => 2
            [date] => 2023-11-16
        )
    [2] => Array
        (
            [exhibition_title] => Picasso
            [venue_name] => Gallery 1
            [room_name] => Room 4
            [start_date] => 2023-11-15
            [end_date] => 2023-12-10
            [room_id] => 261
            [exhibition_id] => 3540
            [venue_order] => 2
            [date] => 2023-11-17
        )
    [... etc. for the rest of the dates in the Picasso date range]

    [25] => Array
        (
            [exhibition_title] => Monet
            [venue_name] => Gallery 4
            [room_name] => Room 2
            [start_date] => 2023-10-01
            [end_date] => 2023-11-13
            [room_id] => 274
            [exhibition_id] => 8417
            [venue_order] => 1
            [date] => 2023-10-01
        )

    [26] => Array
        (
            [exhibition_title] => Monet
            [venue_name] => Gallery 4
            [room_name] => Room 2
            [start_date] => 2023-10-01
            [end_date] => 2023-11-13
            [room_id] => 274
            [exhibition_id] => 8417
            [venue_order] => 1
            [date] => 2023-10-02
        )

    [27] => Array
        (
            [exhibition_title] => Monet
            [venue_name] => Gallery 4
            [room_name] => Room 2
            [start_date] => 2023-10-01
            [end_date] => 2023-11-13
            [room_id] => 274
            [exhibition_id] => 8417
            [venue_order] => 1
            [date] => 2023-10-03
        )
    [... etc. for the rest of the dates in the Monet date range]
)

I saw this similar post and modified it so that instead of echoing the $given_date, it added it to the array in a new column called "date".

foreach ($exhibitions as $key => $value) {

    $startDate = strtotime($value['start_date']);
    $endDate = strtotime($value['end_date']);

    // Loop between timestamps, 24 hours at a time
    for ($i = $startDate; $i <= $endDate; $i = $i + 86400 ) {
            $given_date = date('Y-m-d', $i );     
            $exhibitions[$key]['date'] = $given_date;
    }    

};

The problem was that instead of adding one date per date in the date range, it added the last date in the range only. When I echoed the $given_date just to make sure it was iterating each date, it lists every date in the range, so I'm assuming there's something wrong with how I added the new column but I can't figure out what.


Solution

    • Iterate your input array's rows and access the start and end dates.
    • Create an iterable payload of dates using PHP's native DateTime methods.
    • Loop over those dates, append the individual date to the row data, then pass the extended row into the result array.

    To have an "inclusive" range of dates, add 1 day to the end date (otherwise every range will be one day short).

    By appending the generated date to the row data with the union operator (+), you avoid needing to write out every element in the row while pushing.

    Code: (Demo)

    $result = [];
    foreach ($exhibitions as $row) {
        $dateRange = new DatePeriod(
            new DateTime($row['start_date']),
            new DateInterval('P1D'),  // 1 day per step
            new DateTime("{$row['end_date']} +1 day")  // +1 day to include end date
        );
        foreach ($dateRange as $d) {
            $result[] = $row + ['date' => $d->format("Y-m-d")];
        }
    }
    var_export($result);
    

    ...from PHP8.2, you can use a special flag instead of +1 day. (Demo)

    $dateRange = new DatePeriod(
        new DateTime($row['start_date']),
        new DateInterval('P1D'),
        new DateTime($row['end_date']),
        DatePeriod::INCLUDE_END_DATE
    );