0

I am given data from an API in a format that is pretty difficult to use (in the manner that is necessary to be used in) - I do not have the ability to modify the API. Below is an example of the data I am given:

$data = array(
    array('day' => 'Monday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Tuesday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Wednesday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Thursday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Friday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Saturday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Sunday', 'start' => 1200, 'end' => 1400)
);

The data may not include all seven days of the week, start and end vary between 0000 and 2400. I am trying to come up with a way to format the data with the following output:

Monday - Wednesday (1100-1300), Thursday - Saturday (1200-1300), Sunday (1200-1400)

Basically, contiguous days (whose start and end times are the same) are separated by a hyphen.

I am kind of trying to avoid a huge block of ugly code.

3
  • 1
    It shouldn't be that ugly at all, just loop through the days of the week and compare start and end times. The only slightly tricky part would be to make sure that you're covering days in the right order, but that can be hard coded ($days_of_week=array("Monday","Tuesday",...,"Sunday");). Commented Oct 17, 2011 at 19:16
  • 1
    Is there guaranteed to only be a single sub-array for each day of the week? Commented Oct 17, 2011 at 19:19
  • See related tasks at Create date ranges from consecutive dates in an array Commented Sep 26, 2024 at 23:00

2 Answers 2

2

Ugliness is in the eye of the beholder. ;) I think this is OK, but of course it might be exactly what you're trying to avoid.

<?php
$data = array(
    array('day' => 'Monday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Tuesday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Wednesday', 'start' => 1100, 'end' => 1300),
    array('day' => 'Thursday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Friday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Saturday', 'start' => 1200, 'end' => 1300),
    array('day' => 'Sunday', 'start' => 1200, 'end' => 1400)
);

$periods = array();

$start = $end = $i = -1;

foreach ($data as $datum) {
    if ($start != $datum['start'] || $end != $datum['end']) {
        $i++;
        $start = $datum['start'];
        $end   = $datum['end'];

        $periods[$i] = array(
                          'start'    => $start, 
                          'end'      => $end,
                          'startDay' => $datum['day']);
    }
    $periods[$i]['endDay'] = $datum['day'];
}

foreach ($periods as $k => $period) {
    echo ($k) ? ', ' : '';
    if ($period['startDay'] === $period['endDay']) {
        echo $period['startDay'];
    } else {
        echo "{$period['startDay']} - {$period['endDay']}";
    }
    echo " ({$period['start']} - {$period['end']})";
}

Would output:

Monday - Wednesday (1100 - 1300), Thursday - Saturday (1200 - 1300), Sunday (1200 - 1400)
Sign up to request clarification or add additional context in comments.

1 Comment

Obviously, my solution assumes the data is encountered in the correct order in the first place. Also, if you're concerned about making your code look messy, place that in a function library or class elsewhere, and call Utilities::formatDatesNicely($data). =)
0

While looping over the rows unconditionally attempt to write or overwrite the word before the identical hour range (if at the string) with the current day of the week. If that replacement attempt fails, append the current day and time range to the end of the string.

This script has the benefit of not needing to pre-group the data and then loop again to present the grouped data. This approach is only an injection or concatenation process. Demo

$data = [
    ['day' => 'Monday', 'start' => 1100, 'end' => 1300],
    ['day' => 'Tuesday', 'start' => 1100, 'end' => 1300],
    ['day' => 'Wednesday', 'start' => 1100, 'end' => 1300],
    ['day' => 'Thursday', 'start' => 1200, 'end' => 1300],
    ['day' => 'Friday', 'start' => 1200, 'end' => 1300],
    ['day' => 'Saturday', 'start' => 1200, 'end' => 1300],
    ['day' => 'Sunday', 'start' => 1200, 'end' => 1400],
];

function hyphenateConsecutiveDaysWithDuplicateTimes(array $rows): string {
    $string = '';
    foreach ($rows as $row) {
        $time = "({$row['start']}-{$row['end']})";
        $string = preg_replace(
            "/(?: - [^(]+)?(?= \Q$time\E$)/",
            " - {$row['day']}",
            $string,
            1,
            $count
        );
        if (!$count) {
            $string .= ($string ? ', ' : '') . "{$row['day']} $time";
        }
    }
    return $string;
}

echo hyphenateConsecutiveDaysWithDuplicateTimes($data);

The regex pattern:

(?:        #start of non-capturing group
    -      #match space, hyphen, space
   [^(]+   #match one or more non-opening-parentheses
)?         #end of non-capture group and allow zero or one of the internal match
(?=        #start of lookahead      
    \Q     #match space, then tell the engine to treat everything literally
   $time   #match the parenthetical time range expression
   \E      #tell the engine to stop treating everything literally
   $       #match the end of the string
)          #end the lookahead

If the above script isn't pretty or intuitive enough, maybe declare more variables to make it more expressive.

function hyphenateConsecutiveDaysWithDuplicateTimes(array $rows): string {
    $string = '';
    $optionalEndDay = '(?: - [^(]+)?';
    foreach ($rows as $row) {
        extract($row);
        $time = "($start-$end)";
        $sameTimeRange = "(?= \Q$time\E$)";
        $string = preg_replace("/$optionalEndDay$sameTimeRange/", " - $day", $string, 1, $count);
        if (!$count) {
            $string .= ($string ? ', ' : '') . "$day $time";
        }
    }
    return $string;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.