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('MMMM Do YYYY, ') + '' + + t.tz(tz).format('HH:mm') + '' + + t.tz(tz).format(' Z z') + ''; + 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 = '
Timezone | Start | End |
---|---|---|
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) + ' |
Timezone | Start | End |
---|---|---|
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) + ' |
For more on regular IETF meetings see here
Meeting important dates are not included in upcoming meeting calendars. They have their own calendar
@@ -67,18 +103,27 @@ {% for entry in entries %} -