diff --git a/static/src/js/schedule.js b/static/src/js/schedule.js
new file mode 100644
index 0000000000000000000000000000000000000000..7eddd54bf6b37181a1c851f6c29d372b4db98150
--- /dev/null
+++ b/static/src/js/schedule.js
@@ -0,0 +1,129 @@
+$(function() {
+ /* Schedule display localisation */
+ var showCurrentTab = function() {
+ var fragment = window.location.hash.toLowerCase().substring(1);
+
+ var dayTabs = $('#schedule-tabs .schedule-day');
+ if (dayTabs.length === 0) return;
+
+ if (fragment) {
+ var fragmentId = "#schedule_day_" + fragment + "-tab";
+ $(fragmentId).tab('show');
+ } else {
+ // Show tab based on current time.
+ var now = luxon.DateTime.local();
+ for (var i = 0; i < dayTabs.length; ++i) {
+ var dayTab = $(dayTabs[i]);
+ var tabDate = dayTab.data('date');
+
+ var scheduleDate = luxon.DateTime.fromISO(tabDate, { zone: CONF_TZ });
+ var startOfDay = scheduleDate.startOf('day');
+ var endOfDay = scheduleDate.endOf('day');
+ if (now >= startOfDay && now < endOfDay) {
+ tabShown = true;
+ dayTab.tab('show');
+ break;
+ }
+ }
+ }
+ }
+
+ var updateScheduleGrid = function() {
+ var rowHeaders = $('.calendar-row th.time');
+ for (var i = 0; i < rowHeaders.length; ++i) {
+ var rowHeader = $(rowHeaders[i]);
+ var rowTime = rowHeader.data('time');
+ var scheduleDate = luxon.DateTime.fromISO(rowTime, { zone: CONF_TZ });
+ var localDate = scheduleDate.toLocal();
+
+ // If the schedule date is already in the user's TZ, skip it.
+ if (scheduleDate.offset === localDate.offset) break;
+
+ var confFormatted = scheduleDate.toLocaleString({
+ weekday: scheduleDate.weekday === localDate.weekday ? undefined : 'short',
+ hour: 'numeric',
+ minute: 'numeric',
+ timeZoneName: 'short',
+ });
+ var localFormatted = localDate.toLocaleString({
+ weekday: scheduleDate.weekday === localDate.weekday ? undefined : 'short',
+ hour: 'numeric',
+ minute: 'numeric',
+ timeZoneName: 'short',
+ });
+ var timeText = rowHeader.find('p').text();
+ rowHeader.find('p').html(confFormatted + '
(' + localFormatted + ')');
+ }
+ }
+
+ var updatePresentationTimes = function() {
+ var presentationTimes = $('span.presentation-time');
+ for (var i = 0; i < presentationTimes.length; ++i) {
+ var presentationTime = $(presentationTimes[i]);
+ var startTime = presentationTime.data('starttime');
+ var endTime = presentationTime.data('endtime');
+ var confStartTime = luxon.DateTime.fromISO(startTime, { zone: CONF_TZ });
+ var confEndTime = luxon.DateTime.fromISO(endTime, { zone: CONF_TZ });
+
+ var localStartTime = confStartTime.toLocal();
+ var localEndTime = confEndTime.toLocal();
+
+ // If the conf date is already in the user's TZ, skip it.
+ if (confStartTime.offset === localStartTime.offset) break;
+
+ var confStartTimeFormatted = confStartTime.toLocaleString({
+ weekday: 'short',
+ month: 'short',
+ day: '2-digit',
+ hour: 'numeric',
+ minute: 'numeric',
+ });
+ var confEndTimeFormatted = confEndTime.toLocaleString({
+ hour: 'numeric',
+ minute: 'numeric',
+ timeZoneName: 'short',
+ });
+ var localStartTimeFormatted = localStartTime.toLocaleString({
+ weekday: confStartTime.weekday === localStartTime.weekday ? undefined : 'short',
+ month: confStartTime.weekday === localStartTime.weekday ? undefined : 'short',
+ day: confStartTime.weekday === localStartTime.weekday ? undefined : '2-digit',
+ hour: 'numeric',
+ minute: 'numeric',
+ });
+ var localEndTimeFormatted = localEndTime.toLocaleString({
+ weekday: localStartTime.weekday === localEndTime.weekday ? undefined : 'short',
+ month: localStartTime.weekday === localEndTime.weekday ? undefined : 'short',
+ day: localStartTime.weekday === localEndTime.weekday ? undefined : '2-digit',
+ hour: 'numeric',
+ minute: 'numeric',
+ timeZoneName: 'short',
+ });
+
+ presentationTime.html(confStartTimeFormatted + ' - ' + confEndTimeFormatted + ' (' + localStartTimeFormatted + ' - ' + localEndTimeFormatted + ')');
+ }
+ }
+
+ /* Schedule Editing */
+ $("a.edit-slot").click(function(e) {
+ $("#slotEditModal").load($(this).data("action"), function() {
+ $("#slotEditModal").modal("show");
+ });
+ e.preventDefault();
+ });
+
+ $("form#schedule-builder :submit").click(function(e) {
+ var name = this.name;
+ if(name == 'delete') {
+ if (!confirm("Are you sure you want to delete the schedule?"))
+ {
+ e.preventDefault();
+ return;
+ }
+ }
+ });
+
+ /* Update schedule display */
+ showCurrentTab();
+ updateScheduleGrid();
+ updatePresentationTimes();
+});