Fixed program schedule generation algorithm
This commit is contained in:
@@ -18,7 +18,7 @@ class ProgramController extends Controller
|
||||
((`schedule`.`enddate` IS NULL) OR (`schedule`.`enddate` >= :startdate)) AND
|
||||
`schedule`.`active` = 1 AND
|
||||
`schedule`.`final` = 1
|
||||
ORDER BY `schedule`.`priority` DESC, MOD(`startday` + 6, 7) + 1, `schedule`.`starttime` ASC
|
||||
ORDER BY `schedule`.`priority` DESC, `schedule`.`startday` ASC, `schedule`.`starttime` ASC
|
||||
QUERY;
|
||||
|
||||
/**
|
||||
@@ -30,6 +30,153 @@ QUERY;
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function addToSchedule(&$schedule, $start, $end, $program) {
|
||||
if($start == $end) {
|
||||
return;
|
||||
}
|
||||
|
||||
$last = count($schedule) ? $schedule[count($schedule) - 1] : null;
|
||||
if($last && ($last['program'] == $program) && ($last['end'] == $start))
|
||||
{
|
||||
// Merge with the previous entry
|
||||
$last['end'] = $end;
|
||||
}
|
||||
else
|
||||
{
|
||||
$schedule[] = ['start' => clone $start, 'end' => clone $end, 'program' => new \Model\Program($program)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function createSchedule(\DateTimeImmutable $start, \DateTimeImmutable $end, $DEBUG = false) {
|
||||
$startDay = $start->format('N');
|
||||
$endDay = $end->format('N');
|
||||
$schedule = [];
|
||||
|
||||
$START = 1; $END = -1;
|
||||
$WEEK = new \DateInterval('P7D');
|
||||
$WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" /* == 0 and 7 */];
|
||||
$scheduleItems = app('db')->select(self::$SCHEDULE_SQL,
|
||||
['enddate' => $end->format('Y-m-d'), 'startdate' => $start->format('Y-m-d')]);
|
||||
$scheduleChanges = new \SplPriorityQueue();
|
||||
$active = [];
|
||||
foreach($scheduleItems as $item) {
|
||||
// Find the start time and end time of the last occurrence on or before the start time
|
||||
$itemStart = new \DateTime("last " . $WEEKDAYS[$item->startday] . " " . $start->format('Y-m-d') . " " . $item->starttime);
|
||||
$itemEnd = new \DateTime("last " . $WEEKDAYS[$item->endday] . " " . $start->format('Y-m-d') . " " . $item->endtime);
|
||||
if($itemEnd <= $itemStart) {
|
||||
$itemEnd->add($WEEK);
|
||||
}
|
||||
|
||||
// If the first occurrence is completely before the period of interest, look at the next week
|
||||
while($itemEnd < $start) {
|
||||
$itemStart->add($WEEK);
|
||||
$itemEnd->add($WEEK);
|
||||
}
|
||||
|
||||
// Create a list in which all start and end times are separate entries
|
||||
$scheduleChanges->insert( [$START, $itemStart, $item], [-$itemStart->getTimestamp(), -$START] );
|
||||
$scheduleChanges->insert( [$END, $itemEnd, $item], [-$itemEnd->getTimestamp(), -$END] );
|
||||
|
||||
// Also keep track of all priorities seen
|
||||
$active[$item->priority] = null;
|
||||
}
|
||||
|
||||
if($DEBUG) {
|
||||
print "<h2>Schedule changes - initial state</h2><ul>";
|
||||
foreach(clone $scheduleChanges as $c) {
|
||||
print "<li>{$c[1]->format('d-m-Y H:i')}: " . ($c[0] == $START ? 'Start' : 'Einde') . " {$c[2]->name} {$c[2]->suffix}</li>";
|
||||
}
|
||||
print "</ul><hr/>";
|
||||
}
|
||||
|
||||
// Go through all programs and keep going until we have one that starts after the end date.
|
||||
$activeProgram = null;
|
||||
$activeProgramStart = null;
|
||||
while(true) {
|
||||
$item = $scheduleChanges->extract();
|
||||
|
||||
if($DEBUG) print "<b>{$item[1]->format('d-m-Y H:i')}: {$item[2]->name} " . ($item[0] == $START ? "begint" : "eindigt") . "</b><br/>";
|
||||
|
||||
// If we passed the end time, we are (almost) done
|
||||
if($item[1] > $end) {
|
||||
if($DEBUG) { print "Reached end at {$item[1]->format('d-m-Y H:i')} ({$item[2]->name}).<br/>"; }
|
||||
break;
|
||||
}
|
||||
|
||||
$priority = $item[2]->priority;
|
||||
if($item[0] == $START) {
|
||||
if($active[$priority] != null) {
|
||||
print( "<b>Error:</b>" . $item[2]->name . ' starts at ' . $item[1]->format('d-m-Y H:i') . ' but ' . $active[$priority]->name . ' is still active then.' );
|
||||
print "<pre>"; var_dump($active); print "</pre>";
|
||||
}
|
||||
|
||||
// When a program starts, it becomes the active program in its layer
|
||||
$active[$priority] = $item[2];
|
||||
|
||||
// In addition, if it has higher priority than the currently active program, it replaces that.
|
||||
if($activeProgram == null || $priority < $activeProgram->priority) {
|
||||
if(($activeProgram != null) && ($item[1] > $start)) {
|
||||
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} (reason: {$item[2]->name} starts).<br/>";
|
||||
$this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram);
|
||||
}
|
||||
|
||||
$activeProgramStart = ($item[1] < $start) ? $start : clone $item[1];
|
||||
$activeProgram = $item[2];
|
||||
}
|
||||
|
||||
} else if($item[0] == $END) {
|
||||
if($active[$priority] == null) {
|
||||
print( "<b>Error:</b>" . $item[2]->name . ' ends at ' . $item[1]->format('d-m-Y H:i') . ' but no program is active at that priority and timestamp.');
|
||||
print "<pre>"; var_dump($active); print "</pre>";
|
||||
}
|
||||
|
||||
// When the program ends, its layer becomes inactive.
|
||||
$active[$priority] = null;
|
||||
|
||||
// In addition, when the ending program was the active program, we need to schedule it and find the new currently active program
|
||||
if($activeProgram == $item[2]) {
|
||||
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} (reason: {$item[2]->name} ends).<br/>";
|
||||
$this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram);
|
||||
|
||||
$activeProgram = null;
|
||||
foreach($active as $newPriority => $newActive) {
|
||||
if(($newActive != null) && ($activeProgram == null || $newPriority < $activeProgram->priority)) {
|
||||
$activeProgram = $newActive;
|
||||
}
|
||||
}
|
||||
|
||||
if($activeProgram != null) {
|
||||
$activeProgramStart = clone $item[1];
|
||||
}
|
||||
|
||||
if($DEBUG) print " New active program is " . ($activeProgram ? $activeProgram->name : "---") . "<br/>";
|
||||
}
|
||||
} else {
|
||||
return response()->abort(500, "Invalid item type: expected START ($START) or END ($END), but got {$item[0]}.");
|
||||
}
|
||||
|
||||
// The item will recur in a week
|
||||
$item[1]->add($WEEK);
|
||||
$scheduleChanges->insert($item, [-$item[1]->getTimestamp(), -$item[0]]);
|
||||
}
|
||||
|
||||
if(($activeProgram != null) && ($activeProgramStart != $end)) {
|
||||
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$end->format('d-m-Y H:i')}: {$activeProgram->name} (reason: schedule finished).<br/>";
|
||||
$this->addToSchedule($schedule, $activeProgramStart, $end, $activeProgram);
|
||||
}
|
||||
|
||||
return ['startdate' => $start, 'enddate' => $end, 'schedule' => $schedule];
|
||||
}
|
||||
|
||||
public function testSchedule($from, $to)
|
||||
{
|
||||
$start = new \DateTimeImmutable(urldecode($from));
|
||||
$end = new \DateTimeImmutable(urldecode($to));
|
||||
$this->createSchedule($start, $end, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Details over een specifiek programma, inclusief schema en podcasts
|
||||
@@ -51,88 +198,14 @@ QUERY;
|
||||
AND `user_emailaddresses`.`preference` = 0
|
||||
AND `programs_users`.`active` = 1
|
||||
ORDER BY `name`", ['program' => $program->id]);
|
||||
|
||||
foreach($hosts as $host) {
|
||||
$program->hosts[] = new \Model\ProgramHost($host);
|
||||
}
|
||||
|
||||
$schedule = app('db')->select(<<<QUERY
|
||||
SELECT `schedule`.`startdate`, `schedule`.`startday`, `schedule`.`starttime`, `schedule`.`enddate`, `schedule`.`endday`, `schedule`.`endtime`, `schedule`.`shortnamesuffix` AS `suffix`, `schedule`.`priority`
|
||||
FROM `programs_schedule` AS `schedule`
|
||||
WHERE (schedule.`program` = :program) AND (`schedule`.`active` = 1) AND (`schedule`.`final` = 1)
|
||||
ORDER BY `schedule`.`priority` DESC, MOD(`startday` + 6, 7) + 1, `schedule`.`starttime` ASC
|
||||
QUERY
|
||||
, ['program' => $program->id] );
|
||||
$recentSchedule = $this->recent($schedule);
|
||||
$program->schedule = $recentSchedule;
|
||||
|
||||
return response()->json($program);
|
||||
}
|
||||
|
||||
private function recent($schedule) {
|
||||
$now = new \DateTimeImmutable();
|
||||
$ONE_DAY = \DateInterval::createFromDateString('1 day');
|
||||
$ONE_WEEK = \DateInterval::createFromDateString('1 week');
|
||||
$recent = [];
|
||||
$next = [];
|
||||
|
||||
// Vind voor elke regel in de uitzendmomenten de eerstvolgende en twee meest recente.
|
||||
$isCurrent = false;
|
||||
foreach($schedule as $broadcast) {
|
||||
$broadcast->startdate = ($broadcast->startdate == null) ? null : new \DateTimeImmutable($broadcast->startdate);
|
||||
$broadcast->enddate = ($broadcast->enddate == null) ? null : new \DateTimeImmutable($broadcast->enddate);
|
||||
$startTijd = new \DateTimeImmutable($broadcast->starttime);
|
||||
$endDate = ($broadcast->enddate == null) ? $now : $broadcast->enddate;
|
||||
$endDate = $endDate->setTime($startTijd->format('H'), $startTijd->format('i'), $startTijd->format('s'));
|
||||
$endDateWeekday = $endDate->format('N');
|
||||
|
||||
// Zoek de laatste uitzending voor de einddatum (of voor het huidige tijdstip), op de juiste weekdag
|
||||
while($endDateWeekday != $broadcast->startday) {
|
||||
$endDate = $endDate->sub($ONE_DAY);
|
||||
if(--$endDateWeekday < 0) { $endDateWeekday += 7; }
|
||||
}
|
||||
|
||||
$eindTijd = new \DateTimeImmutable($broadcast->endtime);
|
||||
$endOfBroadcast = $endDate->setTime($eindTijd->format('H'), $eindTijd->format('i'), $eindTijd->format('s'));
|
||||
if($endOfBroadcast < $endDate) { $endOfBroadcast = $endOfBroadcast->add($ONE_DAY); }
|
||||
|
||||
if($isInProgress = (($endDate <= $now) && ($endOfBroadcast > $now))) {
|
||||
$isCurrent = true;
|
||||
}
|
||||
|
||||
// Als de uitzending niet al afgelopen is, telt hij als volgende.
|
||||
// Dit gebeurt als de uitzending later vandaag is of nu loopt.
|
||||
if($endOfBroadcast >= $now) {
|
||||
$endDate = $endDate->sub($ONE_WEEK);
|
||||
$endOfBroadcast = $endOfBroadcast->sub($ONE_WEEK);
|
||||
}
|
||||
|
||||
if((($broadcast->startdate == null) || ($broadcast->startdate <= $endDate))
|
||||
&& (($broadcast->enddate == null) || ($broadcast->enddate >= $endDate->setTime(0,0,0)))) {
|
||||
$recent[] = ['time' => $endDate, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)];
|
||||
}
|
||||
|
||||
|
||||
$previous = $endDate->sub($ONE_WEEK);
|
||||
if((($broadcast->startdate == null) || ($broadcast->startdate <= $previous))
|
||||
&& (($broadcast->enddate == null) || ($broadcast->enddate >= $previous->setTime(0,0,0)))) {
|
||||
$recent[] = ['time' => $previous, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)];
|
||||
}
|
||||
|
||||
$following = $endDate->add($ONE_WEEK);
|
||||
if(($broadcast->enddate == null) || ($broadcast->enddate->setTime(23,59,59) > $following)) {
|
||||
if($following < $now) {
|
||||
$isCurrent = true;
|
||||
}
|
||||
|
||||
$next[] = ['time' => $following, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)];
|
||||
}
|
||||
}
|
||||
|
||||
sort($next);
|
||||
rsort($recent);
|
||||
return ['recent' => array_reverse(array_splice($recent, 0, 2)), 'next' => count($next) ? $next[0] : null, 'iscurrent' => $isCurrent];
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmas nu en straks (24 uur vooruit)
|
||||
*/
|
||||
@@ -153,109 +226,4 @@ QUERY
|
||||
|
||||
return response()->json($this->createSchedule($start, $einde));
|
||||
}
|
||||
|
||||
private function createSchedule($start, $einde) {
|
||||
if(!defined('PROGRAMMA_START')) {
|
||||
define('PROGRAMMA_START', 1);
|
||||
define('PROGRAMMA_EINDE', -1);
|
||||
|
||||
define('PW_TIJDSTIP', 0);
|
||||
define('PW_TYPE', 1);
|
||||
define('PW_PROGRAMMA', 2);
|
||||
}
|
||||
|
||||
$oneWeek = \DateInterval::createFromDateString('1 week');
|
||||
|
||||
$programmas = app('db')->select(self::$SCHEDULE_SQL,
|
||||
['enddate' => $einde->format('Y-m-d'), 'startdate' => $start->format('Y-m-d')]);
|
||||
$programmaInfo = [];
|
||||
|
||||
// Maak een lijstje van alle start en eindtijden en welk programma dan begint/eindigt
|
||||
$startEinde = [];
|
||||
$startMaandag = $start->modify('Monday this week');
|
||||
foreach($programmas as $programma) {
|
||||
if($programma->suffix) $programma->name .= ' ' . $programma->suffix;
|
||||
$programmaInfo[$programma->scheduleid] = new \Model\Program($programma);
|
||||
|
||||
$programmaStart = $startMaandag->add(\DateInterval::createFromDateString(($programma->startday - 1) % 7 . ' days'));
|
||||
$programmaEinde = $startMaandag->add(\DateInterval::createFromDateString(($programma->endday - 1) % 7 . ' days'));
|
||||
|
||||
$startTijd = new \DateTime($programma->starttime);
|
||||
$eindTijd = new \DateTime($programma->endtime);
|
||||
|
||||
$programmaStart = $programmaStart->setTime($startTijd->format('H'), $startTijd->format('i'), $startTijd->format('s'));
|
||||
$programmaEinde = $programmaEinde->setTime($eindTijd->format('H'), $eindTijd->format('i'), $eindTijd->format('s'));
|
||||
|
||||
if($programmaEinde <= $programmaStart) {
|
||||
$programmaEinde = $programmaEinde->add($oneWeek);
|
||||
}
|
||||
|
||||
if($programmaEinde < $start) {
|
||||
$programmaStart = $programmaStart->add($oneWeek);
|
||||
$programmaEinde = $programmaEinde->add($oneWeek);
|
||||
}
|
||||
|
||||
while($programmaStart < $einde) {
|
||||
if((($programma->startdate == null) || ($programmaStart >= new \DateTime($programma->startdate)))
|
||||
&& (($programma->enddate == null) || ($programmaStart <= (new \DateTimeImmutable($programma->enddate))->setTime(23,59,59)))
|
||||
&& (($programmaEinde >= $start) && ($programmaStart <= $einde)))
|
||||
{
|
||||
$startEinde[] = [$programmaStart, PROGRAMMA_START, $programma->scheduleid];
|
||||
$startEinde[] = [$programmaEinde, PROGRAMMA_EINDE, $programma->scheduleid];
|
||||
}
|
||||
|
||||
$programmaStart = $programmaStart->add($oneWeek);
|
||||
$programmaEinde = $programmaEinde->add($oneWeek);
|
||||
}
|
||||
}
|
||||
|
||||
array_multisort($startEinde);
|
||||
|
||||
$actieveProgrammas = [null, null, null, null];
|
||||
$schema = [];
|
||||
$fouten = [];
|
||||
$index = -1;
|
||||
foreach($startEinde as $programmaWissel) {
|
||||
$scheduleId = $programmaWissel[PW_PROGRAMMA];
|
||||
$programma = $programmaInfo[$scheduleId];
|
||||
$tijdstip = $programmaWissel[PW_TIJDSTIP];
|
||||
if($programmaWissel[PW_TIJDSTIP] < $start) { $tijdstip = $start; }
|
||||
if($programmaWissel[PW_TIJDSTIP] > $einde) { $tijdstip = $einde; }
|
||||
if($tijdstip == $einde) { break; }
|
||||
|
||||
if($programmaWissel[PW_TYPE] == PROGRAMMA_START) {
|
||||
if($actieveProgrammas[$programma->priority] != null) {
|
||||
$fouten[] = [$tijdstip, "Begin van {$programma->name} maar {$actieveProgrammas[$programma->priority]->name} is nog actief."];
|
||||
}
|
||||
|
||||
$actieveProgrammas[$programma->priority] = $scheduleId;
|
||||
for($prio = $programma->priority; $prio >= 0; --$prio) {
|
||||
if($actieveProgrammas[$prio] != null) {
|
||||
if(($index == -1) || ($schema[$index]['starttime'] != $tijdstip)) { $index++; }
|
||||
$schema[$index] = ['starttime' => $tijdstip, 'endtime' => null, 'program' => $programmaInfo[$actieveProgrammas[$prio]]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else /*if($programmaWissel[PW_TYPE] == PROGRAMMA_EINDE)*/ {
|
||||
$actieveProgrammas[$programma->priority] = null;
|
||||
if(($index == -1) || ($schema[$index]['program'] != null)) { $index++; }
|
||||
$schema[$index] = ['starttime' => $tijdstip, 'program' => null];
|
||||
for($prio = $programma->priority; $prio < count($actieveProgrammas); ++$prio) {
|
||||
if($actieveProgrammas[$prio] != null) {
|
||||
$schema[$index] = ['starttime' => $tijdstip, 'endtime' => null, 'program' => $programmaInfo[$actieveProgrammas[$prio]]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for($i = 1; $i <= count($schema); $i++) {
|
||||
$eindTijd = ($i == count($schema)) ? $einde : $schema[$i]['starttime'];
|
||||
$schema[$i - 1]['endtime'] = $eindTijd;
|
||||
//$programma = $schema[$i-1]['program'];
|
||||
//print $schema[$i-1]['starttime']->format('D d-m-Y, H:i:s') . " - " . $eindTijd->format('D d-m-Y, H:i:s') . ": " . ($programma != null ? $programma->name : "NULL") . "\n";
|
||||
}
|
||||
|
||||
return ['startdate' => $start, 'enddate' => $einde, 'schedule' => $schema, 'errors' => $fouten];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ $app->get('podcast/stream/{id:\d+}/{title}', 'PodcastController@stream' );
|
||||
|
||||
$app->get('programma/schema/nustraks', 'ProgramController@comingup' );
|
||||
$app->get('programma/schema/week[/{shiftWeeks:-?\d+}]', 'ProgramController@schedule' );
|
||||
$app->get('/programma/details/{id:\d+}', 'ProgramController@details' );
|
||||
$app->get('programma/details/{id:\d+}', 'ProgramController@details' );
|
||||
|
||||
$app->get('programma/schema/test/{from}/{to}', 'ProgramController@testSchedule' );
|
||||
|
||||
// live/onair
|
||||
// live/stream
|
||||
|
||||
@@ -9,12 +9,16 @@ class Program extends Model {
|
||||
public $email;
|
||||
public $priority;
|
||||
|
||||
// TODO: Implementeren
|
||||
public $hosts;
|
||||
public $schedule;
|
||||
|
||||
public function __construct($data) {
|
||||
parent::__construct($data);
|
||||
|
||||
if(isset($data->suffix) && $data->suffix) {
|
||||
$this->name .= ' ' . $data->suffix;
|
||||
}
|
||||
|
||||
if($this->schedule && $this->schedule->next && $this->schedule->next->time) {
|
||||
parent::ConvertToDateTime($this->schedule->next->time);
|
||||
}
|
||||
@@ -31,4 +35,8 @@ class Program extends Model {
|
||||
|
||||
$this->url = "/{$this->id}/" . parent::url_slug($this->name);
|
||||
}
|
||||
|
||||
public function isSpecial() {
|
||||
return ($this->priority < 2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user