refactor: refactor timestamp handling so tests in meeting app pass (#4371)

* refactor: replace datetime.now with timezone.now

* refactor: migrate model fields to use timezone.now as default

* refactor: replace datetime.today with timezone.now

datetime.datetime.today() is equivalent to datetime.datetime.now(); both
return a naive datetime with the current local time.

* refactor: rephrase datetime.now(tz) as timezone.now().astimezone(tz)

This is effectively the same, but is less likely to encourage accidental
use of naive datetimes.

* refactor: revert datetime.today() change to old migrations

* refactor: change a missed datetime.now to timezone.now

* chore: renumber timezone_now migration

* chore: add migration to change timestamps to UTC

* refactor: move tz instantiation/caching from TimeSlot to Meeting

* fix: assume utc if meeting.time_zone is blank

* chore: make datetime.combine() calls tz aware in the meeting app

* ci: correctly use meeting.tz in TimeSlotFactory

* chore: compute TimeSlot utc / local times assuming tz-aware times

* chore: use tzaware math for agenda editor timeslot layout

* chore: fill in Meeting.time_zone where it is blank

Nearly all interim meetings on or before 2016-07-01 have blank
time_zone values. This migration fills these in with PST8PDT.

* chore: disallow blank Meeting.time_zone value

* refactor: no need to handle blank time_zone case in TZ migration

* refactor: remove now-unnecessary checks that meeting has time_zone

* chore: fix timezone handling in agenda.ics and Meeting.updated()

* chore: fix tz handling in interim_request_details, exercise in tests

* chore: fix timezone handling for test_interim_send_announcement

* chore: fix timezone handling in agenda_json()

* chore: fix timezone handling in old agenda

* chore: fix timezone handling for EditTimeslotsTests

* refactor: refactor a few fixes for more consistent timezone handling

* chore: add timezone info to timestamps in fixtures

* chore: remove naive datetime warnings found in meetings.tests_views

* chore: fix a few more test failures in meetings.tests_views

All tests in meetings.tests_views now passing

* chore: remove unused import

* chore: fix timezone handling in test_schedule_generator.py

* chore: fix timezone handling affecting meeting.tests_js

* chore: fix timeslot test bug when local date != UTC date

* test: fix a few failing tests, all meetings tests now pass

(for me, anyway)

* chore: renumber migrations

* chore: update timestamp conversion migration

The django-celery-beat package introduces tables with timestamp
columns. These columns are stored in CELERY_TIMEZONE. Because we run with
this set to UTC, the migration ignores these columns.

* chore: fix pytz-related change in migration

* chore: remove duplicate migrations

* chore: remove CELERY_BEAT_TZ_AWARE setting now that USE_TZ is True

* test: avoid failure in test with bogus timezone
This commit is contained in:
Jennifer Richards 2022-08-26 16:53:19 -03:00 committed by GitHub
parent 42203d7a9c
commit 8b52d27b02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 480 additions and 308 deletions

View file

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
import datetime
import email.utils
import jsonfield
import os
@ -181,11 +180,15 @@ class Group(GroupInfo):
return self.role_set.none()
def status_for_meeting(self,meeting):
end_date = meeting.end_date()+datetime.timedelta(days=1)
previous_meeting = meeting.previous_meeting()
status_events = self.groupevent_set.filter(type='status_update',time__lte=end_date).order_by('-time')
status_events = self.groupevent_set.filter(
type='status_update',
time__lt=meeting.end_datetime(),
).order_by('-time')
if previous_meeting:
status_events = status_events.filter(time__gte=previous_meeting.end_date()+datetime.timedelta(days=1))
status_events = status_events.filter(
time__gte=previous_meeting.end_datetime()
)
return status_events.first()
def get_description(self):

View file

@ -187,7 +187,9 @@ class TimeSlotFactory(factory.django.DjangoModelFactory):
@factory.lazy_attribute
def time(self):
return datetime.datetime.combine(self.meeting.date,datetime.time(11,0))
return self.meeting.tz().localize(
datetime.datetime.combine(self.meeting.date, datetime.time(11, 0))
)
@factory.lazy_attribute
def duration(self):

View file

@ -32,7 +32,7 @@
"comments": "",
"list_subscribe": "",
"state": "active",
"time": "2012-02-26T00:21:36",
"time": "2012-02-26T00:21:36Z",
"unused_tags": [],
"list_archive": "",
"type": "ietf",

View file

@ -118,7 +118,10 @@ def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefe
# assignments = list(assignments_queryset) # make sure we're set in stone
assignments = assignments_queryset
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
# meeting_time is meeting-local midnight at the start of the meeting date
meeting_time = meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time())
)
# replace groups with historic counterparts
groups = [ ]
@ -1149,11 +1152,15 @@ def sessions_post_cancel(request, sessions):
def update_interim_session_assignment(form):
"""Helper function to create / update timeslot assigned to interim session"""
time = datetime.datetime.combine(
form.cleaned_data['date'],
form.cleaned_data['time'])
"""Helper function to create / update timeslot assigned to interim session
form is an InterimSessionModelForm
"""
session = form.instance
meeting = session.meeting
time = meeting.tz().localize(
datetime.datetime.combine(form.cleaned_data['date'], form.cleaned_data['time'])
)
if session.official_timeslotassignment():
slot = session.official_timeslotassignment().timeslot
slot.time = time
@ -1161,14 +1168,14 @@ def update_interim_session_assignment(form):
slot.save()
else:
slot = TimeSlot.objects.create(
meeting=session.meeting,
meeting=meeting,
type_id='regular',
duration=session.requested_duration,
time=time)
SchedTimeSessAssignment.objects.create(
timeslot=slot,
session=session,
schedule=session.meeting.schedule)
schedule=meeting.schedule)
def populate_important_dates(meeting):
assert ImportantDate.objects.filter(meeting=meeting).exists() is False

View file

@ -48,7 +48,7 @@ import socket
import datetime
import pytz
from django.core.management.base import BaseCommand
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import Q
@ -75,10 +75,12 @@ class Command(BaseCommand):
def _meeting_datetime(self, day, *time_args):
"""Generate a datetime on a meeting day"""
return datetime.datetime.combine(
self.start_date,
datetime.time(*time_args)
) + datetime.timedelta(days=day)
return self.meeting_tz.localize(
datetime.datetime.combine(
self.start_date,
datetime.time(*time_args)
) + datetime.timedelta(days=day)
)
def handle(self, *args, **options):
if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]:
@ -87,10 +89,7 @@ class Command(BaseCommand):
opt_delete = options.get('delete', False)
opt_use_old_conflicts = options.get('old_conflicts', False)
self.start_date = options['start_date']
meeting_tz = options['tz']
if not opt_delete and (meeting_tz not in pytz.common_timezones):
self.stderr.write("Warning: {} is not a recognized time zone.".format(meeting_tz))
meeting_tzname = options['tz']
if opt_delete:
if Meeting.objects.filter(number='999').exists():
Meeting.objects.filter(number='999').delete()
@ -98,6 +97,11 @@ class Command(BaseCommand):
else:
self.stderr.write("Dummy meeting IETF 999 does not exist; nothing to do.\n")
else:
try:
self.meeting_tz = pytz.timezone(meeting_tzname)
except pytz.UnknownTimeZoneError:
raise CommandError("{} is not a recognized time zone.".format(meeting_tzname))
if Meeting.objects.filter(number='999').exists():
self.stderr.write("Dummy meeting IETF 999 already exists; nothing to do.\n")
else:
@ -111,7 +115,7 @@ class Command(BaseCommand):
type_id='IETF',
date=self._meeting_datetime(0).date(),
days=7,
time_zone=meeting_tz,
time_zone=meeting_tzname,
)
# Set enabled constraints

View file

@ -138,6 +138,11 @@ class Meeting(models.Model):
def end_date(self):
return self.get_meeting_date(self.days-1)
def end_datetime(self):
"""Datetime of the first instant _after_ the meeting's last day"""
return self.tz().localize(
datetime.datetime.combine(self.get_meeting_date(self.days), datetime.time())
)
def get_00_cutoff(self):
start_date = datetime.datetime(year=self.date.year, month=self.date.month, day=self.date.day, tzinfo=pytz.utc)
importantdate = self.importantdate_set.filter(name_id='idcutoff').first()
@ -322,7 +327,7 @@ class Meeting(models.Model):
for ts in self.timeslot_set.all():
if ts.location_id is None:
continue
ymd = ts.time.date()
ymd = ts.local_start_time().date()
if ymd not in time_slices:
time_slices[ymd] = []
slots[ymd] = []
@ -330,15 +335,15 @@ class Meeting(models.Model):
if ymd in time_slices:
# only keep unique entries
if [ts.time, ts.time + ts.duration, ts.duration.seconds] not in time_slices[ymd]:
time_slices[ymd].append([ts.time, ts.time + ts.duration, ts.duration.seconds])
if [ts.local_start_time(), ts.local_end_time(), ts.duration.seconds] not in time_slices[ymd]:
time_slices[ymd].append([ts.local_start_time(), ts.local_end_time(), ts.duration.seconds])
slots[ymd].append(ts)
days.sort()
for ymd in time_slices:
# Make sure these sort the same way
time_slices[ymd].sort()
slots[ymd].sort(key=lambda x: (x.time, x.duration))
slots[ymd].sort(key=lambda x: (x.local_start_time(), x.duration))
return days,time_slices,slots
# this functions makes a list of timeslices and rooms, and
@ -354,6 +359,11 @@ class Meeting(models.Model):
# SchedTimeSessAssignment.objects.create(schedule = sched,
# timeslot = ts)
def tz(self):
if not hasattr(self, '_cached_tz'):
self._cached_tz = pytz.timezone(self.time_zone)
return self._cached_tz
def vtimezone(self):
try:
tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
@ -374,16 +384,14 @@ class Meeting(models.Model):
self.save()
def updated(self):
min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
# should be Meeting.modified, but we don't have that
min_time = pytz.utc.localize(datetime.datetime(1970, 1, 1, 0, 0, 0))
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
assignments_updated = min_time
if self.schedule:
assignments_updated = SchedTimeSessAssignment.objects.filter(schedule__in=[self.schedule, self.schedule.base if self.schedule else None]).aggregate(Max('modified'))["modified__max"] or min_time
ts = max(timeslots_updated, sessions_updated, assignments_updated)
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
ts = tz.localize(ts)
return ts
return max(timeslots_updated, sessions_updated, assignments_updated)
@memoize
def previous_meeting(self):
@ -604,29 +612,22 @@ class TimeSlot(models.Model):
return self._cached_html_location
def tz(self):
if not hasattr(self, '_cached_tz'):
self._cached_tz = pytz.timezone(self.meeting.time_zone)
return self._cached_tz
return self.meeting.tz()
def tzname(self):
return self.tz().tzname(self.time)
def utc_start_time(self):
local_start_time = self.tz().localize(self.time)
return local_start_time.astimezone(pytz.utc)
return self.time.astimezone(pytz.utc) # USE_TZ is True, so time is aware
def utc_end_time(self):
utc_start = self.utc_start_time()
# Add duration after converting start time, otherwise errors creep in around DST change
return None if utc_start is None else utc_start + self.duration
return self.time.astimezone(pytz.utc) + self.duration # USE_TZ is True, so time is aware
def local_start_time(self):
return self.tz().localize(self.time)
return self.time.astimezone(self.tz())
def local_end_time(self):
local_start = self.local_start_time()
# Add duration after converting start time, otherwise errors creep in around DST change
return None if local_start is None else local_start + self.duration
return (self.time.astimezone(pytz.utc) + self.duration).astimezone(self.tz())
@property
def js_identifier(self):

View file

@ -21,10 +21,12 @@ from ietf.person.factories import PersonFactory
from ietf.person.models import Person
from ietf.utils.test_data import make_test_data
def make_interim_meeting(group,date,status='sched'):
def make_interim_meeting(group,date,status='sched',tz='UTC'):
system_person = Person.objects.get(name="(System)")
time = datetime.datetime.combine(date, datetime.time(9))
meeting = create_interim_meeting(group=group,date=date)
meeting = create_interim_meeting(group=group,date=date,timezone=tz)
time = meeting.tz().localize(
datetime.datetime.combine(date, datetime.time(9))
)
session = SessionFactory(meeting=meeting, group=group,
attendees=10,
requested_duration=datetime.timedelta(minutes=20),
@ -102,24 +104,37 @@ def make_meeting_test_data(meeting=None, create_interims=False):
# slots
session_date = meeting.date + datetime.timedelta(days=1)
tz = meeting.tz()
slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
time=datetime.datetime.combine(session_date, datetime.time(9, 30)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(9, 30))
))
slot2 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
time=datetime.datetime.combine(session_date, datetime.time(10, 50)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(10, 50))
))
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", location=breakfast_room,
duration=datetime.timedelta(minutes=90),
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(7,0))
))
reg_slot = TimeSlot.objects.create(meeting=meeting, type_id="reg", location=reg_room,
duration=datetime.timedelta(minutes=480),
time=datetime.datetime.combine(session_date, datetime.time(9,0)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(9,0))
))
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
duration=datetime.timedelta(minutes=90),
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(7,0))
))
plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
duration=datetime.timedelta(minutes=60),
time=datetime.datetime.combine(session_date, datetime.time(11,0)))
time=tz.localize(
datetime.datetime.combine(session_date, datetime.time(11,0))
))
# mars WG
mars = Group.objects.get(acronym='mars')
mars_session = SessionFactory(meeting=meeting, group=mars,
@ -213,7 +228,7 @@ def make_meeting_test_data(meeting=None, create_interims=False):
return meeting
def make_interim_test_data():
def make_interim_test_data(meeting_tz='UTC'):
date = datetime.date.today() + datetime.timedelta(days=365)
date2 = datetime.date.today() + datetime.timedelta(days=1000)
PersonFactory(user__username='plain')
@ -225,10 +240,10 @@ def make_interim_test_data():
RoleFactory(group=mars,person__user__username='marschairman',name_id='chair')
RoleFactory(group=ames,person__user__username='ameschairman',name_id='chair')
make_interim_meeting(group=mars,date=date,status='sched')
make_interim_meeting(group=mars,date=date2,status='apprw')
make_interim_meeting(group=ames,date=date,status='canceled')
make_interim_meeting(group=ames,date=date2,status='apprw')
make_interim_meeting(group=mars,date=date,status='sched',tz=meeting_tz)
make_interim_meeting(group=mars,date=date2,status='apprw',tz=meeting_tz)
make_interim_meeting(group=ames,date=date,status='canceled',tz=meeting_tz)
make_interim_meeting(group=ames,date=date2,status='apprw',tz=meeting_tz)
return

View file

@ -36,6 +36,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.utils.test_utils import assert_ical_response_is_valid
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
presence_of_element_child_by_css_selector )
from ietf.utils.timezone import datetime_today
if selenium_enabled():
from selenium.webdriver.common.action_chains import ActionChains
@ -1434,13 +1435,16 @@ class AgendaTests(IetfSeleniumTestCase):
# for others (break, reg, other):
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug>
meeting_number = components[1]
start_time = datetime.datetime(
year=int(components[2]),
month=int(components[3]),
day=int(components[4]),
hour=int(components[6][0:2]),
minute=int(components[6][2:4]),
start_time = pytz.utc.localize(
datetime.datetime(
year=int(components[2]),
month=int(components[3]),
day=int(components[4]),
hour=int(components[6][0:2]),
minute=int(components[6][2:4]),
)
)
# If labeled as plenary, it's plenary...
if components[7] == '1plenary':
session_type = 'plenary'
@ -1904,10 +1908,9 @@ 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 = pytz.timezone('UTC').localize(
start_time_utc = pytz.utc.localize(
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
)
start_time_local = start_time_utc.astimezone(pytz.timezone(self.meeting.time_zone))
daytime_session = SessionFactory(
meeting=self.meeting,
@ -1916,7 +1919,7 @@ class WeekviewTests(IetfSeleniumTestCase):
)
daytime_timeslot = TimeSlotFactory(
meeting=self.meeting,
time=start_time_local.replace(tzinfo=None), # drop timezone for Django
time=start_time_utc,
duration=duration,
)
daytime_session.timeslotassignments.create(timeslot=daytime_timeslot, schedule=self.meeting.schedule)
@ -1929,11 +1932,12 @@ class WeekviewTests(IetfSeleniumTestCase):
)
overnight_timeslot = TimeSlotFactory(
meeting=self.meeting,
time=datetime.datetime.combine(self.meeting.date, datetime.time(23,0)),
time=self.meeting.tz().localize(
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
),
duration=duration,
)
overnight_session.timeslotassignments.create(timeslot=overnight_timeslot, schedule=self.meeting.schedule)
# Check assumptions about events overlapping midnight
self.assertEqual(daytime_timeslot.local_start_time().day,
daytime_timeslot.local_end_time().day,
@ -2191,7 +2195,7 @@ class InterimTests(IetfSeleniumTestCase):
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
schedule__in=expected_schedules,
session__in=expected_interim_sessions,
timeslot__time__gte=datetime.date.today(),
timeslot__time__gte=datetime_today(),
))
# The UID formats should match those in the upcoming.ics template
expected_uids = [
@ -2688,13 +2692,17 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
delete: [TimeSlot] = TimeSlotFactory.create_batch(
2,
meeting=self.meeting,
time=datetime.datetime.combine(delete_day, delete_time),
time=self.meeting.tz().localize(
datetime.datetime.combine(delete_day, delete_time)
),
duration=duration)
keep: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
time=datetime.datetime.combine(day, time),
time=self.meeting.tz().localize(
datetime.datetime.combine(day, time)
),
duration=duration
)
for (day, time) in (
@ -2711,7 +2719,9 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
'[data-col-id="{}T{}-{}"]'.format(
delete_day.isoformat(),
delete_time.strftime('%H:%M'),
(datetime.datetime.combine(delete_day, delete_time) + duration).strftime(
self.meeting.tz().localize(
datetime.datetime.combine(delete_day, delete_time) + duration
).strftime(
'%H:%M'
))
)
@ -2733,14 +2743,18 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
delete: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
time=datetime.datetime.combine(delete_day, time),
time=self.meeting.tz().localize(
datetime.datetime.combine(delete_day, time)
),
) for time in times
]
keep: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
time=datetime.datetime.combine(day, time),
time=self.meeting.tz().localize(
datetime.datetime.combine(day, time)
),
) for day in other_days for time in times
]

View file

@ -56,16 +56,16 @@ class MeetingTests(TestCase):
def test_vtimezone(self):
# normal time zone that should have a zoneinfo file
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
vtz = meeting.vtimezone()
self.assertIsNotNone(vtz)
self.assertGreater(len(vtz), 0)
# time zone that does not have a zoneinfo file should return None
meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone')
meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone', populate_schedule=False)
vtz = meeting.vtimezone()
self.assertIsNone(vtz)
# ioerror trying to read zoneinfo should return None
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
with patch('ietf.meeting.models.io.open', side_effect=IOError):
vtz = meeting.vtimezone()
self.assertIsNone(vtz)

View file

@ -1,6 +1,7 @@
# Copyright The IETF Trust 2020, All Rights Reserved
import calendar
import datetime
import pytz
from io import StringIO
from django.core.management.base import CommandError
@ -36,9 +37,11 @@ class ScheduleGeneratorTest(TestCase):
t = TimeSlotFactory(
meeting=self.meeting,
location=room,
time=datetime.datetime.combine(
self.meeting.date + datetime.timedelta(days=day),
datetime.time(hour, 0),
time=self.meeting.tz().localize(
datetime.datetime.combine(
self.meeting.date + datetime.timedelta(days=day),
datetime.time(hour, 0),
)
),
duration=datetime.timedelta(minutes=60),
)
@ -306,8 +309,11 @@ class ScheduleGeneratorTest(TestCase):
add_to_schedule=False
)
# use a timeslot not on Sunday
meeting_date = pytz.utc.localize(
datetime.datetime.combine(self.meeting.get_meeting_date(1), datetime.time())
)
ts = self.meeting.timeslot_set.filter(
time__gt=self.meeting.date + datetime.timedelta(days=1),
time__gt=meeting_date,
location__capacity__lt=base_reg_session.attendees,
).order_by(
'time'

View file

@ -52,6 +52,7 @@ from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
from ietf.utils.text import xslugify
from ietf.utils.timezone import date_today, time_now
from ietf.person.factories import PersonFactory
from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory
@ -155,7 +156,7 @@ class MeetingTests(BaseMeetingTestCase):
#
self.write_materials_files(meeting, session)
#
future_year = datetime.date.today().year+1
future_year = date_today().year+1
future_num = (future_year-1984)*3 # valid for the mid-year meeting
future_meeting = Meeting.objects.create(date=datetime.date(future_year, 7, 22), number=future_num, type_id='ietf',
city="Panama City", country="PA", time_zone='America/Panama')
@ -228,7 +229,10 @@ class MeetingTests(BaseMeetingTestCase):
'Time zone indicator should be in nav sidebar')
# plain
time_interval = r"%s<span.*/span>-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0"))
time_interval = r"{}<span.*/span>-{}".format(
slot.time.astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
slot.end_time().astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
)
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
self.assertEqual(r.status_code, 200)
@ -430,7 +434,9 @@ class MeetingTests(BaseMeetingTestCase):
session_date = meeting.date + datetime.timedelta(days=1)
slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
time=datetime.datetime.combine(session_date, datetime.time(13, 30)))
time=meeting.tz().localize(
datetime.datetime.combine(session_date, datetime.time(13, 30))
))
SchedTimeSessAssignment.objects.create(timeslot=slot3, session=venus_session, schedule=meeting.schedule)
url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number))
r = self.client.get(url)
@ -801,7 +807,12 @@ class MeetingTests(BaseMeetingTestCase):
a1 = s1.official_timeslotassignment()
t1 = a1.timeslot
# Create an extra session
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
t2 = TimeSlotFactory.create(
meeting=meeting,
time=meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(11, 30))
)
)
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
#
@ -811,16 +822,16 @@ class MeetingTests(BaseMeetingTestCase):
r,
expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=2)
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
#
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
r = self.client.get(url)
assert_ical_response_is_valid(self, r,
expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=1)
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
self.assertNotContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
def test_meeting_agenda_has_static_ical_links(self):
"""Links to the agenda_ical view must appear on the agenda page
@ -960,7 +971,7 @@ class MeetingTests(BaseMeetingTestCase):
url = urlreverse('ietf.meeting.views.current_materials')
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
MeetingFactory(type_id='ietf', date=datetime.date.today())
MeetingFactory(type_id='ietf', date=date_today())
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
@ -1105,7 +1116,9 @@ class EditMeetingScheduleTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=room,
time=datetime.datetime.combine(meeting.date, time),
time=meeting.tz().localize(
datetime.datetime.combine(meeting.date, time)
),
duration=datetime.timedelta(minutes=duration),
)
@ -1189,7 +1202,11 @@ class EditMeetingScheduleTests(TestCase):
]
# Set up different sets of timeslots
t0 = datetime.datetime.combine(meeting.date, datetime.time(11, 0))
# Work with t0 in UTC for arithmetic. This does not change the results but is cleaner if someone looks
# at intermediate results which may be misleading until passed through tz.normalize().
t0 = meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(11, 0))
).astimezone(pytz.utc)
dur = datetime.timedelta(hours=2)
for room in room_groups[0]:
TimeSlotFactory(meeting=meeting, location=room, duration=dur, time=t0)
@ -1284,7 +1301,7 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past
right_now = self._right_now_in(meeting.time_zone)
right_now = self._right_now_in(meeting.tz())
for room in room_groups[0]:
ts = room.timeslot_set.last()
ts.time = right_now - datetime.timedelta(minutes=5)
@ -1466,12 +1483,18 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past
right_now = self._right_now_in(meeting.time_zone)
yesterday = (right_now - datetime.timedelta(days=1)).date()
day_before = (right_now - datetime.timedelta(days=2)).date()
right_now = self._right_now_in(meeting.tz())
yesterday = right_now.date() - datetime.timedelta(days=1)
day_before = right_now.date() - datetime.timedelta(days=2)
for room in room_groups[0]:
ts = room.timeslot_set.last()
ts.time = datetime.datetime.combine(yesterday, ts.time.time())
# Calculation keeps local clock time, shifted to a different day.
ts.time = meeting.tz().localize(
datetime.datetime.combine(
yesterday,
ts.time.astimezone(meeting.tz()).time()
),
)
ts.save()
# timeslot_set is ordered by -time, so check that we know which is past/future
self.assertTrue(room_groups[0][0].timeslot_set.last().time < right_now)
@ -1505,7 +1528,12 @@ class EditMeetingScheduleTests(TestCase):
# now with both in the past
for room in room_groups[0]:
ts = room.timeslot_set.first()
ts.time = datetime.datetime.combine(day_before, ts.time.time())
ts.time = meeting.tz().localize(
datetime.datetime.combine(
day_before,
ts.time.astimezone(meeting.tz()).time(),
)
)
ts.save()
past_slots = room_groups[0][0].timeslot_set.filter(time__lt=right_now)
self.assertEqual(len(past_slots), 2, 'Need two timeslots in the past!')
@ -1528,8 +1556,8 @@ class EditMeetingScheduleTests(TestCase):
self.fail('Response was not valid JSON: {}'.format(err))
@staticmethod
def _right_now_in(tzname):
right_now = timezone.now().astimezone(pytz.timezone(tzname))
def _right_now_in(tzinfo):
right_now = timezone.now().astimezone(tzinfo)
if not settings.USE_TZ:
right_now = right_now.replace(tzinfo=None)
return right_now
@ -1541,7 +1569,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@ -1601,7 +1629,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@ -1736,7 +1764,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@ -1857,7 +1885,7 @@ class EditTimeslotsTests(TestCase):
return MeetingFactory(
type_id='ietf',
number=number,
date=timezone.now() + datetime.timedelta(days=10),
date=date_today() + datetime.timedelta(days=10),
populate_schedule=False,
)
@ -1889,7 +1917,8 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_bare_meeting(number=number)
RoomFactory.create_batch(8, meeting=meeting)
self.create_initial_schedule(meeting)
return meeting
# retrieve meeting from DB so it goes through Django's processing
return Meeting.objects.get(pk=meeting.pk)
def test_view_permissions(self):
"""Only the secretary should be able to edit timeslots"""
@ -2058,7 +2087,7 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_meeting()
# add some timeslots
times = [datetime.time(hour=h) for h in (11, 14)]
days = [meeting.get_meeting_date(ii).date() for ii in range(meeting.days)]
days = [meeting.get_meeting_date(ii) for ii in range(meeting.days)]
timeslots = []
duration = datetime.timedelta(minutes=90)
@ -2068,7 +2097,7 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=room,
time=datetime.datetime.combine(day, t),
time=meeting.tz().localize(datetime.datetime.combine(day, t)),
duration=duration,
)
for t in times
@ -2149,17 +2178,21 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=meeting.room_set.first(),
time=datetime.datetime.combine(
meeting.get_meeting_date(day).date(),
datetime.time(hour=11)
time=meeting.tz().localize(
datetime.datetime.combine(
meeting.get_meeting_date(day),
datetime.time(hour=11),
)
),
)
TimeSlotFactory(
meeting=meeting,
location=meeting.room_set.first(),
time=datetime.datetime.combine(
meeting.get_meeting_date(day).date(),
datetime.time(hour=14)
time=meeting.tz().localize(
datetime.datetime.combine(
meeting.get_meeting_date(day),
datetime.time(hour=14),
)
),
)
@ -2258,10 +2291,8 @@ class EditTimeslotsTests(TestCase):
name_before = 'Name Classic (tm)'
type_before = 'regular'
time_before = datetime.datetime.combine(
meeting.date,
datetime.time(hour=10),
)
time_utc = pytz.utc.localize(datetime.datetime.combine(meeting.date, datetime.time(hour=10)))
time_before = time_utc.astimezone(meeting.tz())
duration_before = datetime.timedelta(minutes=60)
show_location_before = True
location_before = meeting.room_set.first()
@ -2278,7 +2309,7 @@ class EditTimeslotsTests(TestCase):
self.login()
name_after = 'New Name (tm)'
type_after = 'plenary'
time_after = time_before + datetime.timedelta(days=1, hours=2)
time_after = (time_utc + datetime.timedelta(days=1, hours=2)).astimezone(meeting.tz())
duration_after = duration_before * 2
show_location_after = False
location_after = meeting.room_set.last()
@ -2458,8 +2489,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(str(ts.time.date().toordinal()), post_data['days'])
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.local_start_time().date().toordinal()), post_data['days'])
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations'])
@ -2468,7 +2499,7 @@ class EditTimeslotsTests(TestCase):
"""Creating a single timeslot outside the official meeting days should work"""
meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
other_date = meeting.get_meeting_date(-7).date()
other_date = meeting.get_meeting_date(-7)
post_data = dict(
name='some name',
type='regular',
@ -2491,8 +2522,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(ts.time.date(), other_date)
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
self.assertEqual(ts.local_start_time().date(), other_date)
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations'])
@ -2706,8 +2737,8 @@ class EditTimeslotsTests(TestCase):
"""Creating multiple timeslots should work"""
meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
days = [meeting.get_meeting_date(n).date() for n in range(meeting.days)]
other_date = meeting.get_meeting_date(-1).date() # date before start of meeting
days = [meeting.get_meeting_date(n) for n in range(meeting.days)]
other_date = meeting.get_meeting_date(-1) # date before start of meeting
self.assertNotIn(other_date, days)
locations = meeting.room_set.all()
post_data = dict(
@ -2737,10 +2768,10 @@ class EditTimeslotsTests(TestCase):
for ts in meeting.timeslot_set.exclude(pk__in=timeslots_before):
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
self.assertIn(ts.time.date(), days)
self.assertIn(ts.local_start_time().date(), days)
self.assertIn(ts.location, locations)
self.assertIn((ts.time.date(), ts.location), day_locs,
'Duplicated day / location found')
@ -3288,7 +3319,9 @@ class EditTests(TestCase):
room = Room.objects.get(meeting=meeting, session_types='regular')
base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=50),
time=datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30)))
time=meeting.tz().localize(
datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30))
))
timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time'))
@ -3510,7 +3543,12 @@ class EditTests(TestCase):
self.assertIn("#scroll=1234", r['Location'])
test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing")
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(8, 30)))
self.assertEqual(
test_timeslot.time,
meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(8, 30))
),
)
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1, minutes=30))
self.assertEqual(test_timeslot.location_id, break_room.pk)
self.assertEqual(test_timeslot.show_location, True)
@ -3552,7 +3590,12 @@ class EditTests(TestCase):
})
self.assertNoFormPostErrors(r)
test_timeslot.refresh_from_db()
self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(9, 30)))
self.assertEqual(
test_timeslot.time,
meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(9, 30))
),
)
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1))
self.assertEqual(test_timeslot.location_id, breakfast_room.pk)
self.assertEqual(test_timeslot.show_location, False)
@ -4329,12 +4372,10 @@ class InterimTests(TestCase):
self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True, base_session=True)
def do_interim_send_announcement_test(self, base_session=False, extra_session=False, canceled_session=False):
make_interim_test_data()
make_interim_test_data(meeting_tz='America/Los_Angeles')
session = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first()
meeting = session.meeting
meeting.time_zone = 'America/Los_Angeles'
meeting.save()
if base_session:
base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False)
@ -4679,9 +4720,9 @@ class InterimTests(TestCase):
def do_interim_request_single_virtual(self, emails_expected):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30)
time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
date = date_today() + datetime.timedelta(days=30)
time = time_now().replace(microsecond=0,second=0)
dt = pytz.utc.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3)
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
@ -4750,13 +4791,14 @@ class InterimTests(TestCase):
def test_interim_request_single_in_person(self):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30)
time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
date = date_today() + datetime.timedelta(days=30)
time = time_now().replace(microsecond=0,second=0)
time_zone = 'America/Los_Angeles'
tz = pytz.timezone(time_zone)
dt = tz.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3)
city = 'San Francisco'
country = 'US'
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@ -4797,16 +4839,17 @@ class InterimTests(TestCase):
def test_interim_request_multi_day(self):
make_meeting_test_data()
date = datetime.date.today() + datetime.timedelta(days=30)
date = date_today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=1)
time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
dt2 = datetime.datetime.combine(date2, time)
time = time_now().replace(microsecond=0,second=0)
time_zone = 'America/Los_Angeles'
tz = pytz.timezone(time_zone)
dt = tz.localize(datetime.datetime.combine(date, time))
dt2 = tz.localize(datetime.datetime.combine(date2, time))
duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars')
city = 'San Francisco'
country = 'US'
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@ -4923,7 +4966,7 @@ class InterimTests(TestCase):
def test_interim_request_series(self):
make_meeting_test_data()
meeting_count_before = Meeting.objects.filter(type='interim').count()
date = datetime.date.today() + datetime.timedelta(days=30)
date = date_today() + datetime.timedelta(days=30)
if (date.month, date.day) == (12, 31):
# Avoid date and date2 in separate years
# (otherwise the test will fail if run on December 1st)
@ -4933,14 +4976,15 @@ class InterimTests(TestCase):
if date.year != date2.year:
date += datetime.timedelta(days=1)
date2 += datetime.timedelta(days=1)
time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
dt2 = datetime.datetime.combine(date2, time)
time = time_now().replace(microsecond=0,second=0)
time_zone = 'America/Los_Angeles'
tz = pytz.timezone(time_zone)
dt = tz.localize(datetime.datetime.combine(date, time))
dt2 = tz.localize(datetime.datetime.combine(date2, time))
duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars')
city = ''
country = ''
time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@ -5082,14 +5126,14 @@ class InterimTests(TestCase):
self.assertFalse(can_manage_group(user=user,group=group))
def test_interim_request_details(self):
make_interim_test_data()
make_interim_test_data(meeting_tz='America/Chicago')
meeting = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
start_time = meeting.session_set.first().official_timeslotassignment().timeslot.time.strftime('%H:%M')
start_time = meeting.session_set.first().official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M')
utc_start_time = meeting.session_set.first().official_timeslotassignment().timeslot.utc_start_time().strftime('%H:%M')
self.assertIn(start_time, unicontent(r))
self.assertIn(utc_start_time, unicontent(r))
@ -5630,7 +5674,11 @@ class InterimTests(TestCase):
a1 = s1.official_timeslotassignment()
t1 = a1.timeslot
# Create an extra session
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
t2 = TimeSlotFactory.create(
meeting=meeting,
time=meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(11, 30))
))
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
#
@ -5640,8 +5688,8 @@ class InterimTests(TestCase):
self.assertContains(r, 'BEGIN:VEVENT')
self.assertEqual(r.content.count(b'UID'), 2)
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, 'END:VEVENT')
#
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
@ -7422,7 +7470,7 @@ class ProceedingsTests(BaseMeetingTestCase):
def test_proceedings_no_agenda(self):
# Meeting number must be larger than the last special-cased proceedings (currently 96)
meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=datetime.date.today(), number='100')
meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=date_today(), number='100')
url = urlreverse('ietf.meeting.views.proceedings')
r = self.client.get(url)
self.assertRedirects(r, urlreverse('ietf.meeting.views.materials'))
@ -7531,7 +7579,7 @@ class ProceedingsTests(BaseMeetingTestCase):
"""Generate a meeting for proceedings material test"""
# meeting number 123 avoids various legacy cases that affect these tests
# (as of Aug 2021, anything above 96 is probably ok)
return MeetingFactory(type_id='ietf', number='123', date=datetime.date.today())
return MeetingFactory(type_id='ietf', number='123', date=date_today())
def _secretary_only_permission_test(self, url, include_post=True):
self.client.logout()

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import datetime
import itertools
import pytz
import requests
import subprocess
@ -34,13 +35,17 @@ def session_time_for_sorting(session, use_meeting_date):
if official_timeslot:
return official_timeslot.time
elif use_meeting_date and session.meeting.date:
return datetime.datetime.combine(session.meeting.date, datetime.time.min)
return session.meeting.tz().localize(
datetime.datetime.combine(session.meeting.date, datetime.time.min)
)
else:
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
if first_event:
return first_event.time
else:
return datetime.datetime.min
# n.b. cannot interpret this in timezones west of UTC. That is not expected to be necessary,
# but could probably safely add a day to the minimum datetime to make that possible.
return pytz.utc.localize(datetime.datetime.min)
def session_requested_by(session):
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
@ -159,7 +164,12 @@ def create_proceedings_templates(meeting):
def finalize(meeting):
end_date = meeting.end_date()
end_time = datetime.datetime.combine(end_date, datetime.datetime.min.time())+datetime.timedelta(days=1)
end_time = meeting.tz().localize(
datetime.datetime.combine(
end_date,
datetime.time.min,
)
).astimezone(pytz.utc) + datetime.timedelta(days=1)
for session in meeting.session_set.all():
for sp in session.sessionpresentation_set.filter(document__type='draft',rev=None):
rev_before_end = [e for e in sp.document.docevent_set.filter(newrevisiondocevent__isnull=False).order_by('-time') if e.time <= end_time ]
@ -323,7 +333,9 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
# synthesize AD constraints - we can treat them as a special kind of 'bethere'
responsible_ad_for_group = {}
session_groups = set(s.group for s in sessions if s.group and s.group.parent and s.group.parent.type_id == 'area')
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
meeting_time = meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
)
# dig up historic AD names
for group_id, history_time, pk in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'pk').order_by('rolehistory__group__time'):

View file

@ -96,6 +96,7 @@ from ietf.utils.pipe import pipe
from ietf.utils.pdf import pdf_pages
from ietf.utils.response import permission_denied
from ietf.utils.text import xslugify
from ietf.utils.timezone import datetime_today, date_today
from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
@ -127,7 +128,7 @@ def materials(request, num=None):
begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date()
now = datetime.date.today()
now = date_today()
old = timezone.now() - datetime.timedelta(days=1)
if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET:
pass
@ -142,7 +143,7 @@ def materials(request, num=None):
'cor_cut_off_date': cor_cut_off_date
})
past_cutoff_date = datetime.date.today() > meeting.get_submission_correction_date()
past_cutoff_date = date_today() > meeting.get_submission_correction_date()
schedule = get_schedule(meeting, None)
@ -192,7 +193,7 @@ def materials(request, num=None):
})
def current_materials(request):
today = datetime.date.today()
today = date_today()
meetings = Meeting.objects.exclude(number__startswith='interim-').filter(date__lte=today).order_by('-date')
if meetings:
return redirect(materials, meetings[0].number)
@ -281,60 +282,63 @@ def materials_editable_groups(request, num=None):
def edit_timeslots(request, num=None):
meeting = get_meeting(num)
timezone.activate(meeting.tz())
try:
if request.method == 'POST':
# handle AJAX requests
action = request.POST.get('action')
if action == 'delete':
# delete a timeslot
# Parameters:
# slot_id: comma-separated list of TimeSlot PKs to delete
slot_id = request.POST.get('slot_id')
if slot_id is None:
return HttpResponseBadRequest('missing slot_id')
slot_ids = [id.strip() for id in slot_id.split(',')]
try:
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
except ValueError:
return HttpResponseBadRequest('invalid slot_id specification')
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
if len(missing_ids) != 0:
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
meeting.number,
', '.join(sorted(missing_ids))
))
timeslots.delete()
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
else:
return HttpResponseBadRequest('unknown action')
if request.method == 'POST':
# handle AJAX requests
action = request.POST.get('action')
if action == 'delete':
# delete a timeslot
# Parameters:
# slot_id: comma-separated list of TimeSlot PKs to delete
slot_id = request.POST.get('slot_id')
if slot_id is None:
return HttpResponseBadRequest('missing slot_id')
slot_ids = [id.strip() for id in slot_id.split(',')]
try:
timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
except ValueError:
return HttpResponseBadRequest('invalid slot_id specification')
missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
if len(missing_ids) != 0:
return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
meeting.number,
', '.join(sorted(missing_ids))
))
timeslots.delete()
return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
else:
return HttpResponseBadRequest('unknown action')
# Labels here differ from those in the build_timeslices() method. The labels here are
# relative to the table: time_slices are the row headings (ie, days), date_slices are
# the column headings (i.e., time intervals), and slots are the per-day list of timeslots
# (with only one timeslot per unique time/duration)
time_slices, date_slices, slots = meeting.build_timeslices()
# Labels here differ from those in the build_timeslices() method. The labels here are
# relative to the table: time_slices are the row headings (ie, days), date_slices are
# the column headings (i.e., time intervals), and slots are the per-day list of timeslots
# (with only one timeslot per unique time/duration)
time_slices, date_slices, slots = meeting.build_timeslices()
ts_list = deque()
rooms = meeting.room_set.order_by("capacity","name","id")
for room in rooms:
for day in time_slices:
for slice in date_slices[day]:
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
ts_list = deque()
rooms = meeting.room_set.order_by("capacity","name","id")
for room in rooms:
for day in time_slices:
for slice in date_slices[day]:
ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
# Grab these in one query each to identify sessions that are in use and should be handled with care
ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
# Grab these in one query each to identify sessions that are in use and should be handled with care
ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
return render(request, "meeting/timeslot_edit.html",
{"rooms":rooms,
"time_slices":time_slices,
"slot_slices": slots,
"date_slices":date_slices,
"meeting":meeting,
"ts_list":ts_list,
"ts_with_official_assignments": ts_with_official_assignments,
"ts_with_any_assignments": ts_with_any_assignments,
})
return render(request, "meeting/timeslot_edit.html",
{"rooms":rooms,
"time_slices":time_slices,
"slot_slices": slots,
"date_slices":date_slices,
"meeting":meeting,
"ts_list":ts_list,
"ts_with_official_assignments": ts_with_official_assignments,
"ts_with_any_assignments": ts_with_any_assignments,
})
finally:
timezone.deactivate()
class NewScheduleForm(forms.ModelForm):
class Meta:
@ -1151,7 +1155,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
meeting=meeting,
type=c['type'],
name=c['name'],
time=datetime.datetime.combine(c['day'], c['time']),
time=meeting.tz().localize(datetime.datetime.combine(c['day'], c['time'])),
duration=c['duration'],
location=c['location'],
show_location=c['show_location'],
@ -1200,7 +1204,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
timeslot.type = c['type']
timeslot.name = c['name']
timeslot.time = datetime.datetime.combine(c['day'], c['time'])
timeslot.time = meeting.tz().localize(datetime.datetime.combine(c['day'], c['time']))
timeslot.duration = c['duration']
timeslot.location = c['location']
timeslot.show_location = c['show_location']
@ -1287,8 +1291,9 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
for t in timeslot_qs:
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
min_time = min([t.time.time() for t in timeslot_qs] + [datetime.time(8)])
max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)])
# Calculate full time range for display in meeting-local time, always showing at least 8am to 10pm
min_time = min([t.local_start_time().time() for t in timeslot_qs] + [datetime.time(8)])
max_time = max([t.local_end_time().time() for t in timeslot_qs] + [datetime.time(22)])
min_max_delta = datetime.datetime.combine(meeting.date, max_time) - datetime.datetime.combine(meeting.date, min_time)
day_grid = []
@ -1310,7 +1315,14 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
if s:
t.assigned_sessions.append(s)
t.left_offset = 100.0 * (t.time - datetime.datetime.combine(t.time.date(), min_time)) / min_max_delta
local_start_dt = t.local_start_time()
local_min_dt = local_start_dt.replace(
hour=min_time.hour,
minute=min_time.minute,
second=min_time.second,
microsecond=min_time.microsecond,
)
t.left_offset = 100.0 * (local_start_dt - local_min_dt) / min_max_delta
t.layout_width = min(100.0 * t.duration / min_max_delta, 100 - t.left_offset)
ts.append(t)
@ -1553,19 +1565,29 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
rendered_page = render(request, "meeting/"+base+ext, {
"personalize": False,
"schedule": schedule,
"filtered_assignments": filtered_assignments,
"updated": updated,
"filter_categories": filter_organizer.get_filter_categories(),
"non_area_keywords": filter_organizer.get_non_area_keywords(),
"now": timezone.now().astimezone(pytz.utc),
"timezone": meeting.time_zone,
"is_current_meeting": is_current_meeting,
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
"cache_time": 150 if is_current_meeting else 3600,
}, content_type=mimetype[ext])
display_timezone = 'UTC' if utc else meeting.time_zone
timezone.activate(display_timezone)
try:
rendered_page = render(
request,
"meeting/" + base + ext,
{
"personalize": False,
"schedule": schedule,
"filtered_assignments": filtered_assignments,
"updated": updated,
"filter_categories": filter_organizer.get_filter_categories(),
"non_area_keywords": filter_organizer.get_non_area_keywords(),
"now": timezone.now().astimezone(pytz.utc),
"display_timezone": display_timezone,
"is_current_meeting": is_current_meeting,
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
"cache_time": 150 if is_current_meeting else 3600,
},
content_type=mimetype[ext],
)
finally:
timezone.deactivate()
return rendered_page
@ -1917,7 +1939,7 @@ def agenda_personalize(request, num):
'filtered_assignments': filtered_assignments,
'filter_categories': filter_organizer.get_filter_categories(),
'non_area_labels': filter_organizer.get_non_area_keywords(),
'timezone': meeting.time_zone,
'display_timezone': meeting.time_zone,
'is_current_meeting': is_current_meeting,
'cache_time': 150 if is_current_meeting else 3600,
}
@ -2305,16 +2327,14 @@ def agenda_json(request, num=None):
meetinfo.sort(key=lambda x: x['modified'],reverse=True)
last_modified = meetinfo and meetinfo[0]['modified']
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
for obj in meetinfo:
obj['modified'] = tz.localize(obj['modified']).astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
obj['modified'] = obj['modified'].astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
data = {"%s"%num: meetinfo}
response = HttpResponse(json.dumps(data, indent=2, sort_keys=True), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET)
if last_modified:
last_modified = tz.localize(last_modified).astimezone(pytz.utc)
last_modified = last_modified.astimezone(pytz.utc)
response['Last-Modified'] = format_date_time(timegm(last_modified.timetuple()))
return response
@ -2386,7 +2406,9 @@ def session_details(request, num, acronym):
# Find the time of the meeting, so that we can look back historically
# for what the group was called at the time.
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
meeting_time = meeting.tz().localize(
datetime.datetime.combine(meeting.date, datetime.time())
)
groups = list(set([ s.group for s in sessions ]))
group_replacements = find_history_replacements_active_at(groups, meeting_time)
@ -2453,7 +2475,7 @@ def session_details(request, num, acronym):
'is_materials_manager' : session.group.has_role(request.user, session.group.features.matman_roles),
'can_manage_materials' : can_manage,
'can_view_request': can_view_request,
'thisweek': datetime.date.today()-datetime.timedelta(days=7),
'thisweek': datetime_today()-datetime.timedelta(days=7),
'now': timezone.now(),
'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
})
@ -3552,7 +3574,7 @@ def past(request):
def upcoming(request):
'''List of upcoming meetings'''
today = datetime.date.today()
today = datetime_today()
# 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))
@ -3616,7 +3638,7 @@ def upcoming(request):
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
'now': timezone.now(),
'use_codimd': True if datetime.date.today()>=settings.MEETING_USES_CODIMD_DATE else False,
'use_codimd': (date_today() >= settings.MEETING_USES_CODIMD_DATE),
})
@ -3630,7 +3652,7 @@ def upcoming_ical(request):
except ValueError as e:
return HttpResponseBadRequest(str(e))
today = datetime.date.today()
today = datetime_today()
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).prefetch_related('schedule').order_by('date'))
@ -3680,7 +3702,7 @@ def upcoming_ical(request):
def upcoming_json(request):
'''Return Upcoming meetings in json format'''
today = datetime.date.today()
today = datetime_today()
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).order_by('date'))
@ -3729,7 +3751,7 @@ def proceedings(request, num=None):
begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date()
now = datetime.date.today()
now = date_today()
schedule = get_schedule(meeting, None)
sessions = add_event_info_to_session_qs(
@ -4026,7 +4048,7 @@ def important_dates(request, num=None, output_format=None):
base_num = int(meeting.number)
user = request.user
today = datetime.date.today()
today = datetime_today()
meetings = []
if meeting.show_important_dates or meeting.date < today:
meetings.append(meeting)
@ -4078,23 +4100,27 @@ def edit_timeslot(request, num, slot_id):
meeting = get_object_or_404(Meeting, number=num)
if timeslot.meeting != meeting:
raise Http404()
if request.method == 'POST':
form = TimeSlotEditForm(instance=timeslot, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
else:
form = TimeSlotEditForm(instance=timeslot)
timezone.activate(meeting.tz()) # specifies current_timezone used for rendering and form handling
try:
if request.method == 'POST':
form = TimeSlotEditForm(instance=timeslot, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
else:
form = TimeSlotEditForm(instance=timeslot)
sessions = timeslot.sessions.filter(
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
sessions = timeslot.sessions.filter(
timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
return render(
request,
'meeting/edit_timeslot.html',
{'timeslot': timeslot, 'form': form, 'sessions': sessions},
status=400 if form.errors else 200,
)
return render(
request,
'meeting/edit_timeslot.html',
{'timeslot': timeslot, 'form': form, 'sessions': sessions},
status=400 if form.errors else 200,
)
finally:
timezone.deactivate()
@role_required('Secretariat')
@ -4105,7 +4131,7 @@ def create_timeslot(request, num):
if form.is_valid():
bulk_create_timeslots(
meeting,
[datetime.datetime.combine(day, form.cleaned_data['time'])
[meeting.tz().localize(datetime.datetime.combine(day, form.cleaned_data['time']))
for day in form.cleaned_data.get('days', [])],
form.cleaned_data['locations'],
dict(

View file

@ -15998,7 +15998,7 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2022-07-13T00:09:29.108",
"time": "2022-07-13T00:09:29.108Z",
"used": true,
"version": "xym 0.5"
},
@ -16009,7 +16009,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2022-07-13T00:09:29.475",
"time": "2022-07-13T00:09:29.475Z",
"used": true,
"version": "pyang 2.5.3"
},
@ -16020,7 +16020,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2022-07-13T00:09:29.497",
"time": "2022-07-13T00:09:29.497Z",
"used": true,
"version": "yanglint SO 1.9.2"
},
@ -16031,7 +16031,7 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2022-07-13T00:09:30.513",
"time": "2022-07-13T00:09:30.513Z",
"used": true,
"version": "xml2rfc 3.13.0"
},

View file

@ -9,6 +9,7 @@ This module contains all the functions for generating static proceedings pages
'''
import datetime
import os
import pytz
import re
import subprocess
from urllib.parse import urlencode
@ -201,17 +202,22 @@ def send_audio_import_warning(unmatched_files):
# End Recording Functions
# -------------------------------------------------
def get_progress_stats(sdate,edate):
def get_progress_stats(sdate, edate):
'''
This function takes a date range and produces a dictionary of statistics / objects for
use in a progress report. Generally the end date will be the date of the last meeting
and the start date will be the date of the meeting before that.
Data between midnight UTC on the specified dates are included in the stats.
'''
sdatetime = pytz.utc.localize(datetime.datetime.combine(sdate, datetime.time()))
edatetime = pytz.utc.localize(datetime.datetime.combine(edate, datetime.time()))
data = {}
data['sdate'] = sdate
data['edate'] = edate
events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lt=edate)
events = DocEvent.objects.filter(doc__type='draft', time__gte=sdatetime, time__lt=edatetime)
data['actions_count'] = events.filter(type='iesg_approved').count()
data['last_calls_count'] = events.filter(type='sent_last_call').count()
@ -226,7 +232,7 @@ def get_progress_stats(sdate,edate):
data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ]))
# Calculate Final Four Weeks stats (ffw)
ffwdate = edate - datetime.timedelta(days=28)
ffwdate = edatetime - datetime.timedelta(days=28)
ffw_new_count = events.filter(time__gte=ffwdate,newrevisiondocevent__rev='00').count()
try:
ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%')
@ -257,14 +263,14 @@ def get_progress_stats(sdate,edate):
data['new_groups'] = Group.objects.filter(
type='wg',
groupevent__changestategroupevent__state='active',
groupevent__time__gte=sdate,
groupevent__time__lt=edate)
groupevent__time__gte=sdatetime,
groupevent__time__lt=edatetime)
data['concluded_groups'] = Group.objects.filter(
type='wg',
groupevent__changestategroupevent__state='conclude',
groupevent__time__gte=sdate,
groupevent__time__lt=edate)
groupevent__time__gte=sdatetime,
groupevent__time__lt=edatetime)
return data

View file

@ -114,7 +114,7 @@ SITE_ID = 1
# to load the internationalization machinery.
USE_I18N = False
USE_TZ = False
USE_TZ = True
if SERVER_MODE == 'production':
MEDIA_ROOT = '/a/www/www6s/lib/dt/media/'
@ -1187,8 +1187,6 @@ CELERY_TIMEZONE = 'UTC'
CELERY_BROKER_URL = 'amqp://mq/'
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
CELERY_BEAT_SYNC_EVERY = 1 # update DB after every event
assert not USE_TZ, 'Drop DJANGO_CELERY_BEAT_TZ_AWARE setting once USE_TZ is True!'
DJANGO_CELERY_BEAT_TZ_AWARE = False
# Meetecho API setup: Uncomment this and provide real credentials to enable
# Meetecho conference creation for interim session requests

View file

@ -4,7 +4,7 @@
{% load static %}
{% load ietf_filters %}
{% load textfilters %}
{% load htmlfilters agenda_custom_tags %}
{% load htmlfilters agenda_custom_tags tz %}
{% block title %}
IETF {{ schedule.meeting.number }} Meeting Agenda
{% if "-utc" in request.path %}(UTC){% endif %}
@ -36,10 +36,10 @@
<a class="btn btn-sm btn-primary my-3 now-link" href="#">Jump to current session</a>
{% endif %}
<div class="d-flex">
{% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=timezone minimal=True only %}
{% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=display_timezone minimal=True only %}
</div>
<div class="small text-muted">
Showing <span class="current-tz">{{ timezone|split:"_"|join:" "|split:"/"|join:" / " }}</span> time
Showing <span class="current-tz">{{ display_timezone|split:"_"|join:" "|split:"/"|join:" / " }}</span> time
</div>
</div>
</div>
@ -63,7 +63,7 @@
{{ schedule.meeting.agenda_info_note|removetags:"h1"|safe }}
</p>
{% endif %}
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=timezone only %}
{% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=display_timezone only %}
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Filter this agenda view..." always_show=personalize %}
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting personalize=personalize only %}
<div class="input-group mb-3">
@ -249,7 +249,8 @@
</td>
{% endif %}
{% if item.slot_type.slug == 'plenary' %}
<td class="text-end">{% include "meeting/timeslot_start_end.html" %}</td>
<td class="text-end">
{% include "meeting/timeslot_start_end.html" %}</td>
<td colspan="3">
{% if item.timeslot.show_location and item.timeslot.location %}
{% location_anchor item.timeslot %}
@ -327,11 +328,11 @@
{% if item.session.rescheduled_to %}
TO
<div class="timetooltip reschedtimetooltip">
<div data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}"
data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}"
<div data-start-time="{{ item.session.rescheduled_to.time|utc|date:"U" }}"
data-end-time="{{ item.session.rescheduled_to.end_time|utc|date:"U" }}"
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %} data-weekday="1"{% endif %}>
{% if "-utc" in request.path %}
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
{{ item.session.rescheduled_to.time|utc|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|utc|date:"G:i" }}
{% else %}
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}
{% endif %}
@ -487,7 +488,7 @@
$(document).ready(function() {
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
meeting_timezone = '{{ timezone }}';
meeting_timezone = '{{ display_timezone }}';
// 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.

View file

@ -1,4 +1,4 @@
{% load humanize %}{% autoescape off %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
{% load humanize tz %}{% autoescape off %}{% timezone schedule.meeting.tz %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
PRODID:-//IETF//datatracker.ietf.org ical agenda//EN
@ -8,8 +8,8 @@ SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{% if n
{% if item.timeslot.show_location %}LOCATION:{{item.timeslot.get_location}}
{% endif %}STATUS:{{item.session.ical_status}}
CLASS:PUBLIC
DTSTART{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
DTEND{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
DTSTART;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
DTEND;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
DTSTAMP:{{ item.timeslot.modified|date:"Ymd" }}T{{ item.timeslot.modified|date:"His" }}Z{% if item.session.agenda %}
URL:{{item.session.agenda.get_versionless_href}}{% endif %}
DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
@ -25,4 +25,4 @@ DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
\n{# link agenda for ietf meetings #}
See in schedule: {% absurl 'ietf.meeting.views.agenda' num=schedule.meeting.number %}#row-{{ item.slug }}\n{% endif %}
END:VEVENT
{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endautoescape %}
{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endtimezone %}{% endautoescape %}

View file

@ -1,11 +1,11 @@
{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
{% load ietf_filters tz %}{% timezone meeting.tz %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
{% endif %}The {{ group.name }} ({{ group.acronym }}) {% if group.type.slug == 'wg' and group.state.slug == 'bof' %}BOF{% else %}{{group.type.name}}{% endif %} will hold
{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.utc_start_time | date:"H:i" }} to {{ assignments.first.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}.
{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.time | utc | date:"H:i" }} to {{ assignments.first.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}.
{% else %}a multi-day {% if not meeting.city %}virtual {% endif %}interim meeting.
{% for assignment in assignments %}Session {{ forloop.counter }}:
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.utc_start_time | date:"H:i" }} to {{ assignment.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.time | utc | date:"H:i" }} to {{ assignment.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}
{% endfor %}{% endif %}
{% if meeting.city %}Meeting Location:
{{ meeting.city }}, {{ meeting.country }}
@ -17,3 +17,4 @@ Information about remote participation:
{{ meeting.session_set.first.remote_instructions }}
{{ meeting.session_set.first.agenda_note }}
{% endtimezone %}

View file

@ -1,12 +1,12 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters %}
{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters tz %}
{% block title %}Interim Request Details{% endblock %}
{% block pagehead %}
<link rel="stylesheet" href="{% static 'ietf/css/select2.css' %}">
{% endblock %}
{% block content %}
{% block content %}{% timezone meeting.tz %}
{% origin %}
<h1>Interim Meeting Request Details</h1>
<dl class="row my-3">
@ -67,7 +67,7 @@
<dd class="col-sm-10">
{{ assignment.timeslot.time|date:"H:i" }}
{% if meeting.time_zone != 'UTC' %}
({{ assignment.timeslot.utc_start_time|date:"H:i" }} UTC)
({{ assignment.timeslot.time|utc|date:"H:i" }} UTC)
{% endif %}
</dd>
<dt class="col-sm-2">
@ -151,7 +151,7 @@
{% endif %}
{% endwith %}
{% if can_approve and status_slug == 'apprw' %}</form>{% endif %}
{% endblock %}
{% endtimezone %}{% endblock %}
{% block js %}
<script src="{% static 'ietf/js/select2.js' %}"></script>
<script src="{% static 'ietf/js/meeting-interim-request.js' %}"></script>

View file

@ -2,10 +2,6 @@
<div class="time"
data-start-time="{{ item.start_timestamp }}"
data-end-time="{{ item.end_timestamp }}">
{% if "-utc" in request.path %}
{{ item.timeslot.utc_start_time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.utc_end_time|date:"H:i" }}
{% else %}
{{ item.timeslot.time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.end_time|date:"H:i" }}
{% endif %}
{{ item.timeslot.time|date:"H:i" }}<span class="d-lg-none"><br></span>-{{ item.timeslot.end_time|date:"H:i" }}
</div>
</div>

View file

@ -2,7 +2,7 @@
{# Copyright The IETF Trust 2015, 2020, All Rights Reserved #}
{% load origin %}
{% load cache %}
{% load ietf_filters static classname %}
{% load ietf_filters static classname tz %}
{% block pagehead %}
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
<link rel="stylesheet" href="{% static "ietf/js/fullcalendar.css" %}">
@ -67,9 +67,9 @@
{% elif entry|classname == 'Session' %}
{% with session=entry group=entry.group meeting=entry.meeting %}
<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" }}
data-start-utc="{{ session.official_timeslotassignment.timeslot.time | utc | date:'Y-m-d H:i' }}Z"
data-end-utc="{{ session.official_timeslotassignment.timeslot.end_time | utc | date:'Y-m-d H:i' }}Z">
{{ session.official_timeslotassignment.timeslot.time | utc | date:"Y-m-d H:i" }}-{{ session.official_timeslotassignment.timeslot.end_time | utc | date:"H:i" }}
</td>
<td>
<a href="{% url 'ietf.group.views.group_home' acronym=group.acronym %}">{{ group.acronym }}</a>
@ -132,8 +132,8 @@
{
group: '{% if session.group %}{{session.group.acronym}}{% endif %}{% if session.name %} - {{session.name}}{% endif %}',
filter_keywords: ["{{ session.filter_keywords|join:'","' }}"],
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"}}'),
start_moment: moment.utc('{{session.official_timeslotassignment.timeslot.time | utc | date:"Y-m-d H:i"}}'),
end_moment: moment.utc('{{session.official_timeslotassignment.timeslot.end_time | utc | date:"Y-m-d H:i"}}'),
url: '{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}'
}
{% endwith %}

View file

@ -3,6 +3,7 @@ import email.utils
import datetime
from django.conf import settings
from django.utils import timezone
def local_timezone_to_utc(d):
"""Takes a naive datetime in the local timezone and returns a
@ -36,4 +37,35 @@ def email_time_to_local_timezone(date_string):
def date2datetime(date, tz=pytz.utc):
return datetime.datetime(*(date.timetuple()[:6]), tzinfo=tz)
def datetime_today(tzinfo=None):
"""Get a timezone-aware datetime representing midnight today
For use with datetime fields representing a date.
"""
if tzinfo is None:
tzinfo = pytz.utc
return timezone.now().astimezone(tzinfo).replace(hour=0, minute=0, second=0, microsecond=0)
def date_today(tzinfo=None):
"""Get the date corresponding to the current moment
Note that Dates are not themselves timezone aware.
"""
if tzinfo is None:
tzinfo = pytz.utc
return timezone.now().astimezone(tzinfo).date()
def time_now(tzinfo=None):
"""Get the "wall clock" time corresponding to the current moment
The value returned by this data is a Time with no tzinfo attached. (Time
objects have only limited timezone support, even if tzinfo is filled in,
and may not behave correctly when daylight savings time shifts are relevant.)
"""
if tzinfo is None:
tzinfo = pytz.utc
return timezone.now().astimezone(tzinfo).time()