preserve schedule timezones and show current time + event timezone on the website (#117)

This commit is contained in:
Anton Schubert 2020-10-31 20:43:09 +01:00 committed by GitHub
parent 84b8eb8048
commit c31a16cb6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 125 deletions

View file

@ -16,19 +16,21 @@ body .schedule {
.now {
position: absolute;
left: 0;
width: 150px;
height: 100%;
background-color: @schedule-now-bg;
font-size: 14px;
pointer-events: none;
height: 100%;
display: flex;
left: 0;
z-index: 5;
span {
display: block;
position: absolute;
right: -28px;
.overlay {
width: 150px;
height: 100%;
background-color: @schedule-now-bg;
}
.label {
font-size: 14px;
padding-left: 5px;
color: @schedule-now;
}
}
@ -50,7 +52,7 @@ body .schedule {
.inner {
display: block;
padding: 10px;
padding: 15px;
height: 100%;
}

View file

@ -55,6 +55,11 @@ nav {
}
}
.navbar-time {
line-height: 27px;
padding: 10px 10px;
}
.button-wrapper > .btn {
width: 40px;
}

View file

@ -127,6 +127,7 @@ $(function() {
$(function() {
var
$schedule = $('body .schedule'),
$time = $('.navbar-time'),
$now = $schedule.find('.now'),
scrollLock = false,
rewindTimeout,
@ -154,6 +155,23 @@ $(function() {
}
});
function formatLocalTime(timestamp, offset) {
const d = new Date(timestamp * 1000);
// js timezone offset is negative
const diff = -d.getTimezoneOffset() - offset;
d.setUTCMinutes(d.getUTCMinutes() - diff);
return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
}
function formatOffset(offset) {
const sign = offset < 0 ? "-" : "+";
const hours = String(Math.floor(Math.abs(offset)/60)).padStart(2, '0');
const minutes = String(Math.abs(offset)%60).padStart(2, '0');
return sign + hours + ":" + minutes;
}
// schedule now-marker & scrolling
function updateProgramView(initial) {
var
@ -167,12 +185,22 @@ $(function() {
.find('.room')
.first()
.find('.block')
.filter(function(i, el) {
.filter(function(i, el) {
return $(this).data('start') < now;
}).last();
if($block.length == 0)
return $now.css('width', 0);
// if we are yet to start find first block as reference
if (!$block.length)
$block = $schedule
.find('.room').first()
.find('.block').first();
// still no luck
if(!$block.length) {
$now.find('.overlay').css('width', 0);
$now.find('.label').text('now');
return;
}
var
// start & end-timestamp
@ -195,15 +223,19 @@ $(function() {
px_in_display = px - scrollx;
//console.log($block.get(0), new Date(start*1000), new Date(now*1000), new Date(end*1000), normalized, px);
$now.css('width', px);
const eventOffset = parseFloat($block.data('offset')) || 0;
const eventTime = formatLocalTime(now, eventOffset);
$now.find('.overlay').css('width', px);
$now.find('.label').text("now (" + eventTime + ")");
$time.text(eventTime + " (" + formatOffset(eventOffset) + ")");
// scrolling is locked by manual interaction
if(scrollLock)
if (scrollLock)
return;
if(
// now marker is > 2/3 of the schedule-display-width
px_in_display > (displayw * 2/3) ||
px_in_display > (displayw * 2/3) ||
// now marker is <1/7 of the schedule-display-width
px_in_display < (displayw/7)

View file

@ -89,31 +89,32 @@ class Schedule
// so to be on the safer side we calculate our own daystart/end here
foreach($schedule->day as $day)
{
$daystart = PHP_INT_MAX;
$dayend = 0;
$daystart = new DateTimeImmutable('+100 year');
$dayend = new DateTimeImmutable('-100 year');
foreach($day->room as $room)
{
$roomName = (string)$room['name'];
if($this->isRoomFiltered($roomName))
continue;
if(!in_array($roomName, $rooms))
$rooms[] = $roomName;
foreach($room->event as $event)
{
$start = strtotime((string)$event->date);
$duration = $this->strToDuration((string)$event->duration);
$end = $start + $duration;
$start = new DateTimeImmutable((string)$event->date);
$interval = $this->strToInterval((string)$event->duration);
$end = $start->add($interval);
$daystart = min($daystart, $start);
$dayend = max($dayend, $end);
$daystart = $start < $daystart ? $start : $daystart;
$dayend = $end > $dayend ? $end : $dayend;
}
}
$day['start'] = $daystart;
$day['end'] = $dayend;
// stringify again to store in simplexml
$day['start'] = $daystart->format('c');
$day['end'] = $dayend->format('c');
}
@ -123,23 +124,23 @@ class Schedule
$daysSorted[] = $day;
}
usort($daysSorted, function($a, $b) {
return (int)$a['start'] - (int)$b['start'];
usort($daysSorted, function($a, $b): int {
return strcmp($a['start'], $b['start']);
});
$dayidx = 0;
foreach($daysSorted as $day)
{
$dayidx++;
$daystart = (int)$day['start'];
$dayend = (int)$day['end'];
$daystart = new DateTimeImmutable($day['start']);
$dayend = new DateTimeImmutable($day['end']);
$roomidx = 0;
foreach($rooms as $roomName)
{
$roomidx++;
$laststart = false;
$lastend = false;
$laststart = NULL;
$lastend = NULL;
if($this->isRoomFiltered($roomName))
continue;
@ -147,24 +148,16 @@ class Schedule
$result = $day->xpath("room[@name='".$roomName."']");
if(!$result) {
// this room has no events on this day -> add long gap
$program[$roomName][] = array(
'special' => 'gap',
$gap = $this->makeEvent($daystart, $dayend);
$gap['special'] = 'gap';
$program[$roomName][] = $gap;
'fstart' => date('c', $daystart),
'fend' => date('c', $dayend),
'start' => $daystart,
'end' => $dayend,
'duration' => $dayend - $daystart,
);
$program[$roomName][] = array(
'special' => 'daychange',
'title' => 'Daychange from Day '.$dayidx.' to '.($dayidx+1),
'start' => $dayend,
'end' => (int)$schedule->day[$dayidx]['start'],
'duration' => 60*60,
);
$end = new DateTimeImmutable($schedule->day[$dayidx]['start']);
$daychange = $this->makeEvent($dayend, $end);
$daychange['special'] = 'daychange';
$daychange['title'] = 'Daychange from Day '.$dayidx.' to '.($dayidx+1);
$daychange['duration'] = 3600;
$program[$roomName][] = $daychange;
continue;
}
$room = $result[0];
@ -184,103 +177,74 @@ class Schedule
foreach($eventsSorted as $event)
{
$start = strtotime((string)$event->date);
$duration = $this->strToDuration((string)$event->duration);
$end = $start + $duration;
$start = new DateTimeImmutable((string)$event->date);
$interval = $this->strToInterval((string)$event->duration);
$end = $start->add($interval);
// skip duplicate events in fahrplan source
if ( $laststart == $start )
continue;
continue;
if($lastend && $lastend < $start)
{
// synthesize pause event
$pauseduration = $start - $lastend;
$program[$roomName][] = array(
'special' => 'pause',
'title' => round($pauseduration / 60).' minutes pause',
'fstart' => date('c', $lastend),
'fend' => date('c', $start),
'start' => $lastend,
'end' => $start,
'duration' => $pauseduration,
'room_known' => $this->isRoomMapped($roomName),
);
// pause between talks
$pause = $this->makeEvent($lastend, $start);
$pause['special'] = 'pause';
$pause['title'] = round($pause['duration'] / 60).' minutes pause';
$pause['room_known'] = $this->isRoomMapped($roomName);
$program[$roomName][] = $pause;
}
else if(!$lastend && $daystart < $start)
{
$program[$roomName][] = array(
'special' => 'gap',
'fstart' => date('c', $daystart),
'fend' => date('c', $start),
'start' => $daystart,
'end' => $start,
'duration' => $start - $daystart,
'room_known' => $this->isRoomMapped($roomName),
);
// gap before first talk
$gap = $this->makeEvent($daystart, $start);
$gap['special'] = 'gap';
$gap['room_known'] = $this->isRoomMapped($roomName);
$program[$roomName][] = $gap;
}
$personnames = array();
if(isset($event->persons)) foreach($event->persons->person as $person)
$personnames[] = (string)$person;
$program[$roomName][] = array(
'title' => (string)$event->title,
'speaker' => implode(', ', $personnames),
'fstart' => date('c', $start),
'fend' => date('c', $end),
'start' => $start,
'end' => $end,
'duration' => $duration,
'room_known' => $this->isRoomMapped($roomName),
'optout' => $this->isOptout($event),
);
// normal talk
$talk = $this->makeEvent($start, $end);
$talk['title'] = (string)$event->title;
$talk['speaker'] = implode(', ', $personnames);
$talk['room_known'] = $this->isRoomMapped($roomName);
$talk['optout'] = $this->isOptout($event);
$program[$roomName][] = $talk;
$laststart = $start;
$lastend = $end;
}
// synthesize daychange event
if(!$lastend) $lastend = $daystart;
if($lastend < $dayend)
{
$program[$roomName][] = array(
'special' => 'gap',
'fstart' => date('c', $lastend),
'fend' => date('c', $dayend),
'start' => $lastend,
'end' => $dayend,
'duration' => $dayend - $lastend,
);
// gap after last talk
$gap = $this->makeEvent($lastend, $dayend);
$gap['special'] = 'gap';
$program[$roomName][] = $gap;
}
if($dayidx < count($schedule->day))
{
$program[$roomName][] = array(
'special' => 'daychange',
'title' => 'Daychange from Day '.$dayidx.' to '.($dayidx+1),
'start' => $dayend,
'end' => (int)$schedule->day[$dayidx]['start'],
'duration' => 60*60,
);
// daychange
$end = new DateTimeImmutable($schedule->day[$dayidx]['start']);
$daychange = $this->makeEvent($dayend, $end);
$daychange['special'] = 'daychange';
$daychange['title'] = 'Daychange from Day '.$dayidx.' to '.($dayidx+1);
$daychange['duration'] = 3600;
$program[$roomName][] = $daychange;
}
}
}
$mapping = $this->getScheduleToRoomSlugMapping();
if($this->getConference()->has('SCHEDULE.ROOMFILTER'))
{
// sort by roomfilter
// determine roomfilter
$roomfilter = $this->getConference()->get('SCHEDULE.ROOMFILTER');
// map roomfilter-rooms to room-slugs
@ -291,7 +255,7 @@ class Schedule
return $e;
}, $roomfilter);
// sort according to roomtilter ordering
// sort according to roomfilter ordering
uksort($program, function($a, $b) use ($roomfilter) {
return array_search($a, $roomfilter) - array_search($b, $roomfilter);
});
@ -300,6 +264,32 @@ class Schedule
return $program;
}
private function makeEvent(DateTimeImmutable $from, DateTimeImmutable $to): array {
return array(
'fstart' => $from->format('c'),
'fend' => $to->format('c'),
'tstart' => $from->format('H:i'),
'tend' => $to->format('H:i'),
'start' => $from->getTimestamp(),
'end' => $to->getTimestamp(),
'offset' => $from->getOffset(),
'duration' => $to->getTimestamp() - $from->getTimestamp(),
);
}
private function intervalToDuration(DateInterval $interval): int {
$one = new DateTimeImmutable();
$two = $one->add($interval);
return $two->getTimestamp() - $one->getTimestamp();
}
private function strToInterval(string $str): DateInterval
{
$parts = explode(':', $str);
return new DateInterval('PT'.$parts[0].'H'.$parts[1].'M');
}
public function getDurationSum()
{
@ -312,23 +302,18 @@ class Schedule
}
private function strToDuration($str)
{
$parts = explode(':', $str);
return ((int)$parts[0] * 60 + (int)$parts[1]) * 60;
}
public function getScheduleUrl()
{
return $this->getConference()->get('SCHEDULE.URL');
}
public function getScheduleCache()
{
return sprintf('/tmp/schedule-cache-%s.xml', $this->getConference()->getSlug());
}
public function getScheduleToRoomSlugMapping()
{
$mapping = array();

View file

@ -22,6 +22,12 @@
<span class="fa fa-info"></span>
</a>
</div>
<? if(isset($room) && $room->hasSchedule()): ?>
<div class="navbar-right navbar-time">
Current Time
</div>
<? endif ?>
</div>
</nav>

View file

@ -1,10 +1,14 @@
<div class="schedule scroll-container">
<div class="scroll-element">
<div class="now"><span>now</span></div>
<? $totalWidth = round($schedule->getDurationSum() / $schedule->getScale()) ?>
<div class="now" style="width: <?= h($totalWidth) ?>px">
<div class="overlay"></div>
<div class="label">now</div>
</div>
<? $rooms = $schedule->getSchedule() ?>
<? foreach($rooms as $roomname => $events): ?>
<? $scheduleRoom = $schedule->getMappedRoom($roomname) ?>
<div class="room <? if(isset($room) && $roomname == $room->getScheduleName()): ?>highlight<? endif ?>" style="width: <?=round($schedule->getDurationSum() / $schedule->getScale())?>px">
<div class="room <? if(isset($room) && $roomname == $room->getScheduleName()): ?>highlight<? endif ?>" style="width: <?= h($totalWidth) ?>px">
<? $fromstart = 0; ?>
<? foreach($events as $event): ?>
<div
@ -12,6 +16,7 @@
style="width: <?=h(round($event['duration'] / $schedule->getScale()))?>px; left: <?=h(round($fromstart / $schedule->getScale()))?>px"
data-start="<?=intval($event['start'])?>"
data-end="<?=intval($event['end'])?>"
data-offset="<?=intval($event['offset']/60)?>"
>
<? $fromstart += $event['duration'] ?>
<? if($scheduleRoom): ?>
@ -42,9 +47,9 @@
<? else: ?>
<? if($event['duration'] > 10*60): /* only display when event is longer as 10 minutes */ ?>
<h4><?=h(strftime('%H:%M', $event['start']))?>
<h4><?=h($event['tstart'])?>
&ndash;
<?=h(strftime('%H:%M', $event['end']))?>
<?=h($event['tend'])?>
&nbsp;in&nbsp;
<?=h($scheduleRoom ? $scheduleRoom->getDisplayShort() : $roomname) ?>
</h4>