From a5604992f2c9e20759048bfde5c7bdc0cb6e447f Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Fri, 30 Apr 2021 16:14:00 +0000 Subject: [PATCH] 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. Commit ready for merge. - Legacy-Id: 18970 --- ietf/meeting/tests_js.py | 267 ++++++++++++--- ietf/meeting/views.py | 12 +- ietf/static/ietf/js/agenda/agenda_filter.js | 1 + ietf/static/ietf/js/agenda/agenda_timezone.js | 229 +++++++++++++ ietf/static/ietf/js/agenda/timezone.js | 323 ++++-------------- ietf/templates/meeting/agenda.html | 107 +++--- ietf/templates/meeting/upcoming.html | 191 +++++++++-- 7 files changed, 746 insertions(+), 384 deletions(-) create mode 100644 ietf/static/ietf/js/agenda/agenda_timezone.js diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 13fec4bc3..95e7b9a35 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -13,7 +13,8 @@ import django #from django.urls import reverse as urlreverse 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 @@ -37,7 +38,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 @@ -957,25 +958,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') @@ -991,73 +989,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): @@ -1099,7 +1134,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: @@ -1155,10 +1190,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, @@ -1551,6 +1586,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 diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index d3d3edc09..0b5e662da 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -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) diff --git a/ietf/static/ietf/js/agenda/agenda_filter.js b/ietf/static/ietf/js/agenda/agenda_filter.js index 9840303df..58799390b 100644 --- a/ietf/static/ietf/js/agenda/agenda_filter.js +++ b/ietf/static/ietf/js/agenda/agenda_filter.js @@ -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(); diff --git a/ietf/static/ietf/js/agenda/agenda_timezone.js b/ietf/static/ietf/js/agenda/agenda_timezone.js new file mode 100644 index 000000000..1d05a65d2 --- /dev/null +++ b/ietf/static/ietf/js/agenda/agenda_timezone.js @@ -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, ') + '' + + t.tz(tz).format('HH:mm') + ''; + 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 '' + notice + ''; +} + +// Format tooltip table +function format_tooltip_table(start, end) { + var current_timezone = get_current_tz_cb(); + var out = ''; + if (meeting_timezone !== "") { + out += ''; + } + out += ''; + if (current_timezone !== 'UTC') { + out += ''; + } + out += ''; + out += '
TimezoneStartEnd
Meeting timezone:' + + format_time(start, meeting_timezone, 0) + '' + + format_time(end, meeting_timezone, 0) + '
Local timezone:' + + format_time(start, local_timezone, 0) + '' + + format_time(end, local_timezone, 0) + '
Selected Timezone:' + + format_time(start, current_timezone, 0) + '' + + format_time(end, current_timezone, 0) + '
UTC:' + + format_time(start, 'UTC', 0) + '' + + format_time(end, 'UTC', 0) + '
' + format_tooltip_notice(start, end); + return out; +} + +// Format tooltip for item +function format_tooltip(start, end) { + return '' + + format_tooltip_table(start, end) + + ''; +} + +// 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($('
')); +} + +// 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; +} \ No newline at end of file diff --git a/ietf/static/ietf/js/agenda/timezone.js b/ietf/static/ietf/js/agenda/timezone.js index 61e271e53..c282185a7 100644 --- a/ietf/static/ietf/js/agenda/timezone.js +++ b/ietf/static/ietf/js/agenda/timezone.js @@ -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($('