Localise schedule times
Show local times on schedule and presentation details if user is in a different timezone to the conference.
This commit is contained in:
		
							parent
							
								
									d9e8a72d14
								
							
						
					
					
						commit
						b56ca32259
					
				
					 14 changed files with 168 additions and 73 deletions
				
			
		|  | @ -453,6 +453,8 @@ GAPC_STORAGE = { | |||
| SETTINGS_EXPORT = [ | ||||
|     'DEBUG', | ||||
|     'ANALYTICS_KEY', | ||||
|     'TIME_ZONE', | ||||
|     'LCA_START', | ||||
| ] | ||||
| 
 | ||||
| if DEV_MODE and DEV_MODE == "LAPTOP": | ||||
|  | @ -514,11 +516,11 @@ class PenguinDinnerCat(Category): | |||
|         return t | ||||
| 
 | ||||
| 
 | ||||
| _TZINFO = pytz.timezone(TIME_ZONE) | ||||
| LCA_START = datetime(2021, 1, 23, tzinfo=_TZINFO) | ||||
| LCA_END = datetime(2021, 1, 25, tzinfo=_TZINFO) | ||||
| LCA_MINICONF_END = datetime(2021, 1, 23, 23, 59, tzinfo=_TZINFO) | ||||
| EARLY_BIRD_DEADLINE = datetime(2020, 12, 1, tzinfo=_TZINFO) | ||||
| LCA_TZINFO = pytz.timezone(TIME_ZONE) | ||||
| LCA_START = LCA_TZINFO.localize(datetime(2021, 1, 23)) | ||||
| LCA_END = LCA_TZINFO.localize(datetime(2021, 1, 25)) | ||||
| LCA_MINICONF_END = LCA_TZINFO.localize(datetime(2021, 1, 23, 23, 59)) | ||||
| EARLY_BIRD_DEADLINE = LCA_TZINFO.localize(datetime(2020, 12, 1)) | ||||
| PENGUIN_DINNER_TICKET_DATE = date(2021, 1, 23) | ||||
| SPEAKER_DINNER_TICKET_DATE = date(2021, 1, 25) | ||||
| PDNS_TICKET_DATE = date(2021, 1, 24) | ||||
|  |  | |||
|  | @ -40,6 +40,10 @@ | |||
|   {% block extra_head_base %} | ||||
|   {% block extra_head %}{% endblock %} | ||||
|   {% endblock %} | ||||
| 
 | ||||
|   <script type="text/javascript"> | ||||
|     var CONF_TZ = "{{ settings.TIME_ZONE }}"; | ||||
|   </script> | ||||
| </head> | ||||
| <body class="{% block body_class %}{% endblock %}"> | ||||
|   {% block template_overrides %}{% endblock %} | ||||
|  | @ -85,7 +89,7 @@ | |||
|     {% endblock %} | ||||
| 
 | ||||
|     {% block scripts %} | ||||
|     <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> | ||||
|     <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> | ||||
|     <script src="{% static 'js/app.js' %}" type="text/javascript"></script> | ||||
|     <script src="{% static 'js/jquery.formset.js' %}"></script> | ||||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {% load lca2018_tags %} | ||||
| {% load waffle_tags %} | ||||
| 
 | ||||
| <table class="calendar table table-bordered"> | ||||
| <table class="calendar table table-bordered" data-date="{{ timetable.day.date|date:'c' }}"> | ||||
|   <thead> | ||||
|     <tr> | ||||
|       <th scope="row" class="time"><em>Room</em></th> | ||||
|  | @ -26,9 +26,9 @@ | |||
|   <tbody> | ||||
|     {% for row in timetable %} | ||||
|     <tr class="calendar-row"> | ||||
|       <th scope="row" class="time"><p>{{ row.time|date:"h:iA" }}</p></th> | ||||
|       <th scope="row" class="time" data-time="{{ timetable.day.date|date:'c' }}T{{ row.time|date:'c' }}"><p>{{ row.time|date:"h:iA" }}</p></th> | ||||
|       {% for slot in row.slots %} | ||||
|       <td class="slot slot-{{ slot.kind.label }}" colspan="{{ slot.colspan }}" rowspan="{{ slot.rowspan }}"> | ||||
|       <td class="slot slot-{{ slot.kind.label }}" colspan="{{ slot.colspan }}" rowspan="{{ slot.rowspan }}" data-startdatetime="{{ slot.start_datetime }}" data-enddatetime="{{ slot.end_datetime }}"> | ||||
|         {% with slot.kind.label.lower as label %} | ||||
|         {% if label == "talk" or label == "tutorial" %} | ||||
|           {% if slot.content.unpublish and not request.user.is_staff %} | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| <a | ||||
|   class="nav-link {% if active %}active{% endif %}" | ||||
|   class="nav-link {% if active %}active{% endif %} schedule-day" | ||||
|   id="schedule_day_{{ label|lower }}-tab" | ||||
|   href="#{{ label|lower }}" | ||||
|   data-toggle="pill" | ||||
|   role="tab" | ||||
|   aria-controls="schedule_day_{{ label|lower }}" | ||||
|   aria-selected="{% if active %}true{% else %}false{% endif %}"> | ||||
|   aria-selected="{% if active %}true{% else %}false{% endif %}" | ||||
|   data-date="{{ date }}"> | ||||
|   {{ label }} | ||||
| </a> | ||||
|  |  | |||
							
								
								
									
										8
									
								
								pinaxcon/templates/symposion/schedule/base.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pinaxcon/templates/symposion/schedule/base.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| {% extends "site_base.html" %} | ||||
| 
 | ||||
| {% load static %} | ||||
| 
 | ||||
| {% block extra_script %} | ||||
| <script src="{% static 'js/luxon.min.js' %}"></script> | ||||
| <script src="{% static 'js/schedule.js' %}" type="text/javascript"></script> | ||||
| {% endblock %} | ||||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load lca2018_tags %} | ||||
| {% load lca2019_tags %} | ||||
|  | @ -10,7 +10,7 @@ | |||
| {% block page_title %}{{ presentation.title }}{% endblock %} | ||||
| {% block page_lead %} | ||||
| {% if presentation.slot %} | ||||
| {{ presentation.slot.rooms.0 }} | {{ presentation.slot.day.date|date:"D d M" }} | {{ presentation.slot.start }}–{{ presentation.slot.end }} | ||||
| {{ presentation.slot.rooms.0 }} | <span class="presentation-time" data-starttime="{{ presentation.slot.day.date|date:'c' }}T{{ presentation.slot.start|date:'c' }}" data-endtime="{{ presentation.slot.day.date|date:'c' }}T{{ presentation.slot.end|date:'c' }}">{{ presentation.slot.day.date|date:"D d M" }} {{ presentation.slot.start }}–{{ presentation.slot.end }}</span> | ||||
| {% else %} | ||||
| <em>Not currently scheduled.</em> | ||||
| {% endif %} | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load i18n %} | ||||
| {% load cache %} | ||||
|  | @ -27,7 +27,7 @@ | |||
|         {% for section in sections %} | ||||
|         {% for timetable in section.days %} | ||||
|           <li class="nav-item flex-md-fill text-md-center"> | ||||
|             {% include "symposion/schedule/_schedule_nav_link.html" with active=forloop.first label=timetable.day.date|date:"l" %} | ||||
|             {% include "symposion/schedule/_schedule_nav_link.html" with active=forloop.first label=timetable.day.date|date:"l" date=timetable.day.date|date:"Y-m-d" %} | ||||
|           </li> | ||||
|         {% endfor %} | ||||
|       {% endfor %} | ||||
|  | @ -46,6 +46,7 @@ | |||
|             <span class="clearfix d-sm-block d-md-none"></span> | ||||
|             <small class="text-muted">{{ timetable.day.date|date:"l" }}, {{ timetable.day.date }}</small> | ||||
|           </h2> | ||||
|           <p class="timezone-info small">Conference times are in {{ settings.LCA_START|date:'T' }} (UTC{{ settings.LCA_START|date:'O' }}).</p> | ||||
|           <div class="table-responsive d-none d-md-block"> | ||||
|             {% include "symposion/schedule/_grid.html" %} | ||||
|           </div> | ||||
|  | @ -62,34 +63,6 @@ | |||
| 
 | ||||
| {% block scripts_extra %} | ||||
|   <script type="text/javascript"> | ||||
| 
 | ||||
|     var fragment = window.location.hash.toLowerCase().substring(1); | ||||
| 
 | ||||
|     if (fragment) { | ||||
|       var fragmentid = "#schedule_day_" + fragment + "-tab"; | ||||
|       $(fragmentid).tab('show'); | ||||
|     } else { | ||||
|       var OFFSET = -10 * (60 * 60 * 1000); // Gold Coast is 10 hours ahead of UTC in Jan. | ||||
|       var JAN = 0; // because January is 0, not 1 | ||||
| 
 | ||||
|       var fragments = [ | ||||
|         {"day": "saturday", "time": Date.UTC(2021, JAN, 23)}, | ||||
|         {"day": "sunday", "time": Date.UTC(2021, JAN, 24)}, | ||||
|         {"day": "monday", "time": Date.UTC(2021, JAN, 25)}, | ||||
|         {"day": "tuesday", "time": Date.UTC(2021, JAN, 26)}, | ||||
|       ]; | ||||
| 
 | ||||
|       var now = new Date().getTime(); | ||||
| 
 | ||||
|       for (var i = 0; i < 5; i++) { | ||||
|         var f = fragments[i]; | ||||
|         var g = fragments[i+1]; | ||||
|         if ((f.time + OFFSET) <= now && now < (g.time + OFFSET)) { | ||||
|           fragment = f.day; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     window.addEventListener("hashchange", function(event) { | ||||
|       var fragment = window.location.hash.toLowerCase().substring(1); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load i18n %} | ||||
| {% load cache %} | ||||
|  | @ -16,6 +16,7 @@ | |||
|         <h2 class="my-4"> | ||||
|           {{ timetable.day.date|date:"l" }}, {{ timetable.day.date }} | ||||
|         </h2> | ||||
|         <p class="timezone-info small">Conference times are in {{ settings.LCA_START|date:'T' }} (UTC{{ settings.LCA_START|date:'O' }}).</p> | ||||
|         <div class="table-responsive d-none d-md-block"> | ||||
|           {% include "symposion/schedule/_grid.html" %} | ||||
|         </div> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load i18n %} | ||||
| 
 | ||||
|  | @ -24,6 +24,7 @@ | |||
|       <h2 class="my-4"> | ||||
|         {{ timetable.day.date|date:"l" }}, {{ timetable.day.date }} | ||||
|       </h2> | ||||
|       <p class="timezone-info small">Conference times are in {{ settings.LCA_START|date:'T' }} (UTC{{ settings.LCA_START|date:'O' }}).</p> | ||||
|       <div class="table-responsive d-none d-md-block"> | ||||
|         {% include "symposion/schedule/_grid.html" with edit_schedule=True %} | ||||
|       </div> | ||||
|  | @ -37,28 +38,3 @@ | |||
|   <div class="modal fade" id="slotEditModal"></div> | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block extra_script %} | ||||
|     <script type="text/javascript"> | ||||
|         $(function() { | ||||
|             $("a.edit-slot").click(function(e) { | ||||
|                 $("#slotEditModal").load($(this).data("action"), function() { | ||||
|                     $("#slotEditModal").modal("show"); | ||||
|                 }); | ||||
|                 e.preventDefault(); | ||||
|             }); | ||||
|         }); | ||||
|         $(function() { | ||||
|            //submit event handler | ||||
|             $("form#schedule-builder :submit").click(function(e) { | ||||
|                 var name = this.name; | ||||
|                 if(name == 'delete') { | ||||
|                     if (!confirm("Are you sure you want to delete the schedule?")) | ||||
|                         { | ||||
|                             e.preventDefault(); | ||||
|                             return; | ||||
|                         } | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|     </script> | ||||
| {% endblock %} | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load i18n %} | ||||
| {% load cache %} | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load lca2018_tags %} | ||||
| {% load sitetree %} | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% extends "site_base.html" %} | ||||
| {% extends "symposion/schedule/base.html" %} | ||||
| 
 | ||||
| {% load lca2018_tags %} | ||||
| {% load sitetree %} | ||||
|  |  | |||
							
								
								
									
										1
									
								
								static/src/js/luxon.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/src/js/luxon.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										129
									
								
								static/src/js/schedule.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								static/src/js/schedule.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | |||
| $(function() { | ||||
|     /* Schedule display localisation */ | ||||
|     var showCurrentTab = function() { | ||||
|         var fragment = window.location.hash.toLowerCase().substring(1); | ||||
| 
 | ||||
|         var dayTabs = $('#schedule-tabs .schedule-day'); | ||||
|         if (dayTabs.length === 0) return; | ||||
| 
 | ||||
|         if (fragment) { | ||||
|             var fragmentId = "#schedule_day_" + fragment + "-tab"; | ||||
|             $(fragmentId).tab('show'); | ||||
|         } else { | ||||
|             // Show tab based on current time.
 | ||||
|             var now = luxon.DateTime.local(); | ||||
|             for (var i = 0; i < dayTabs.length; ++i) { | ||||
|                 var dayTab = $(dayTabs[i]); | ||||
|                 var tabDate = dayTab.data('date'); | ||||
| 
 | ||||
|                 var scheduleDate = luxon.DateTime.fromISO(tabDate, { zone: CONF_TZ }); | ||||
|                 var startOfDay = scheduleDate.startOf('day'); | ||||
|                 var endOfDay = scheduleDate.endOf('day'); | ||||
|                 if (now >= startOfDay && now < endOfDay) { | ||||
|                     tabShown = true; | ||||
|                     dayTab.tab('show'); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     var updateScheduleGrid = function() { | ||||
|         var rowHeaders = $('.calendar-row th.time'); | ||||
|         for (var i = 0; i < rowHeaders.length; ++i) { | ||||
|             var rowHeader = $(rowHeaders[i]); | ||||
|             var rowTime = rowHeader.data('time'); | ||||
|             var scheduleDate = luxon.DateTime.fromISO(rowTime, { zone: CONF_TZ }); | ||||
|             var localDate = scheduleDate.toLocal(); | ||||
| 
 | ||||
|             // If the schedule date is already in the user's TZ, skip it.
 | ||||
|             if (scheduleDate.offset === localDate.offset) break; | ||||
| 
 | ||||
|             var confFormatted = scheduleDate.toLocaleString({ | ||||
|                 weekday: scheduleDate.weekday === localDate.weekday ? undefined : 'short', | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|                 timeZoneName: 'short', | ||||
|             }); | ||||
|             var localFormatted = localDate.toLocaleString({ | ||||
|                 weekday: scheduleDate.weekday === localDate.weekday ? undefined : 'short', | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|                 timeZoneName: 'short', | ||||
|             }); | ||||
|             var timeText = rowHeader.find('p').text(); | ||||
|             rowHeader.find('p').html(confFormatted + '<br><em>(' + localFormatted + ')</em>'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     var updatePresentationTimes = function() { | ||||
|         var presentationTimes = $('span.presentation-time'); | ||||
|         for (var i = 0; i < presentationTimes.length; ++i) { | ||||
|             var presentationTime = $(presentationTimes[i]); | ||||
|             var startTime = presentationTime.data('starttime'); | ||||
|             var endTime = presentationTime.data('endtime'); | ||||
|             var confStartTime = luxon.DateTime.fromISO(startTime, { zone: CONF_TZ }); | ||||
|             var confEndTime = luxon.DateTime.fromISO(endTime, { zone: CONF_TZ }); | ||||
| 
 | ||||
|             var localStartTime = confStartTime.toLocal(); | ||||
|             var localEndTime = confEndTime.toLocal(); | ||||
| 
 | ||||
|             // If the conf date is already in the user's TZ, skip it.
 | ||||
|             if (confStartTime.offset === localStartTime.offset) break; | ||||
| 
 | ||||
|             var confStartTimeFormatted = confStartTime.toLocaleString({ | ||||
|                 weekday: 'short', | ||||
|                 month: 'short', | ||||
|                 day: '2-digit', | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|             }); | ||||
|             var confEndTimeFormatted = confEndTime.toLocaleString({ | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|                 timeZoneName: 'short', | ||||
|             }); | ||||
|             var localStartTimeFormatted = localStartTime.toLocaleString({ | ||||
|                 weekday: confStartTime.weekday === localStartTime.weekday ? undefined : 'short', | ||||
|                 month: confStartTime.weekday === localStartTime.weekday ? undefined : 'short', | ||||
|                 day: confStartTime.weekday === localStartTime.weekday ? undefined : '2-digit', | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|             }); | ||||
|             var localEndTimeFormatted = localEndTime.toLocaleString({ | ||||
|                 weekday: localStartTime.weekday === localEndTime.weekday ? undefined : 'short', | ||||
|                 month: localStartTime.weekday === localEndTime.weekday ? undefined : 'short', | ||||
|                 day: localStartTime.weekday === localEndTime.weekday ? undefined : '2-digit', | ||||
|                 hour: 'numeric', | ||||
|                 minute: 'numeric', | ||||
|                 timeZoneName: 'short', | ||||
|             }); | ||||
| 
 | ||||
|             presentationTime.html(confStartTimeFormatted + ' - ' + confEndTimeFormatted + ' <em>(' + localStartTimeFormatted + ' - ' + localEndTimeFormatted + ')</em>'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Schedule Editing */ | ||||
|     $("a.edit-slot").click(function(e) { | ||||
|         $("#slotEditModal").load($(this).data("action"), function() { | ||||
|             $("#slotEditModal").modal("show"); | ||||
|         }); | ||||
|         e.preventDefault(); | ||||
|     }); | ||||
| 
 | ||||
|     $("form#schedule-builder :submit").click(function(e) { | ||||
|         var name = this.name; | ||||
|         if(name == 'delete') { | ||||
|             if (!confirm("Are you sure you want to delete the schedule?")) | ||||
|                 { | ||||
|                     e.preventDefault(); | ||||
|                     return; | ||||
|                 } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     /* Update schedule display */ | ||||
|     showCurrentTab(); | ||||
|     updateScheduleGrid(); | ||||
|     updatePresentationTimes(); | ||||
| }); | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Joel Addison
						Joel Addison