Merged in [18970] from jennifer@painless-security.com:

Add timezone selector to upcoming meetings page. Separate general timezone handling from parts only relevant to main agenda page. Speed up agenda timezone javascript tests. Fixes #3184.
 - Legacy-Id: 18979
Note: SVN reference [18970] has been migrated to Git commit a5604992f2
This commit is contained in:
Robert Sparks 2021-05-07 19:11:25 +00:00
commit d119b22b29
7 changed files with 746 additions and 384 deletions

View file

@ -12,7 +12,8 @@ from unittest import skipIf
import django
from django.utils.text import slugify
from django.db.models import F
from pytz import timezone
import pytz
#from django.test.utils import override_settings
import debug # pyflakes:ignore
@ -36,7 +37,7 @@ from ietf import settings
if selenium_enabled():
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select, WebDriverWait
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import NoSuchElementException
@ -975,25 +976,22 @@ class AgendaTests(IetfSeleniumTestCase):
with self.assertRaises(NoSuchElementException):
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
def _wait_for_tz_change_from(self, old_tz):
"""Helper to wait for tz displays to change from their old value"""
match = 'text()!="%s"' % old_tz
WebDriverWait(self.driver, 2).until(
expected_conditions.presence_of_element_located((By.XPATH, '//*[@class="current-tz"][%s]' % match))
)
def test_agenda_time_zone_selection(self):
self.assertNotEqual(self.meeting.time_zone, 'UTC', 'Meeting time zone must not be UTC')
wait = WebDriverWait(self.driver, 2)
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
# wait for the select box to be updated - look for an arbitrary time zone to be in
# its options list to detect this
WebDriverWait(self.driver, 2).until(
expected_conditions.presence_of_element_located((By.XPATH, '//option[@value="America/Halifax"]'))
arbitrary_tz = 'America/Halifax'
arbitrary_tz_opt = WebDriverWait(self.driver, 2).until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz)
)
)
tz_select_input = Select(self.driver.find_element_by_id('timezone_select'))
tz_select_input = self.driver.find_element_by_id('timezone-select')
meeting_tz_link = self.driver.find_element_by_id('meeting-timezone')
local_tz_link = self.driver.find_element_by_id('local-timezone')
utc_tz_link = self.driver.find_element_by_id('utc-timezone')
@ -1009,73 +1007,110 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertNotEqual(self.meeting.time_zone, local_tz, 'Meeting time zone must not be local time zone')
self.assertNotEqual(local_tz, 'UTC', 'Local time zone must not be UTC')
meeting_tz_opt = tz_select_input.find_element_by_css_selector('option[value="%s"]' % self.meeting.time_zone)
local_tz_opt = tz_select_input.find_element_by_css_selector('option[value="%s"]' % local_tz)
utc_tz_opt = tz_select_input.find_element_by_css_selector('option[value="UTC"]')
# Should start off in meeting time zone
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), self.meeting.time_zone)
self.assertTrue(meeting_tz_opt.is_selected())
# don't yet know local_tz, so can't check that it's deselected here
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), self.meeting.time_zone)
# Click 'local' button
local_tz_link.click()
self._wait_for_tz_change_from(self.meeting.time_zone)
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), local_tz)
wait.until(expected_conditions.element_selection_state_to_be(meeting_tz_opt, False))
self.assertFalse(meeting_tz_opt.is_selected())
# just identified the local_tz_opt as being selected, so no check here, either
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), local_tz)
# click 'utc' button
utc_tz_link.click()
self._wait_for_tz_change_from(local_tz)
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), 'UTC')
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(meeting_tz_opt.is_selected())
self.assertFalse(local_tz_opt.is_selected()) # finally!
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), 'UTC')
# click back to meeting
meeting_tz_link.click()
self._wait_for_tz_change_from('UTC')
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), self.meeting.time_zone)
wait.until(expected_conditions.element_to_be_selected(meeting_tz_opt))
self.assertTrue(meeting_tz_opt.is_selected())
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), self.meeting.time_zone)
# and then back to UTC...
utc_tz_link.click()
self._wait_for_tz_change_from(self.meeting.time_zone)
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), 'UTC')
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(meeting_tz_opt.is_selected())
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), 'UTC')
# ... and test the switch from UTC to local
local_tz_link.click()
self._wait_for_tz_change_from('UTC')
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), local_tz)
wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertFalse(meeting_tz_opt.is_selected())
self.assertTrue(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), local_tz)
# Now select a different item from the select input
tz_select_input.select_by_value('America/Halifax')
self._wait_for_tz_change_from(self.meeting.time_zone)
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), 'America/Halifax')
arbitrary_tz_opt.click()
wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(meeting_tz_opt.is_selected())
self.assertFalse(local_tz_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
for disp in tz_displays:
self.assertEqual(disp.text.strip(), 'America/Halifax')
self.assertEqual(disp.text.strip(), arbitrary_tz)
def test_agenda_time_zone_selection_updates_weekview(self):
"""Changing the time zone should update the weekview to match"""
class in_iframe_href:
"""Condition class for use with WebDriverWait"""
def __init__(self, fragment, iframe):
self.fragment = fragment
self.iframe = iframe
def __call__(self, driver):
driver.switch_to.frame(self.iframe)
current_href= driver.execute_script(
'return document.location.href'
)
driver.switch_to.default_content()
return self.fragment in current_href
# enable a filter so the weekview iframe is visible
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars')
# wait for the select box to be updated - look for an arbitrary time zone to be in
# its options list to detect this
WebDriverWait(self.driver, 2).until(
expected_conditions.presence_of_element_located((By.XPATH, '//option[@value="America/Halifax"]'))
wait = WebDriverWait(self.driver, 2)
option = wait.until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, '#timezone-select > option[value="America/Halifax"]'))
)
tz_select_input = Select(self.driver.find_element_by_id('timezone_select'))
# Now select a different item from the select input
tz_select_input.select_by_value('America/Halifax')
self._wait_for_tz_change_from(self.meeting.time_zone)
self.assertEqual(tz_select_input.first_selected_option.get_attribute('value'), 'America/Halifax')
self.driver.switch_to.frame('weekview')
wv_url = self.driver.execute_script('return document.location.href')
self.assertIn('tz=america/halifax', wv_url)
option.click()
try:
wait.until(in_iframe_href('tz=america/halifax', 'weekview'))
except:
self.fail('iframe href not updated to contain selected time zone')
@ifSeleniumEnabled
class WeekviewTests(IetfSeleniumTestCase):
@ -1117,7 +1152,7 @@ class WeekviewTests(IetfSeleniumTestCase):
zones_to_test = ['utc', 'America/Halifax', 'Asia/Bangkok', 'Africa/Dakar', 'Europe/Dublin']
self.login()
for zone_name in zones_to_test:
zone = timezone(zone_name)
zone = pytz.timezone(zone_name)
self.driver.get(self.absreverse('ietf.meeting.views.week_view') + '?tz=' + zone_name)
for item in self.get_expected_items():
if item.session.name:
@ -1173,10 +1208,10 @@ class WeekviewTests(IetfSeleniumTestCase):
# Session during a single day in meeting local time but multi-day UTC
# Compute a time that overlaps midnight, UTC, but won't when shifted to a local time zone
start_time_utc = timezone('UTC').localize(
start_time_utc = pytz.timezone('UTC').localize(
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
)
start_time_local = start_time_utc.astimezone(timezone(self.meeting.time_zone))
start_time_local = start_time_utc.astimezone(pytz.timezone(self.meeting.time_zone))
daytime_session = SessionFactory(
meeting=self.meeting,
@ -1569,6 +1604,152 @@ class InterimTests(IetfSeleniumTestCase):
meetings.update(self.displayed_interims(groups=['mars']))
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings)
def test_upcoming_view_time_zone_selection(self):
wait = WebDriverWait(self.driver, 2)
def _assert_interim_tz_correct(sessions, tz):
zone = pytz.timezone(tz)
for session in sessions:
ts = session.official_timeslotassignment().timeslot
start = ts.utc_start_time().astimezone(zone).strftime('%Y-%m-%d %H:%M')
end = ts.utc_end_time().astimezone(zone).strftime('%H:%M')
meeting_link = self.driver.find_element_by_link_text(session.meeting.number)
time_td = meeting_link.find_element_by_xpath('../../td[@class="session-time"]')
self.assertIn('%s - %s' % (start, end), time_td.text)
def _assert_ietf_tz_correct(meetings, tz):
zone = pytz.timezone(tz)
for meeting in meetings:
meeting_zone = pytz.timezone(meeting.time_zone)
start_dt = meeting_zone.localize(datetime.datetime.combine(
meeting.date,
datetime.time.min
))
end_dt = meeting_zone.localize(datetime.datetime.combine(
start_dt + datetime.timedelta(days=meeting.days - 1),
datetime.time.max
))
start = start_dt.astimezone(zone).strftime('%Y-%m-%d')
end = end_dt.astimezone(zone).strftime('%Y-%m-%d')
meeting_link = self.driver.find_element_by_link_text("IETF " + meeting.number)
time_td = meeting_link.find_element_by_xpath('../../td[@class="meeting-time"]')
self.assertIn('%s - %s' % (start, end), time_td.text)
sessions = [m.session_set.first() for m in self.displayed_interims()]
self.assertGreater(len(sessions), 0)
ietf_meetings = self.all_ietf_meetings()
self.assertGreater(len(ietf_meetings), 0)
self.driver.get(self.absreverse('ietf.meeting.views.upcoming'))
tz_select_input = self.driver.find_element_by_id('timezone-select')
tz_select_bottom_input = self.driver.find_element_by_id('timezone-select-bottom')
local_tz_link = self.driver.find_element_by_id('local-timezone')
utc_tz_link = self.driver.find_element_by_id('utc-timezone')
local_tz_bottom_link = self.driver.find_element_by_id('local-timezone-bottom')
utc_tz_bottom_link = self.driver.find_element_by_id('utc-timezone-bottom')
# wait for the select box to be updated - look for an arbitrary time zone to be in
# its options list to detect this
arbitrary_tz = 'America/Halifax'
arbitrary_tz_opt = wait.until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz)
)
)
arbitrary_tz_bottom_opt = tz_select_bottom_input.find_element_by_css_selector(
'option[value="%s"]' % arbitrary_tz)
utc_tz_opt = tz_select_input.find_element_by_css_selector('option[value="UTC"]')
utc_tz_bottom_opt= tz_select_bottom_input.find_element_by_css_selector('option[value="UTC"]')
# Moment.js guesses local time zone based on the behavior of Selenium's web client. This seems
# to inherit Django's settings.TIME_ZONE but I don't know whether that's guaranteed to be consistent.
# To avoid test fragility, ask Moment what it considers local and expect that.
local_tz = self.driver.execute_script('return moment.tz.guess();')
local_tz_opt = tz_select_input.find_element_by_css_selector('option[value=%s]' % local_tz)
local_tz_bottom_opt = tz_select_bottom_input.find_element_by_css_selector('option[value="%s"]' % local_tz)
# Should start off in local time zone
self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, local_tz)
_assert_ietf_tz_correct(ietf_meetings, local_tz)
# click 'utc' button
utc_tz_link.click()
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_bottom_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
self.assertTrue(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, 'UTC')
_assert_ietf_tz_correct(ietf_meetings, 'UTC')
# click back to 'local'
local_tz_link.click()
wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_bottom_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
self.assertFalse(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, local_tz)
_assert_ietf_tz_correct(ietf_meetings, local_tz)
# Now select a different item from the select input
arbitrary_tz_opt.click()
wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
self.assertTrue(arbitrary_tz_bottom_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
self.assertFalse(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, arbitrary_tz)
_assert_ietf_tz_correct(ietf_meetings, arbitrary_tz)
# Now repeat those tests using the widgets at the bottom of the page
# click 'utc' button
utc_tz_bottom_link.click()
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_bottom_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
self.assertTrue(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, 'UTC')
_assert_ietf_tz_correct(ietf_meetings, 'UTC')
# click back to 'local'
local_tz_bottom_link.click()
wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_bottom_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
self.assertFalse(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, local_tz)
_assert_ietf_tz_correct(ietf_meetings, local_tz)
# Now select a different item from the select input
arbitrary_tz_bottom_opt.click()
wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
self.assertTrue(arbitrary_tz_bottom_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
self.assertFalse(utc_tz_bottom_opt.is_selected())
_assert_interim_tz_correct(sessions, arbitrary_tz)
_assert_ietf_tz_correct(ietf_meetings, arbitrary_tz)
# The following are useful debugging tools
# If you add this to a LiveServerTestCase and run just this test, you can browse

View file

@ -3363,7 +3363,7 @@ def upcoming(request):
# Get ietf meetings starting 7 days ago, and interim meetings starting today
ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
for m in ietf_meetings:
m.end = m.date+datetime.timedelta(days=m.days)
m.end = m.date + datetime.timedelta(days=m.days-1) # subtract 1 to avoid counting an extra day
interim_sessions = add_event_info_to_session_qs(
Session.objects.filter(
@ -3411,13 +3411,11 @@ def upcoming(request):
for o in entries:
if isinstance(o, Meeting):
o.start_timestamp = int(pytz.utc.localize(datetime.datetime.combine(o.date, datetime.datetime.min.time())).timestamp())
o.end_timestamp = int(pytz.utc.localize(datetime.datetime.combine(o.end, datetime.datetime.max.time())).timestamp())
o.start_timestamp = int(pytz.utc.localize(datetime.datetime.combine(o.date, datetime.time.min)).timestamp())
o.end_timestamp = int(pytz.utc.localize(datetime.datetime.combine(o.end, datetime.time.max)).timestamp())
else:
o.start_timestamp = int(o.official_timeslotassignment().
timeslot.utc_start_time().timestamp());
o.end_timestamp = int(o.official_timeslotassignment().
timeslot.utc_end_time().timestamp());
o.start_timestamp = int(o.official_timeslotassignment().timeslot.utc_start_time().timestamp())
o.end_timestamp = int(o.official_timeslotassignment().timeslot.utc_end_time().timestamp())
# add menu entries
menu_entries = get_interim_menu_entries(request)

View file

@ -232,6 +232,7 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
* button UI. Do not call if you only want to use the parameter parsing routines.
*/
function enable () {
// ready handler fires immediately if document is already "ready"
$(document).ready(function () {
register_handlers();
update_view();

View file

@ -0,0 +1,229 @@
// Copyright The IETF Trust 2021, All Rights Reserved
/*
Timezone support specific to the agenda page
To properly handle timezones other than local, needs a method to retrieve
the current timezone. Set this by passing a method taking no parameters and
returning the current timezone to the set_current_tz_cb() method.
This should be done before calling anything else in the file.
*/
var meeting_timezone;
var local_timezone = moment.tz.guess();
// get_current_tz_cb must be overwritten using set_current_tz_cb
var get_current_tz_cb = function () {
throw new Error('Tried to get current timezone before callback registered. Use set_current_tz_cb().')
};
// Initialize moments
function initialize_moments() {
var times=$('span.time')
$.each(times, function(i, item) {
item.start_ts = moment.unix(this.getAttribute("data-start-time")).utc();
item.end_ts = moment.unix(this.getAttribute("data-end-time")).utc();
if (this.hasAttribute("weekday")) {
item.format=2;
} else {
item.format=1;
}
if (this.hasAttribute("format")) {
item.format = +this.getAttribute("format");
}
});
var times=$('[data-slot-start-ts]')
$.each(times, function(i, item) {
item.slot_start_ts = moment.unix(this.getAttribute("data-slot-start-ts")).utc();
item.slot_end_ts = moment.unix(this.getAttribute("data-slot-end-ts")).utc();
});
}
function format_time(t, tz, fmt) {
var out;
var mtz = meeting_timezone;
if (mtz == "") {
mtz = "UTC";
}
switch (fmt) {
case 0:
out = t.tz(tz).format('dddd, ') + '<span class="hidden-xs">' +
t.tz(tz).format('MMMM Do YYYY, ') + '</span>' +
t.tz(tz).format('HH:mm') + '<span class="hidden-xs">' +
t.tz(tz).format(' Z z') + '</span>';
break;
case 1:
// Note, this code does not work if the meeting crosses the
// year boundary.
out = t.tz(tz).format("HH:mm");
if (+t.tz(tz).dayOfYear() < +t.tz(mtz).dayOfYear()) {
out = out + " (-1)";
} else if (+t.tz(tz).dayOfYear() > +t.tz(mtz).dayOfYear()) {
out = out + " (+1)";
}
break;
case 2:
out = t.tz(mtz).format("dddd, ").toUpperCase() +
t.tz(tz).format("HH:mm");
if (+t.tz(tz).dayOfYear() < +t.tz(mtz).dayOfYear()) {
out = out + " (-1)";
} else if (+t.tz(tz).dayOfYear() > +t.tz(mtz).dayOfYear()) {
out = out + " (+1)";
}
break;
case 3:
out = t.utc().format("YYYY-MM-DD");
break;
case 4:
out = t.tz(tz).format("YYYY-MM-DD HH:mm");
break;
case 5:
out = t.tz(tz).format("HH:mm");
break;
}
return out;
}
// Format tooltip notice
function format_tooltip_notice(start, end) {
var notice = "";
if (end.isBefore()) {
notice = "Event ended " + end.fromNow();
} else if (start.isAfter()) {
notice = "Event will start " + start.fromNow();
} else {
notice = "Event started " + start.fromNow() + " and will end " +
end.fromNow();
}
return '<span class="tooltipnotice">' + notice + '</span>';
}
// Format tooltip table
function format_tooltip_table(start, end) {
var current_timezone = get_current_tz_cb();
var out = '<table><tr><th>Timezone</th><th>Start</th><th>End</th></tr>';
if (meeting_timezone !== "") {
out += '<tr><td class="timehead">Meeting timezone:</td><td>' +
format_time(start, meeting_timezone, 0) + '</td><td>' +
format_time(end, meeting_timezone, 0) + '</td></tr>';
}
out += '<tr><td class="timehead">Local timezone:</td><td>' +
format_time(start, local_timezone, 0) + '</td><td>' +
format_time(end, local_timezone, 0) + '</td></tr>';
if (current_timezone !== 'UTC') {
out += '<tr><td class="timehead">Selected Timezone:</td><td>' +
format_time(start, current_timezone, 0) + '</td><td>' +
format_time(end, current_timezone, 0) + '</td></tr>';
}
out += '<tr><td class="timehead">UTC:</td><td>' +
format_time(start, 'UTC', 0) + '</td><td>' +
format_time(end, 'UTC', 0) + '</td></tr>';
out += '</table>' + format_tooltip_notice(start, end);
return out;
}
// Format tooltip for item
function format_tooltip(start, end) {
return '<span class="timetooltiptext">' +
format_tooltip_table(start, end) +
'</span>';
}
// Add tooltips
function add_tooltips() {
$('span.time').each(function () {
var tooltip = $(format_tooltip(this.start_ts, this.end_ts));
tooltip[0].start_ts = this.start_ts;
tooltip[0].end_ts = this.end_ts;
tooltip[0].ustart_ts = moment(this.start_ts).add(-2, 'hours');
tooltip[0].uend_ts = moment(this.end_ts).add(2, 'hours');
$(this).parent().append(tooltip);
});
}
// Update times on the agenda based on the selected timezone
function update_times(newtz) {
$('span.current-tz').html(newtz);
$('span.time').each(function () {
if (this.format == 4) {
var tz = this.start_ts.tz(newtz).format(" z");
if (this.start_ts.tz(newtz).dayOfYear() ==
this.end_ts.tz(newtz).dayOfYear()) {
$(this).html(format_time(this.start_ts, newtz, this.format) +
'-' + format_time(this.end_ts, newtz, 5) + tz);
} else {
$(this).html(format_time(this.start_ts, newtz, this.format) +
'-' +
format_time(this.end_ts, newtz, this.format) + tz);
}
} else {
$(this).html(format_time(this.start_ts, newtz, this.format) + '-' +
format_time(this.end_ts, newtz, this.format));
}
});
update_tooltips_all();
update_clock();
}
// Highlight ongoing based on the current time
function highlight_ongoing() {
$("div#now").remove("#now");
$('.ongoing').removeClass("ongoing");
var agenda_rows=$('[data-slot-start-ts]')
agenda_rows = agenda_rows.filter(function() {
return moment().isBetween(this.slot_start_ts, this.slot_end_ts);
});
agenda_rows.addClass("ongoing");
agenda_rows.first().children("th, td").
prepend($('<div id="now" class="anchor-target"></div>'));
}
// Update tooltips
function update_tooltips() {
var tooltips=$('.timetooltiptext');
tooltips.filter(function() {
return moment().isBetween(this.ustart_ts, this.uend_ts);
}).each(function () {
$(this).html(format_tooltip_table(this.start_ts, this.end_ts));
});
}
// Update all tooltips
function update_tooltips_all() {
var tooltips=$('.timetooltiptext');
tooltips.each(function () {
$(this).html(format_tooltip_table(this.start_ts, this.end_ts));
});
}
// Update clock
function update_clock() {
$('#current-time').html(format_time(moment(), get_current_tz_cb(), 0));
}
$.urlParam = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results == null) {
return null;
} else {
return results[1] || 0;
}
}
function init_timers() {
var fast_timer = 60000 / (speedup > 600 ? 600 : speedup);
update_clock();
highlight_ongoing();
setInterval(function() { update_clock(); }, fast_timer);
setInterval(function() { highlight_ongoing(); }, fast_timer);
setInterval(function() { update_tooltips(); }, fast_timer);
setInterval(function() { update_tooltips_all(); }, 3600000 / speedup);
}
// set method used to find current time zone
function set_current_tz_cb(fn) {
get_current_tz_cb = fn;
}

View file

@ -1,265 +1,74 @@
// Callback for timezone change - called after current_timezone is updated
var timezone_change_callback;
// Copyright The IETF Trust 2021, All Rights Reserved
// Initialize moments
function initialize_moments() {
var times=$('span.time')
$.each(times, function(i, item) {
item.start_ts = moment.unix(this.getAttribute("data-start-time")).utc();
item.end_ts = moment.unix(this.getAttribute("data-end-time")).utc();
if (this.hasAttribute("weekday")) {
item.format=2;
} else {
item.format=1;
}
if (this.hasAttribute("format")) {
item.format = +this.getAttribute("format");
}
});
var times=$('[data-slot-start-ts]')
$.each(times, function(i, item) {
item.slot_start_ts = moment.unix(this.getAttribute("data-slot-start-ts")).utc();
item.slot_end_ts = moment.unix(this.getAttribute("data-slot-end-ts")).utc();
});
}
/*
Timezone selection handling. Relies on the moment.js library.
// Initialize timezone system
function timezone_init(current) {
var tz_names = moment.tz.names();
var select = $('#timezone_select');
select.empty();
$.each(tz_names, function(i, item) {
if (current === item) {
select.append($('<option/>', {
selected: "selected", html: item, value: item }));
} else {
select.append($('<option/>', {
html: item, value: item }));
}
});
initialize_moments();
select.change(function () {
update_times(this.value);
});
update_times(current);
add_tooltips();
}
To use, create one (or more) select inputs with class "tz-select". When the initialize()
method is called, the options in the select will be replaced with the recognized time zone
names. Time zone can be changed via the select input or by calling the use() method with
the name of a time zone (or 'local' to guess the user's local timezone).
*/
var ietf_timezone; // public interface
// Select which timezone is used, 0 = meeting, 1 = browser local, 2 = UTC
function use_timezone (val) {
switch (val) {
case 0:
tz = meeting_timezone;
break;
case 1:
tz = local_timezone;
break;
default:
tz = 'UTC';
break;
(function () {
'use strict';
// Callback for timezone change - called after current_timezone is updated
var timezone_change_callback;
var current_timezone;
// Select timezone to use. Arg is name of a timezone or 'local' to guess local tz.
function use_timezone (newtz) {
// Guess local timezone if necessary
if (newtz.toLowerCase() === 'local') {
newtz = moment.tz.guess()
}
if (current_timezone !== newtz) {
current_timezone = newtz
// Update values of tz-select inputs but do not trigger change event
$('select.tz-select').val(newtz)
if (timezone_change_callback) {
timezone_change_callback(newtz)
}
}
}
$('#timezone_select').val(tz);
update_times(tz);
}
// Format time for item for timezone. Depending on the fmt
// use different formats.
// Formats: 0 = long format "Saturday, October 24th 2020, 13:52 +00:00 UTC"
// 1 = Short format "13:52", "13:52 (-1)", or "13:52 (+1)"
// 2 = Short format with weekday, "Friday, 13:52 (-1)"
// 3 = Date only "2020-10-24"
// 4 = Date and time "2020-10-24 13:52"
// 5 = Time only "13:52".
/* Initialize timezone system
*
* This will set the timezone to the value of 'current'. Set up the tz_change callback
* before initializing.
*/
function timezone_init (current) {
var tz_names = moment.tz.names()
var select = $('select.tz-select')
function format_time(t, tz, fmt) {
var out;
var mtz = meeting_timezone;
if (mtz == "") {
mtz = "UTC";
select.empty()
$.each(tz_names, function (i, item) {
if (current === item) {
select.append($('<option/>', {
selected: 'selected', html: item, value: item
}))
} else {
select.append($('<option/>', {
html: item, value: item
}))
}
})
select.change(function () {use_timezone(this.value)});
/* When navigating back/forward, the browser may change the select input's
* value after the window load event. It does not fire the change event on
* the input when it does this. The pageshow event occurs after such an update,
* so trigger the change event ourselves to be sure the UI stays consistent
* with the timezone select input. */
window.addEventListener('pageshow', function(){select.change()})
use_timezone(current);
}
switch (fmt) {
case 0:
out = t.tz(tz).format('dddd, ') + '<span class="hidden-xs">' +
t.tz(tz).format('MMMM Do YYYY, ') + '</span>' +
t.tz(tz).format('HH:mm') + '<span class="hidden-xs">' +
t.tz(tz).format(' Z z') + '</span>';
break;
case 1:
// Note, this code does not work if the meeting crosses the
// year boundary.
out = t.tz(tz).format("HH:mm");
if (+t.tz(tz).dayOfYear() < +t.tz(mtz).dayOfYear()) {
out = out + " (-1)";
} else if (+t.tz(tz).dayOfYear() > +t.tz(mtz).dayOfYear()) {
out = out + " (+1)";
}
break;
case 2:
out = t.tz(mtz).format("dddd, ").toUpperCase() +
t.tz(tz).format("HH:mm");
if (+t.tz(tz).dayOfYear() < +t.tz(mtz).dayOfYear()) {
out = out + " (-1)";
} else if (+t.tz(tz).dayOfYear() > +t.tz(mtz).dayOfYear()) {
out = out + " (+1)";
}
break;
case 3:
out = t.utc().format("YYYY-MM-DD");
break;
case 4:
out = t.tz(tz).format("YYYY-MM-DD HH:mm");
break;
case 5:
out = t.tz(tz).format("HH:mm");
break;
// Expose public interface
ietf_timezone = {
get_current_tz: function() {return current_timezone},
initialize: timezone_init,
set_tz_change_callback: function(cb) {timezone_change_callback=cb},
use: use_timezone
}
return out;
}
// Format tooltip notice
function format_tooltip_notice(start, end) {
var notice = "";
if (end.isBefore()) {
notice = "Event ended " + end.fromNow();
} else if (start.isAfter()) {
notice = "Event will start " + start.fromNow();
} else {
notice = "Event started " + start.fromNow() + " and will end " +
end.fromNow();
}
return '<span class="tooltipnotice">' + notice + '</span>';
}
// Format tooltip table
function format_tooltip_table(start, end) {
var out = '<table><tr><th>Timezone</th><th>Start</th><th>End</th></tr>';
if (meeting_timezone != "") {
out += '<tr><td class="timehead">Meeting timezone:</td><td>' +
format_time(start, meeting_timezone, 0) + '</td><td>' +
format_time(end, meeting_timezone, 0) + '</td></tr>';
}
out += '<tr><td class="timehead">Local timezone:</td><td>' +
format_time(start, local_timezone, 0) + '</td><td>' +
format_time(end, local_timezone, 0) + '</td></tr>';
if (current_timezone != 'UTC') {
out += '<tr><td class="timehead">Selected Timezone:</td><td>' +
format_time(start, current_timezone, 0) + '</td><td>' +
format_time(end, current_timezone, 0) + '</td></tr>';
}
out += '<tr><td class="timehead">UTC:</td><td>' +
format_time(start, 'UTC', 0) + '</td><td>' +
format_time(end, 'UTC', 0) + '</td></tr>';
out += '</table>' + format_tooltip_notice(start, end);
return out;
}
// Format tooltip for item
function format_tooltip(start, end) {
return '<span class="timetooltiptext">' +
format_tooltip_table(start, end) +
'</span>';
}
// Add tooltips
function add_tooltips() {
$('span.time').each(function () {
var tooltip = $(format_tooltip(this.start_ts, this.end_ts));
tooltip[0].start_ts = this.start_ts;
tooltip[0].end_ts = this.end_ts;
tooltip[0].ustart_ts = moment(this.start_ts).add(-2, 'hours');
tooltip[0].uend_ts = moment(this.end_ts).add(2, 'hours');
$(this).parent().append(tooltip);
});
}
// Update times on the agenda based on the selected timezone
function update_times(newtz) {
current_timezone = newtz;
$('span.current-tz').html(newtz);
$('span.time').each(function () {
if (this.format == 4) {
var tz = this.start_ts.tz(newtz).format(" z");
if (this.start_ts.tz(newtz).dayOfYear() ==
this.end_ts.tz(newtz).dayOfYear()) {
$(this).html(format_time(this.start_ts, newtz, this.format) +
'-' + format_time(this.end_ts, newtz, 5) + tz);
} else {
$(this).html(format_time(this.start_ts, newtz, this.format) +
'-' +
format_time(this.end_ts, newtz, this.format) + tz);
}
} else {
$(this).html(format_time(this.start_ts, newtz, this.format) + '-' +
format_time(this.end_ts, newtz, this.format));
}
});
update_tooltips_all();
update_clock();
if (timezone_change_callback) {
timezone_change_callback(newtz);
}
}
// Highlight ongoing based on the current time
function highlight_ongoing() {
$("div#now").remove("#now");
$('.ongoing').removeClass("ongoing");
var agenda_rows=$('[data-slot-start-ts]')
agenda_rows = agenda_rows.filter(function() {
return moment().isBetween(this.slot_start_ts, this.slot_end_ts);
});
agenda_rows.addClass("ongoing");
agenda_rows.first().children("th, td").
prepend($('<div id="now" class="anchor-target"></div>'));
}
// Update tooltips
function update_tooltips() {
var tooltips=$('.timetooltiptext');
tooltips.filter(function() {
return moment().isBetween(this.ustart_ts, this.uend_ts);
}).each(function () {
$(this).html(format_tooltip_table(this.start_ts, this.end_ts));
});
}
// Update all tooltips
function update_tooltips_all() {
var tooltips=$('.timetooltiptext');
tooltips.each(function () {
$(this).html(format_tooltip_table(this.start_ts, this.end_ts));
});
}
// Update clock
function update_clock() {
$('#current-time').html(format_time(moment(), current_timezone, 0));
}
$.urlParam = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results == null) {
return null;
} else {
return results[1] || 0;
}
}
function init_timers() {
var fast_timer = 60000 / (speedup > 600 ? 600 : speedup);
update_clock();
highlight_ongoing();
setInterval(function() { update_clock(); }, fast_timer);
setInterval(function() { highlight_ongoing(); }, fast_timer);
setInterval(function() { update_tooltips(); }, fast_timer);
setInterval(function() { update_tooltips_all(); }, 3600000 / speedup);
}
// Register a callback for timezone change
function set_tz_change_callback(cb) {
timezone_change_callback = cb;
}
})();

View file

@ -40,14 +40,14 @@
font-weight: normal;
}
.tz-display select {
min-width: 15em;
min-width: 15em;
}
#affix .nav li.tz-display {
padding: 4px 20px;
padding: 4px 20px;
}
#affix .nav li.tz-display a {
display: inline;
padding: 0;
display: inline;
padding: 0;
}
{% endblock %}
@ -76,12 +76,12 @@
<div class="col-xs-6">
<div class="tz-display">
<div><small>
<label for="timezone_select">Time zone:</label>
<a id="meeting-timezone" onclick="use_timezone(0)">Meeting</a> |
<a id="local-timezone" onclick="use_timezone(1)">Local</a> |
<a id="utc-timezone" onclick="use_timezone(2)">UTC</a>
<label for="timezone-select">Time zone:</label>
<a id="meeting-timezone" onclick="ietf_timezone.use('{{ timezone }}')">Meeting</a> |
<a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> |
<a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a>
</small></div>
<select id="timezone_select">
<select id="timezone-select" class="tz-select">
{# Avoid blank while loading. JavaScript replaces the option list after init. #}
<option selected>{{ timezone }}</option>
</select>
@ -348,23 +348,23 @@
</div>
<div class="col-md-2 hidden-print bs-docs-sidebar" id="affix">
<ul class="nav nav-pills nav-stacked small" data-spy="affix">
<li><a href="#now">Now</a></li>
{% for item in filtered_assignments %}
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
<li><a href="#{{item.timeslot.time|slugify}}">{{ item.timeslot.time|date:"l, F j, Y" }}</a></li>
{% endifchanged %}
{% endfor %}
<li><hr/></li>
<li class="tz-display">Showing <span class="current-tz">{{ timezone }}</span> time</li>
<li class="tz-display"><span> {# span avoids applying nav link styling to these shortcuts #}
<a onclick="use_timezone(0)">Meeting time</a> |
<a onclick="use_timezone(1)">Local time</a> |
<a onclick="use_timezone(2)">UTC</a></span>
</li>
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
<li><hr/></li>
<li><span id="current-time"></span></li>
{% endif %}
<li><a href="#now">Now</a></li>
{% for item in filtered_assignments %}
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
<li><a href="#{{item.timeslot.time|slugify}}">{{ item.timeslot.time|date:"l, F j, Y" }}</a></li>
{% endifchanged %}
{% endfor %}
<li><hr/></li>
<li class="tz-display">Showing <span class="current-tz">{{ timezone }}</span> time</li>
<li class="tz-display"><span> {# span avoids applying nav link styling to these shortcuts #}
<a onclick="ietf_timezone.use('{{ timezone }}')">Meeting time</a> |
<a onclick="ietf_timezone.use('local')">Local time</a> |
<a onclick="ietf_timezone.use('UTC')">UTC</a></span>
</li>
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
<li><hr/></li>
<li><span id="current-time"></span></li>
{% endif %}
</ul>
</div>
</div>
@ -376,8 +376,6 @@
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
<script>
var current_timezone = 'UTC';
// Update the agenda display with specified filters
function update_agenda_display(filter_params) {
var agenda_rows=$('[id^="row-"]')
@ -402,7 +400,7 @@
// this is a "negative" item by wg: when present, hide these rows
agenda_filter.rows_matching_filter_keyword(agenda_rows, v).hide();
});
// Now hide any session label rows with no visible sessions. Identify
// by matching on start/end timestamps.
$('tr.session-label-row').each(function(i, e) {
@ -442,15 +440,15 @@
}
update_weekview_display();
}
function update_weekview_display() {
var weekview = $("#weekview");
if (!weekview.hasClass('hidden')) {
var queryparams = window.location.search;
if (queryparams) {
queryparams += '&tz=' + current_timezone.toLowerCase();
queryparams += '&tz=' + ietf_timezone.get_current_tz().toLowerCase();
} else {
queryparams = '?tz=' + current_timezone.toLowerCase();
queryparams = '?tz=' + ietf_timezone.get_current_tz().toLowerCase();
}
var new_url = 'week-view.html' + queryparams;
var wv_iframe = document.getElementById('weekview');
@ -545,6 +543,7 @@
<script src="{% static 'moment/min/moment.min.js' %}"></script>
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_timezone.js' %}"></script>
<script>
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
@ -567,20 +566,40 @@
speedup = 1;
{% endif %}
// Get meeting and local times, initialize timezone system
meeting_timezone = "{{timezone}}";
local_timezone = moment.tz.guess();
{% if "-utc" in request.path %}
timezone_init('UTC');
{% else %}
timezone_init(meeting_timezone);
{% endif %}
init_timers();
$(document).ready(function() {
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
meeting_timezone = '{{ timezone }}';
set_tz_change_callback(update_weekview_display);
agenda_filter.set_update_callback(update_view);
agenda_filter.enable();
// First, initialize_moments(). This must be done before calling any of the update methods.
// It does not need timezone info, so safe to call before initializing ietf_timezone.
initialize_moments(); // fills in moments in the agenda data
// Now set up callbacks related to ietf_timezone. This must happen before calling initialize().
// In particular, set_current_tz_cb() must be called before the update methods are called.
set_current_tz_cb(ietf_timezone.get_current_tz); // give agenda_timezone access to this method
ietf_timezone.set_tz_change_callback(function(newtz) {
update_times(newtz);
update_weekview_display();
}
);
// With callbacks in place, call ietf_timezone.initialize(). This will call the tz_change callback
// after setting things up.
{% if "-utc" in request.path %}
ietf_timezone.initialize('UTC');
{% else %}
ietf_timezone.initialize(meeting_timezone);
{% endif %}
// Now make other setup calls from agenda_timezone.js
add_tooltips();
init_timers();
// Finally, set up the agenda filter UI. This does not depend on the timezone.
agenda_filter.set_update_callback(update_view);
agenda_filter.enable();
}
);
</script>
{% endblock %}

View file

@ -15,20 +15,56 @@
{% block title %}Upcoming Meetings{% endblock %}
{% block morecss %}
div.title-buttons {
margin-bottom: 0.5em;
margin-top: 1em;
text-align: right;
}
.tz-display {
margin-top: 0.5em;
}
.tz-display label {
font-weight: normal;
}
.tz-display a {
cursor: pointer;
}
select.tz-select {
min-width: 15em;
margin-bottom: 0.3em;
}
{% endblock %}
{% block content %}
{% origin %}
<div class="row">
<div class="col-md-10">
<div class="row">
<div class="col-xs-6">
<h1>Upcoming Meetings</h1>
</div>
<div class="title-buttons col-xs-6">
<div>
<a title="iCalendar subscription for upcoming meetings" href="webcal://{{request.get_host}}{% url 'ietf.meeting.views.upcoming_ical' %}">
<span class="fa fa-stack-1"><i class="fa fa-fw fa-calendar-o fa-stack-1x"></i><i class="fa fa-fw fa-repeat fa-stack-xs"></i></span>
</a>
<a title="iCalendar entry for upcoming meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}"><span class="fa fa-calendar"></span></a>
</div>
<div class="tz-display">
<label for="timezone-select">Time zone:</label>
<small>
<a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> |
<a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a>
</small>
<select class="tz-select" id="timezone-select" autocomplete="off">
<!-- Avoid blank while loading. Needs to agree with native times in the table
so the display is correct if JS is not enabled -->
<option selected>UTC</option>
</select>
<h1>Upcoming Meetings
<span class="regular pull-right">
<a title="iCalendar subscription for upcoming meetings" href="webcal://{{request.get_host}}{% url 'ietf.meeting.views.upcoming_ical' %}">
<span class="fa fa-stack-1"><i class="fa fa-fw fa-calendar-o fa-stack-1x"></i><i class="fa fa-fw fa-repeat fa-stack-xs"></i></span>
</a>
<a title="iCalendar entry for upcoming meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}"><span class="fa fa-calendar"></span></a>
</span>
</h1>
</div>
</div>
</div>
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
<p>Meeting important dates are not included in upcoming meeting calendars. They have <a href="{% url 'ietf.meeting.views.important_dates' %}">their own calendar</a></p>
@ -67,18 +103,27 @@
</thead>
<tbody>
{% for entry in entries %}
<tr class="entry"
<tr class="entry"
{% if entry|classname == 'Session' %}data-filter-keywords="{{ entry.filter_keywords|join:',' }}"{% endif %}>
{% if entry|classname == 'Meeting' %}
{% with meeting=entry %}
<td>{{ meeting.date }} - {{ meeting.end }}</td>
<td class="meeting-time"
data-start-date="{{ meeting.date }}" {# dates local to meeting #}
data-end-date="{{ meeting.end }}"
data-time-zone="{{ meeting.time_zone }}">
{{ meeting.date }} - {{ meeting.end }}
</td>
<td>ietf</td>
<td><a class="ietf-meeting-link" href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
<td></td>
{% endwith %}
{% elif entry|classname == 'Session' %}
{% with session=entry group=entry.group meeting=entry.meeting%}
<td>{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i e" }}</td>
<td class="session-time"
data-start-utc="{{ session.official_timeslotassignment.timeslot.utc_start_time | date:'Y-m-d H:i' }}Z"
data-end-utc="{{ session.official_timeslotassignment.timeslot.utc_end_time | date:'Y-m-d H:i' }}Z">
{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i" }}
</td>
<td><a href="{% url 'ietf.group.views.group_home' acronym=group.acronym %}">{{ group.acronym }}</a></td>
<td>
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=group.acronym %}"> {{ meeting.number }}</a>
@ -109,34 +154,52 @@
{% endcache %}
</div>
</div>
<div id="calendar" class="col-md-10" ></div>
<div class="row">
<div class="col-md-10">
<div class="tz-display text-right">
<label for="timezone-select-bottom">Time zone: </label>
<small>
<a id="local-timezone-bottom" onclick="ietf_timezone.use('local')">Local</a> |
<a id="utc-timezone-bottom" onclick="ietf_timezone.use('UTC')">UTC</a>
</small>
<select class="tz-select" id="timezone-select-bottom"></select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-10">
<div id="calendar"></div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
<script src="{% static 'fullcalendar/core/main.js' %}"></script>
<script src="{% static 'fullcalendar/daygrid/main.js' %}"></script>
<script src="{% static 'moment/min/moment.min.js' %}"></script>
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
<script>
// List of all events with meta-info needed for filtering
var all_event_list = [{% for entry in entries %}
{% if entry|classname == 'Meeting' %}
{% with meeting=entry %}
{
title: 'IETF {{ meeting.number }}',
start: '{{meeting.date}}',
end: '{{meeting.end}}',
ietf_meeting_number: '{{ meeting.number }}',
start_moment: moment.tz('{{meeting.date}}', '{{ meeting.time_zone }}').startOf('day'),
end_moment: moment.tz('{{meeting.end}}', '{{ meeting.time_zone }}').endOf('day'),
url: '{% url 'ietf.meeting.views.agenda' num=meeting.number %}'
}{% if not forloop.last %}, {% endif %}
{% endwith %}
{% else %} {# if it's not a Meeting, it's a Session #}
{% with session=entry %}
{
title: '{{session.official_timeslotassignment.timeslot.utc_start_time|date:"H:i"}}-{{session.official_timeslotassignment.timeslot.utc_end_time|date:"H:i"}}',
group: '{% if session.group %}{{session.group.acronym}}{% endif %}',
filter_keywords: ["{{ session.filter_keywords|join:'","' }}"],
start: '{{session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}}',
end: '{{session.official_timeslotassignment.timeslot.utc_end_time | date:"Y-m-d H:i"}}',
start_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}}'),
end_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_end_time | date:"Y-m-d H:i"}}'),
url: '{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}'
}
{% endwith %}
@ -144,7 +207,9 @@
{% endif %}
{% endfor %}];
var filtered_event_list = []; // currently visible list
var display_events = []; // filtered events, processed for calendar display
var event_calendar; // handle on the calendar object
var current_tz = 'UTC';
// Test whether an event should be visible given a set of filter parameters
function calendar_event_visible(filter_params, event) {
@ -158,28 +223,55 @@
&& agenda_filter.keyword_match(filter_params.show, event.filter_keywords));
}
// Apply filter_params to the event list and format data for the calendar
/* Apply filter_params to the event list */
function filter_calendar_events(filter_params, event_list) {
var calendarEl = document.getElementById('calendar');
var glue = calendarEl.clientWidth > 720 ? ' ' : '\n';
var filtered_output = [];
for (var ii = 0; ii < event_list.length; ii++) {
var this_event = event_list[ii];
if (calendar_event_visible(filter_params, this_event)) {
filtered_output.push({
title: this_event.title + (this_event.group ? (glue + this_event.group) : ''),
start: this_event.start,
end: this_event.end,
url: this_event.url
})
filtered_output.push(this_event);
}
}
return filtered_output;
}
// Initialize or update the calendar, updating the filtered event list
function update_calendar(filter_params) {
filtered_event_list = filter_calendar_events(filter_params, all_event_list);
// format a moment in a tz
var moment_formats = {time: 'HH:mm', date: 'YYYY-MM-DD', datetime: 'YYYY-MM-DD HH:mm'};
function format_moment(t_moment, tz, fmt_type) {
return t_moment.tz(tz).format(moment_formats[fmt_type]);
}
function make_display_events(event_data, tz) {
var output = [];
var calendarEl = document.getElementById('calendar');
var glue = calendarEl.clientWidth > 720 ? ' ' : '\n';
return $.map(event_data, function(src_event) {
var title;
// Render IETF meetings with meeting dates, sessions with actual times
if (src_event.ietf_meeting_number) {
title = 'IETF ' + src_event.ietf_meeting_number;
} else {
title = (format_moment(src_event.start_moment, tz, 'time') + '-'
+ format_moment(src_event.end_moment, tz, 'time')
+ glue + (src_event.group || 'Invalid event'));
}
return {
title: title,
start: format_moment(src_event.start_moment, tz, 'datetime'),
end: format_moment(src_event.end_moment, tz, 'datetime'),
url: src_event.url
}; // all events have the URL
});
}
// Initialize or update the calendar, updating the filtered event list and/or timezone
function update_calendar(tz, filter_params) {
if (filter_params) {
// Update event list if we were called with filter params
filtered_event_list = filter_calendar_events(filter_params, all_event_list);
}
display_events = make_display_events(filtered_event_list, tz);
if (event_calendar) {
event_calendar.refetchEvents()
} else {
@ -191,7 +283,7 @@
event_calendar = new FullCalendar.Calendar(calendarEl, {
plugins: ['dayGrid'],
displayEventTime: false,
events: function (fInfo, success) {success(filtered_event_list)},
events: function (fInfo, success) {success(display_events)},
eventRender: function (info) {
$(info.el).tooltip({ title: info.event.title })
},
@ -239,7 +331,7 @@
function update_view(filter_params) {
update_meeting_display(filter_params);
update_links(filter_params);
update_calendar(filter_params);
update_calendar(current_tz, filter_params);
}
// Set up the filtering - the callback will be called when the page loads and on any filter changes
@ -276,5 +368,38 @@
});
}
});
function format_session_time(session_elt, tz) {
var start = moment.utc($(session_elt).attr('data-start-utc'));
var end = moment.utc($(session_elt).attr('data-end-utc'));
return format_moment(start, tz, 'datetime') + ' - ' + format_moment(end, tz, 'time');
}
function format_meeting_time(meeting_elt, tz) {
var meeting_tz = $(meeting_elt).attr('data-time-zone');
var start = moment.tz($(meeting_elt).attr('data-start-date'), meeting_tz).startOf('day');
var end = moment.tz($(meeting_elt).attr('data-end-date'), meeting_tz).endOf('day');
return format_moment(start, tz, 'date') + ' - ' + format_moment(end, tz, 'date');
}
function timezone_changed(newtz) {
// update times for events in the table
if (current_tz !== newtz) {
current_tz = newtz;
$('.session-time').each(function () {
$(this).html(format_session_time(this, newtz));
});
$('.meeting-time').each(function () {
$(this).html(format_meeting_time(this, newtz));
});
}
update_calendar(newtz);
}
// Init with best guess at local timezone.
ietf_timezone.set_tz_change_callback(timezone_changed);
ietf_timezone.initialize('local');
</script>
{% endblock %}