2

I have an array of performance dates for an event such as:

2014-01-20 20:00:00
2014-01-21 20:00:00
2014-01-22 14:00:00
2014-01-22 20:00:00
2014-01-23 20:00:00
2014-01-25 20:00:00
2014-01-26 20:00:00
2014-01-31 20:00:00
2014-02-01 20:00:00

Is there a straightforward way in php to turn this into a human-readable string such as;

20th-23rd Jan 8pm, 22nd Jan 2pm, 25th-26th Jan 8pm, 31st Jan - 1st Feb 8pm

Note the array will always be in datetime order, although not necessarily contiguous. Matinee performances throw in an extra complication as they need to be dealt with separately.

3
  • Is the array populated with strings or datetime objects? Commented Jan 19, 2014 at 16:42
  • 1
    Straightforward? Maybe. But there are ways - and some should be quite intuitive and not too complex. However, you'll need to write code Commented Jan 19, 2014 at 16:42
  • Yup, fully expecting to have to write some php code. What are the intuitive and not too complex ways you allude to? Commented Jan 19, 2014 at 16:51

4 Answers 4

2

We’ll convert the strings to timestamps with array_map and strtotime and then group together the consecutive elements in the array which are in an Arithmetic Progression with common difference = 86400 (because 86400 seconds = 1 day):

$arr = array(
    "2014-01-20 20:00:00",
    "2014-01-21 20:00:00",
    "2014-01-22 14:00:00",
    "2014-01-22 20:00:00",
    "2014-01-23 20:00:00",
    "2014-01-25 20:00:00",
    "2014-01-26 20:00:00",
    "2014-01-31 20:00:00",
    "2014-02-01 20:00:00"
);
$arr = array_map("strtotime", $arr);
$_arr = array();
$size = sizeof($arr);
$val = $arr[0];
for($i = 0; $i < $size; $i++){
    if(!array_key_exists($i+1, $arr) || $arr[$i+1]-$arr[$i] != 86400){
    $d1 = date("jS", $val);
    $d2 = date("jS", $arr[$i]);
    $m1 = date("M", $val);
    $m2 = date("M", $arr[$i]);
    $t = date("ga", $val);
        if($m1 == $m2){
            if($d1 == $d2){
            $_arr[] = $d1." ".$m1." ".$t;
            }
            else{
            $_arr[] = $d1."-".$d2." ".$m1." ".$t;
            }
        }
        else{
        $_arr[] = $d1." ".$m1."-".$d2." ".$m2." ".$t;
        }
        if(array_key_exists($i+1, $arr)){
        $val = $arr[$i+1];
        }
    }
}
print(implode(", ", $_arr));

The output will be:

20th-21st Jan 8pm, 22nd Jan 2pm, 22nd-23rd Jan 8pm, 25th-26th Jan 8pm, 31st Jan-1st Feb 8pm

See the code at work here.

Sign up to request clarification or add additional context in comments.

2 Comments

That just turns each date into another format for the date - I'm trying to turn the data into contiguous ranges.
This answer does not properly generate the range from 20th to 23rd for 8pm. It should not be ticked.
1

I think Sharanya's answer is probably the most elegant solution, although I'll share what I managed to come up with myself.

The basic logic was to create a temporary working array to hold the ranges of dates with a start and end date plus the time of the performance. I then looped through the array of dates and added to the array when the end date wasn't 1 day less than the current date or the time didn't match.

$dates = array("2014-01-20 20:00:00","2014-01-21 20:00:00","2014-01-22 14:00:00","2014-01-22 20:00:00","2014-01-23 20:00:00","2014-01-25 20:00:00","2014-01-26 20:00:00","2014-01-31 20:00:00","2014-02-01 20:00:00");

$workingarray = array('start' => array(), 'end' => array(), 'time' => array());

// Loop through the results 
foreach($dates as $date) {
    // Check to see if there's a match at this time already
    $key = reverse_search(date("ga", strtotime($date)), $workingarray['time']);
    if($key===false) {
        $workingarray['start'][] = $date;
        $workingarray['end'][] = $date;
        $workingarray['time'][] = date("ga", strtotime($date));
    } else {
        // Check to see if the end date of this performance is exactly one day less
        if($workingarray['end'][$key]==date("Y-m-d H:i:s", strtotime('-1 day', strtotime($date)))) {
            $workingarray['end'][$key] = $date;
        } else {
            $workingarray['start'][] = $date;
            $workingarray['end'][] = $date;
            $workingarray['time'][] = date("ga", strtotime($date));
        }
    }
}

// All the ranges are now in the $workingarray array, so just need to assemble them
$datesarray = array();
for($n = 0; $n <= count($workingarray); $n++) {
    // Handle situations where range spans different months
    if(date("m", strtotime($workingarray['start'][$n]))==date("m", strtotime($workingarray['end'][$n]))) {
        $startstr = date("jS - ", strtotime($workingarray['start'][$n]));
    } else {
        $startstr = date("jS M - ", strtotime($workingarray['start'][$n]));
    }
    // Handle situations where performance is only on one day
    if($workingarray['start'][$n]==$workingarray['end'][$n]) {
        $startstr = "";
    }
    $endstr = date("jS M ", strtotime($workingarray['end'][$n]));
    $timestr = $workingarray['time'][$n];
    $datesarray[] = $startstr.$endstr.$timestr;
}
// Glue the array together as one string
$datestring = implode(", ", $datesarray);
echo $datestring;

// Function to find last matching key as array_search will only find the first one
function reverse_search($value, $array) {
    $found = FALSE;
    foreach($array as $key => $item) {
        if($item==$value) {
            $found = $key;
        }
    }
    return $found;
}

Comments

1

Here is an approach that builds an array of ranged showtimes in a single iteration over the input array, then joins the array after the loop is finished.

The magic is in pushing reference strings into the result array, then using regular expressions to conditionally modify those strings.

Code: (Demo)

$showtimes = [
    "2014-01-20 20:00:00",
    "2014-01-21 20:00:00",
    "2014-01-22 14:00:00",
    "2014-01-22 20:00:00",
    "2014-01-23 20:00:00",
    "2014-01-25 20:00:00",
    "2014-01-26 20:00:00",
    "2014-01-31 20:00:00",
    "2014-02-01 20:00:00"
];

foreach ($showtimes as $showtime) {
    $dt = new DateTime($showtime);
    $thisDay = $dt->format('Y-m-d H:i:s');
    $nextDay = (clone $dt)->modify('+1 day')->format('Y-m-d H:i:s');
    // stand alone showtime
    if (!isset($ref[$thisDay])) {
        $ref[$nextDay] = $dt->format('jS M ga');
        $result[] =& $ref[$nextDay];
        continue;
    }
    // if same month
    if (str_contains($ref[$thisDay], $dt->format('M'))) {
        // inject day
        $ref[$thisDay] = preg_replace(
            '/[tdh]\b\K[^ ]*/',
            $dt->format('-jS'),
            $ref[$thisDay]
        );
    } else {
        // inject day and month
        $ref[$thisDay] = preg_replace(
            '/ (?=[^ ]*$)/',
            $dt->format(' - jS M '),
            $ref[$thisDay]
        );
    }
    // continue the chain of reference to the result element
    $ref[$nextDay] =& $ref[$thisDay];
}
echo implode(', ', $result);

Output:

20th-23rd Jan 8pm, 22nd Jan 2pm, 25th-26th Jan 8pm, 31st Jan - 1st Feb 8pm

Comments

1

Updated answer for 2024

I wrote a generic function that takes an array of date strings and convert them into a human readable date ranges. Note that it makes use of the DateTime class, which is available by default on PHP => 5.2.0.

Here's the full function:

<?php

function formatDates($dates) {
    $output = [];
    $evening_ranges = [];
    $matinees = [];
    $last_evening_date = null;

    foreach ($dates as $date) {
        $dateTime = new DateTime($date);
        $time = $dateTime->format('ga');

        if ($time == '2pm') {
            $matinees[] = $dateTime;
        } else {
            if ($last_evening_date && $last_evening_date->modify('+1 day')->format('Y-m-d') != $dateTime->format('Y-m-d')) {
                if (!empty($evening_ranges)) {
                    $output[] = formatRange($evening_ranges);
                }
                $evening_ranges = [];
            }
            $evening_ranges[] = $dateTime;
            $last_evening_date = clone $dateTime;
        }
    }

    if (!empty($evening_ranges)) {
        $output[] = formatRange($evening_ranges);
    }

    foreach ($matinees as $matinee) {
        $output[] = [$matinee];
    }

    usort($output, function($a, $b) {
        return $a[0] <=> $b[0];
    });

    return implode(', ', array_map('formatOutput', $output));
}

function formatRange($range) {
    $start = $range[0];
    $end = end($range);

    if ($start->format('M') == $end->format('M')) {
        return [$start, $end, $start->format('ga')];
    } else {
        return [$start, $end, $start->format('ga')];
    }
}

function formatOutput($range) {
    if (count($range) == 1) {
        return $range[0]->format('jS M ga');
    } else {
        $start = $range[0];
        $end = $range[1];
        $time = $range[2];

        if ($start->format('M') == $end->format('M')) {
            return $start->format('jS') . '-' . $end->format('jS M') . ' ' . $time;
        } else {
            return $start->format('jS M') . '-' . $end->format('jS M') . ' ' . $time;
        }
    }
}

It can be used as below:

$dates = [
    '2014-01-20 20:00:00',
    '2014-01-21 20:00:00',
    '2014-01-22 14:00:00',
    '2014-01-22 20:00:00',
    '2014-01-23 20:00:00',
    '2014-01-25 20:00:00',
    '2014-01-26 20:00:00',
    '2014-01-31 20:00:00',
    '2014-02-01 20:00:00'
];

echo formatDates($dates);

Output:

20th-23rd Jan 8pm, 22nd Jan 2pm, 25th-26th Jan 8pm, 31st Jan-1st Feb 8pm

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.