5
\$\begingroup\$

I have an array of dates as input in the form "YYYY-mm-dd"=>category_id.0 (category_id counting from 0).

$days =array (
  '2014-10-15' => 49,
  '2014-10-16' => 49,
  '2014-10-17' => 49,
  '2014-10-18' => 0,
  '2014-10-19' => 0,
  '2014-10-20' => 0,
  '2014-10-21' => 0,
  '2014-10-22' => 0,
  '2014-10-23' => 0,
  '2014-10-24' => 0,
  '2014-10-25' => 0,
  '2014-10-26' => 0,
  '2014-10-27' => 0,
  '2014-10-28' => 0,
  '2014-10-29' => 0,
  '2014-10-30' => 0,
  '2014-10-31' => 0,
  '2014-11-01' => 0,
  '2014-11-02' => 0,
  /* MISSING 2014-11-03 */
  '2014-11-04' => 0,
  '2014-11-05' => 0,
  '2014-11-06' => 0,
  '2014-11-07' => 0,
  '2014-11-08' => 0,
  '2014-11-09' => 0,
  '2014-11-10' => 0,
  '2014-11-11' => 0,
  '2014-11-12' => 0,
  '2014-11-13' => 0,
  '2014-11-14' => 0,
  '2014-11-15' => 0,
  '2014-11-16' => 0,
  '2014-11-17' => 0,
  '2014-11-18' => 0,
  '2014-11-19' => 0,
  '2014-11-20' => 0,
  '2014-11-21' => 0,
  '2014-11-22' => 0,
  '2014-11-23' => 0,
  '2014-11-24' => 0,
  '2014-11-25' => 0,
  '2014-11-26' => 0,
  '2014-11-27' => 0,
  '2014-11-28' => 0,
  '2014-11-29' => 0,
  '2014-11-30' => 0,
  '2014-12-01' => 0,
  '2014-12-02' => 0,
  '2014-12-03' => 0,
  '2014-12-04' => 0,
  '2014-12-05' => 0,
  '2014-12-06' => 0,
  '2014-12-07' => 0,
  '2014-12-08' => 0,
  '2014-12-09' => 0,
  '2014-12-10' => 0,
  '2014-12-11' => 0,
  '2014-12-12' => 0,
  '2014-12-13' => 0,
  '2014-12-14' => 0,
  '2014-12-15' => 0,
  '2014-12-16' => 0,
  '2014-12-17' => 0,
  '2014-12-18' => 0,
  '2014-12-19' => 0,
  '2014-12-20' => 0,
  '2014-12-21' => 0,
  '2014-12-22' => 1,
  '2014-12-23' => 3,
  '2014-12-24' => 3,
  '2014-12-25' => 3,
  '2014-12-26' => 3,
  '2014-12-27' => 3,
  '2014-12-28' => 3,
  '2014-12-29' => 50,
  '2014-12-30' => 50,
  '2014-12-31' => 50,
  '2015-12-24' => 2,
  '2015-12-25' => 2,
);

I want to build an array of range of dates grouped by category_id.

For example, category_id 49 has 3 consecutive days from 15 to 17, so the first range should be: array('2014-10-15',2014-10-17',49).

the category_id 0 start from 2014-10-18 and end to 2014-12-21 HOWEVER due the missing day 2014-11-03 I must split the range in two parts:

array('2014-10-18','2014-11-02',0) and array('2014-11-04','2014-12-21',0)

The final output is an array of ranges like this:

Array
(
    [0] => Array
        (
            [0] => 2014-10-15
            [1] => 2014-10-17
            [2] => 49
        )

    [1] => Array
        (
            [0] => 2014-10-18
            [1] => 2014-11-02
            [2] => 0
        )

    [3] => Array
        (
            [0] => 2014-11-04
            [1] => 2014-12-21
            [2] => 0
        )

    [4] => Array
        (
            [0] => 2014-12-22
            [1] => 2014-12-22
            [2] => 1
        )

    [5] => Array
        (
            [0] => 2014-12-23
            [1] => 2014-12-28
            [2] => 3
        )

    [6] => Array
        (
            [0] => 2014-12-29
            [1] => 2014-12-31
            [2] => 50
        )

    [8] => Array
        (
            [0] => 2015-12-24
            [1] => 2015-12-25
            [2] => 2
        )

)

Due the possibility of to have holes in days array, I tought that the best way is to get the first day (in temporary order), then add a day for each cycle. Checking the values, I build the range array.

function get_range_from_days($days) {

    // check if input is empty
    if(count($days) == 0) return array();

    // output to fill
    $ranges = array();

    // order dates keys in order to have the first date in temporary order
    ksort($days);

    // get first and last day
    $firstday = key($days);
    end($days);
    $lastday = key($days);

    // get the type of first day (actually the current day where we looks)
    $current_type = $days[$firstday];

    // using datetime object for easy step of 1 day
    $datetime = new \DateTime($firstday);
    $datetime->setTime(9,0,0); // avoid time problems at midnight (it's needed?)

    // do the first step outside the while
    $datetime->add(new DateInterval('P1D'));

    // store old value of day
    $oldday = $firstday;

    // build the first range
    $ranges[] = array($firstday,null,$current_type);

    while(($day = $datetime->format('Y-m-d')) <= $lastday) {

        // if there are holes, fill it with null
        if(!isset($days[$day])) {
            $days[$day] = null;
        }

        // check if type has changed (=>need new range)
        if(($days[$day] !== $current_type)) {
            $ranges[count($ranges)-1][1] = $oldday;
            $ranges[] = array($day,null,$days[$day]);
            $current_type = $days[$day];
        }

            // store previous day
        $oldday = $day;
            // next day
        $datetime->add(new DateInterval('P1D'));

    }

    // complete the last range
    $ranges[count($ranges)-1][1] = $lastday;

    // remove range of holes
    foreach($ranges as $k=>$range) {
        if(is_null($range[2])) {
            unset($ranges[$k]);
        }
    }

    return $ranges;
}

Any improvements? This looks not so nice.

\$\endgroup\$
  • \$\begingroup\$ Can you explain at least a little bit what your code is doing ? \$\endgroup\$ – Loufylouf Mar 2 '15 at 14:05
  • \$\begingroup\$ Hi @Loufylouf, What's not clear? input/output or the way to build the output? \$\endgroup\$ – Luca Rainone Mar 2 '15 at 14:11
  • \$\begingroup\$ The way to build the output. But I overlooked, that seems quite simple, but a little description wouldn't hurt (at least it wouldn't look like "here's the input/output of my code, and my code, improve it my minions" :) ) \$\endgroup\$ – Loufylouf Mar 2 '15 at 14:29
  • \$\begingroup\$ @Loufylouf done... Sorry but I thought that it was auto explicative, and I was afraid that too many comments made the code not readable :-) \$\endgroup\$ – Luca Rainone Mar 2 '15 at 15:00
  • \$\begingroup\$ No, I cannot see what you're trying to achieve here. What does it mean? Why this input and output? Are the values in the input irrelevant? Why? The number value in the output is what? \$\endgroup\$ – KIKO Software Mar 2 '15 at 23:37
7
\$\begingroup\$

This is the way I would do it:

function getRangeFromDays($days) {
  // collect dates into catagories
  foreach ($days as $date => $catagory) {
    $temp[$catagory][] = $date;
  }
  // process all dates in each catagory
  foreach ($temp as $catagory => $dates) {
    sort($dates);
    $startDate  = $dates[0];
    $finishDate = array_pop($dates);
    // walk through the dates, breaking at gaps
    foreach ($dates as $key => $date)
    if (($key > 0) && (strtotime($date)-strtotime($dates[$key-1]) > 99999)) {
      $result[] = array($startDate,$dates[$key-1],$catagory);
      $startDate = $date;
    }
    // force the end
    $result[] = array($startDate,$finishDate,$catagory);
  }
  return $result;
}

echo '<pre>';
print_r(getRangeFromDays($days));
echo '</pre>';

I use an intermediate array which makes it easier to get the results because it's already segmented by catagory. All that's then left to do is the date ranges. I turn dates into seconds to see if the gap is bigger than one day (> 99999 seconds).

\$\endgroup\$
  • \$\begingroup\$ Interesting approach. Thank you (I'll wait other answers before accept yours :-)) \$\endgroup\$ – Luca Rainone Mar 3 '15 at 12:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.