Repository: ietf-tools/datatracker

Issue:
Interim request edit view uses wrong TZ for initial time value
### Describe the issue

When editing an interim, the meeting time is initialized with the UTC time of the meeting. This field is labeled and treated as the meeting-local time otherwise. This results in incorrectly changing the meeting time when the form is saved.

Need to initialize this field to local time.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

Resolving patch:
diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py
--- a/ietf/meeting/forms.py
+++ b/ietf/meeting/forms.py
@@ -223,10 +223,10 @@ def save(self, *args, **kwargs):
 
 class InterimSessionModelForm(forms.ModelForm):
     date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
-    time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Local time")
+    time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Start time in meeting time zone")
     time.widget.attrs['placeholder'] = "HH:MM"
     requested_duration = CustomDurationField(required=True)
-    end_time = forms.TimeField(required=False, help_text="Local time")
+    end_time = forms.TimeField(required=False, help_text="End time in meeting time zone")
     end_time.widget.attrs['placeholder'] = "HH:MM"
     remote_participation = forms.ChoiceField(choices=(), required=False)
     remote_instructions = forms.CharField(
@@ -258,8 +258,8 @@ def __init__(self, *args, **kwargs):
         self.is_edit = bool(self.instance.pk)
         # setup fields that aren't intrinsic to the Session object
         if self.is_edit:
-            self.initial['date'] = self.instance.official_timeslotassignment().timeslot.time
-            self.initial['time'] = self.instance.official_timeslotassignment().timeslot.time
+            self.initial['date'] = self.instance.official_timeslotassignment().timeslot.local_start_time().date()
+            self.initial['time'] = self.instance.official_timeslotassignment().timeslot.local_start_time().time()
             if self.instance.agenda():
                 doc = self.instance.agenda()
                 content = doc.text_or_error()


Test patch:
diff --git a/ietf/meeting/tests_forms.py b/ietf/meeting/tests_forms.py
--- a/ietf/meeting/tests_forms.py
+++ b/ietf/meeting/tests_forms.py
@@ -6,6 +6,7 @@
 from django.test import override_settings, RequestFactory
 
 from ietf.group.factories import GroupFactory
+from ietf.meeting.factories import SessionFactory
 from ietf.meeting.forms import (FileUploadForm, ApplyToAllFileUploadForm, InterimSessionModelForm,
                                 InterimMeetingModelForm)
 from ietf.person.factories import PersonFactory
@@ -123,6 +124,15 @@ def test_remote_participation_options(self):
         self.assertNotIn('meetecho', choice_vals)
         self.assertIn('manual', choice_vals)
 
+    def test_edits_in_meeting_time_zone(self):
+        # use a time zone that never has a UTC offset of 0, even with DST
+        session = SessionFactory(meeting__type_id='interim', meeting__time_zone='America/Halifax')
+        form = InterimSessionModelForm(instance=session)
+        self.assertEqual(
+            form.initial['time'].strftime('%H:%M'),
+            session.official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M'),
+        )
+
 
 class InterimMeetingModelFormTests(TestCase):
     def test_enforces_authroles(self):
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -5374,12 +5374,11 @@ def test_interim_request_edit_no_notice(self):
         length_before = len(outbox)
         form_initial = r.context['form'].initial
         formset_initial =  r.context['formset'].forms[0].initial
-        new_time = formset_initial['time'] + datetime.timedelta(hours=1)
         data = {'group':group.pk,
                 'meeting_type':'single',
                 'session_set-0-id':meeting.session_set.first().id,
                 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
-                'session_set-0-time':new_time.strftime('%H:%M'),
+                'session_set-0-time':'12:34',
                 'session_set-0-requested_duration': '00:30',
                 'session_set-0-remote_instructions':formset_initial['remote_instructions'],
                 #'session_set-0-agenda':formset_initial['agenda'],
@@ -5392,7 +5391,10 @@ def test_interim_request_edit_no_notice(self):
         self.assertEqual(len(outbox),length_before)
         session = meeting.session_set.first()
         timeslot = session.official_timeslotassignment().timeslot
-        self.assertEqual(timeslot.time,new_time)
+        self.assertEqual(
+            timeslot.time,
+            meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
+        )
         
     def test_interim_request_edit(self):
         '''Edit request.  Send notice of change'''
@@ -5412,13 +5414,12 @@ def test_interim_request_edit(self):
         length_before = len(outbox)
         form_initial = r.context['form'].initial
         formset_initial =  r.context['formset'].forms[0].initial
-        new_time = formset_initial['time'] + datetime.timedelta(hours=1)
         new_duration = formset_initial['requested_duration'] + datetime.timedelta(hours=1)
         data = {'group':group.pk,
                 'meeting_type':'single',
                 'session_set-0-id':meeting.session_set.first().id,
                 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
-                'session_set-0-time':new_time.strftime('%H:%M'),
+                'session_set-0-time': '12:34',
                 'session_set-0-requested_duration':self.strfdelta(new_duration, '{hours}:{minutes}'),
                 'session_set-0-remote_instructions':formset_initial['remote_instructions'],
                 #'session_set-0-agenda':formset_initial['agenda'],
@@ -5432,7 +5433,10 @@ def test_interim_request_edit(self):
         self.assertIn('CHANGED', outbox[-1]['Subject'])
         session = meeting.session_set.first()
         timeslot = session.official_timeslotassignment().timeslot
-        self.assertEqual(timeslot.time,new_time)
+        self.assertEqual(
+            timeslot.time,
+            meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
+        )
         self.assertEqual(timeslot.duration,new_duration)
     
     def strfdelta(self, tdelta, fmt):