Некоторые помогают «синхронизировать» две петли в PHP

Я выполнил следующее решение от @Darragh, чтобы найти последовательные даты и преобразовать их в диапазоны дат:

Проверять последовательные даты в наборе и возвращать как диапазон

Теперь я пытаюсь напечатать дополнительную информацию для вывода этого цикла. Это ошибочный сценарий, который я создал, основываясь на ответе @ Darragh:

<?php $output = ''; $dates = array(); $query = 'SELECT name, company, date FROM project GROUP BY date ORDER BY date'; $sth = $dbh->prepare($query); $sth->execute(); if($sth->rowCount() > 0) { $output .= '<ul>'; while($row = $sth->fetch()) { array_push($dates,new DateTime($row['date'])); $name = $row['name']; $company = $row['company']; } $lastDate = null; $ranges = array(); $currentRange = array(); foreach ($dates as $date) { if (null === $lastDate) { $currentRange[] = $date; } else { $interval = $date->diff($lastDate); if ($interval->days === 1) { $currentRange[] = $date; } else { $ranges[] = $currentRange; $currentRange = array($date); } } $lastDate = $date; } $ranges[] = $currentRange; foreach ($ranges as $range) { $saverange = array(); foreach($range as $entry) { array_push($saverange,$entry->format('Ym-d')); } $startDate = array_shift($range); $str = sprintf('%s', $startDate->format('d/m/Y')); if (count($range)) { $endDate = array_pop($range); $str .= sprintf(' tot %s', $endDate->format('d/m/Y')); } $output .= '<li>'.$name.', '.$company.' - '.$str.'</li>'; } $output .= '</ul>'; // Show me what you got echo $output; } ?> 

Очевидно, цикл while, который петлиет над выводом DB, не синхронизирован с циклом foreach который выводит параметры переноса.

Вывод необработанного DB выглядит следующим образом:

 +------------------+-------------+------------+ | name | company | date | +------------------+-------------+------------+ | EBU | Belgacom sa | 2014-09-12 | | Mosquito Replica | Mosquito nv | 2014-09-17 | | Mosquito Replica | Mosquito nv | 2014-09-19 | | Mosquito Replica | Mosquito nv | 2014-09-20 | +------------------+-------------+------------+ 

Вывод моих сценариев выглядит следующим образом:

 <ul> <li>Mosquito Replica, Mosquito nv - 12/09/2014</li> <li>Mosquito Replica, Mosquito nv - 17/09/2014</li> <li>Mosquito Replica, Mosquito nv - 19/09/2014 tot 20/09/2014</li> </ul> 

Что мне нужно для вывода:

 <ul> <li>EBU, Belgacom sa - 12/09/2014</li> <li>Mosquito Replica, Mosquito nv - 17/09/2014</li> <li>Mosquito Replica, Mosquito nv - 19/09/2014 tot 20/09/2014</li> </ul> 

Это может быть очевидно для вас, ребята, но я не могу, чтобы жизнь меня поняла. Кто покажет мне дорогу? Заранее спасибо!

Причина, почему ваш сценарий не печатает EBU, Belgacum sa лежит в этой части:

 while($row = $sth->fetch()) { array_push($dates,new DateTime($row['date'])); $name = $row['name']; $company = $row['company']; } 

Пока значения даты переносятся в массив, $name и $company перезаписываются каждой новой строкой.

Кроме того, я думаю, ваш сценарий нарушает эту проблему. Как насчет этого?

  $output = ''; $list = array(); $lastitem = $lastdate = null; $query = 'SELECT name, company, date FROM project GROUP BY date ORDER BY date, name, company'; $sth = $dbh->prepare($query); $sth->execute(); if($sth->rowCount() > 0) { $i = 0; while($row = $sth->fetch()) { $date = new DateTime($row['date']); $item = array( 'date' => array(), 'name' => $row['name'], 'company' => $row['company'], ); if ($item === $lastitem && $date->diff($lastdate)->days === 1) { $list[$i-1]['date']['end'] = $date; } else { $list[$i] = $item; $list[$i]['date']['start'] = $date; $lastitem = $item; $lastdate = $date; $i++; } } } if (count($list)) { $output .= '<ul>'; } foreach ($list AS $item) { $output .= '<li>' . $item['name'] . ', ' . $item['company'] . ' - ' . $item['date']['start']->format('d/m/Y'); $output .= isset($item['date']['end']) ? ' tot ' . $item['date']['end']->format('d/m/Y') : ''; $output .= '</li>'; } if (count($list)) { $output .= '</ul>'; } 

Код клиента должен выглядеть следующим образом:

 <?php $output = ''; $query = 'SELECT name, company, date FROM project GROUP BY date ORDER BY date'; $sth = $dbh->prepare($query); $sth->execute(); if ($sth->rowCount() > 0) { $rows = array(); $output .= '<ul>'; //object responsible for holidng all the ranges //and returning the reference to them based on date $dateRange = new TimeRange(); while ($row = $sth->fetch()) { //getting the period id for this row's time $row['range_id'] = $dateRange->getRangeId(new DateTime($row['date'])); //indexing by name, company and range id $rows[$row['name'] . $row['company'] . $row['range_id']] = $row; } //each row has it's range id, now we just loop through them and render //Time Period object is rendering itself foreach ($rows as $row) { $output .= '<li>' . $row['name'] . ', ' . $row['company'] . ' - ' . $dateRange->render($row['range_id']) . '</li>'; } $output .= '</ul>'; // Show me what you got echo $output; } 

И классы для запуска клиентского кода (одна возможная реализация):

 class TimeRange { /** * @var TimePeriod[] */ private $periods = array(); /** * @var TimePeriod */ private $currentPeriod; private function newPeriod(DateTime $time) { $this->currentPeriod = new TimePeriod($time); $this->periods[] = $this->currentPeriod; } /** * Returns the period id for the given date * @param DateTime $time * @return string */ public function getRangeId(DateTime $time) { if ($this->currentPeriod === null) { $this->newPeriod($time); } if (!$this->currentPeriod->setNewEndTime($time)) { $this->newPeriod($time); } //returning period id end($this->periods); return key($this->periods); } /** * Renders the time period based on provided id * @param string $periodId * @return string */ public function render($periodId) { return $this->periods[$periodId]->render(); } } class TimePeriod { /** * @var DateTime */ private $startTime; /** * @var DateTime */ private $endTime; public function __construct(DateTime $startTime) { $this->startTime = $startTime; } /** * Attepmts to set the end time for the period * if the given time is more than 1 day apart from the current end time * the period is ended * @param DateTime $time * @return boolean */ public function setNewEndTime(DateTime $time) { $compareTime = $this->endTime !== null ? $this->endTime : $this->startTime; $interval = $compareTime->diff($time); $dateSet = $interval->days <= 1; if ($dateSet) { $this->endTime = $time; } else { $this->endTime = $this->startTime; } return $dateSet; } /** * Renders the time period * if the end time is less than a day apart only start time is rendered * @return string */ public function render() { $render = $this->startTime->format('d/m/Y'); if ($this->startTime->diff($this->endTime, true)->days >= 1) { $render .= ' tot ' . $this->endTime->format('d/m/Y'); } return $render; } }