From 036bdf7b6465de205c4962468d56c7c655fcc630 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 29 Aug 2022 15:59:17 -0300 Subject: [PATCH 01/34] test: fix timestamp construction in several doc tests --- ietf/doc/tests.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index c201e7f2d5..46385008a8 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -18,6 +18,7 @@ from urllib.parse import urlparse, parse_qs from tempfile import NamedTemporaryFile from collections import defaultdict +from zoneinfo import ZoneInfo from django.core.management import call_command from django.urls import reverse as urlreverse @@ -57,6 +58,8 @@ from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects from ietf.utils.test_utils import TestCase from ietf.utils.text import normalize_text +from ietf.utils.timezone import datetime_today + class SearchTests(TestCase): def test_search(self): @@ -1428,6 +1431,8 @@ def _run_test(username=None, expect_buttons=False): def test_draft_group_link(self): """Link to group 'about' page should have correct format""" + event_datetime = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles')) + for group_type_id in ['wg', 'rg', 'ag']: group = GroupFactory(type_id=group_type_id) draft = WgDraftFactory(name='draft-document-%s' % group_type_id, group=group) @@ -1436,7 +1441,7 @@ def test_draft_group_link(self): self.assert_correct_wg_group_link(r, group) rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group) - DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') + DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime) # get the rfc name to avoid a redirect rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name))) @@ -1451,7 +1456,7 @@ def test_draft_group_link(self): self.assert_correct_non_wg_group_link(r, group) rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group) - DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') + DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime) # get the rfc name to avoid a redirect rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name))) @@ -1837,7 +1842,7 @@ def test_last_call_feed(self): desc="Last call\x0b", # include a control character to be sure it does not break anything type="sent_last_call", by=Person.objects.get(user__username="secretary"), - expires=datetime.date.today() + datetime.timedelta(days=7)) + expires=datetime_today() + datetime.timedelta(days=7)) r = self.client.get("/feed/last-call/") self.assertEqual(r.status_code, 200) @@ -1885,10 +1890,14 @@ def test_document_bibtex(self): #other_aliases = ['rfc6020',], states = [('draft','rfc'),('draft-iesg','pub')], std_level_id = 'ps', - time = datetime.datetime(2010,10,10), + time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles')), ) num = rfc.rfc_number() - DocEventFactory.create(doc=rfc, type='published_rfc', time = '2010-10-10') + DocEventFactory.create( + doc=rfc, + type='published_rfc', + time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles')), + ) # url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name)) r = self.client.get(url) @@ -1906,10 +1915,14 @@ def test_document_bibtex(self): stream_id = 'ise', states = [('draft','rfc'),('draft-iesg','pub')], std_level_id = 'inf', - time = datetime.datetime(1990,0o4,0o1), + time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo('America/Los_Angeles')), ) num = april1.rfc_number() - DocEventFactory.create(doc=april1, type='published_rfc', time = '1990-04-01') + DocEventFactory.create( + doc=april1, + type='published_rfc', + time=datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo('America/Los_Angeles')), + ) # url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name)) r = self.client.get(url) @@ -2044,7 +2057,9 @@ def tearDown(self): super().tearDown() def testManagementCommand(self): - a_month_ago = timezone.now() - datetime.timedelta(30) + tz = ZoneInfo('America/Los_Angeles') + a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(tz) + a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0) ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person shepherd = PersonFactory() author1 = PersonFactory() @@ -2059,9 +2074,9 @@ def testManagementCommand(self): doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad) doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad) doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago) - DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago.strftime("%Y-%m-%d")) - doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10)) - DocEventFactory.create(doc=doc4, type='published_rfc', time = '2010-10-10') + DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago) + doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10, tzinfo=tz)) + DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=tz)) doc5 = IndividualDraftFactory(authors=[author6]) args = [ ] From 71173fa4825c525a1430453bb21d775015d1e7ea Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 29 Aug 2022 17:23:41 -0300 Subject: [PATCH 02/34] refactor: rename date2datetime to datetime_from_date and clarify code --- ietf/meeting/models.py | 6 +++--- ietf/utils/timezone.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index d3fd191fb1..59e47e2b9d 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -41,7 +41,7 @@ from ietf.utils.decorators import memoize from ietf.utils.storage import NoLocationMigrationFileSystemStorage from ietf.utils.text import xslugify -from ietf.utils.timezone import date2datetime +from ietf.utils.timezone import datetime_from_date from ietf.utils.models import ForeignKey from ietf.utils.validators import ( MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type, @@ -152,7 +152,7 @@ def get_00_cutoff(self): cutoff_date = importantdate.date else: cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) - cutoff_time = date2datetime(cutoff_date) + self.idsubmit_cutoff_time_utc + cutoff_time = datetime_from_date(cutoff_date) + self.idsubmit_cutoff_time_utc return cutoff_time def get_01_cutoff(self): @@ -164,7 +164,7 @@ def get_01_cutoff(self): cutoff_date = importantdate.date else: cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) - cutoff_time = date2datetime(cutoff_date) + self.idsubmit_cutoff_time_utc + cutoff_time = datetime_from_date(cutoff_date) + self.idsubmit_cutoff_time_utc return cutoff_time def get_reopen_time(self): diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index 4d3f4be5f5..fa5b7b2918 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -35,8 +35,9 @@ def email_time_to_local_timezone(date_string): return utc_to_local_timezone(d) -def date2datetime(date, tz=pytz.utc): - return datetime.datetime(*(date.timetuple()[:6]), tzinfo=tz) +def datetime_from_date(date, tz=pytz.utc): + """Get datetime at midnight on a given date""" + return datetime.datetime(date.year, date.month, date.day, tzinfo=tz) def datetime_today(tzinfo=None): From 8f5c8367ea56c162997cc8cea387f7a8b5e48d04 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 29 Aug 2022 17:59:08 -0300 Subject: [PATCH 03/34] chore: helper to get tzinfo for PRODUCTION_TIMEZONE --- ietf/utils/timezone.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index fa5b7b2918..69114fe4ea 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -2,6 +2,8 @@ import email.utils import datetime +from zoneinfo import ZoneInfo + from django.conf import settings from django.utils import timezone @@ -70,3 +72,7 @@ def time_now(tzinfo=None): if tzinfo is None: tzinfo = pytz.utc return timezone.now().astimezone(tzinfo).time() + + +def production_tzinfo(): + return ZoneInfo(settings.PRODUCTION_TIMEZONE) From 80e7baaa721478f0c50fcf40b407f51b87a771cf Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 29 Aug 2022 18:00:58 -0300 Subject: [PATCH 04/34] fix: fix timezone handling in make_last_call() --- ietf/doc/views_ballot.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 7a4e345711..8e0a4247c8 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -40,6 +40,8 @@ from ietf.utils.mail import send_mail_text, send_mail_preformatted from ietf.utils.decorators import require_api_key from ietf.utils.response import permission_denied +from ietf.utils.timezone import datetime_from_date, production_tzinfo + BALLOT_CHOICES = (("yes", "Yes"), ("noobj", "No Objection"), @@ -1055,9 +1057,11 @@ def make_last_call(request, name): e.desc = "The following Last Call announcement was sent out (ends %s):

" % expiration_date e.desc += announcement - if form.cleaned_data['last_call_sent_date'] != e.time.date(): - e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) - e.expires = expiration_date + e_production_time = e.time.astimezone(production_tzinfo()) + if form.cleaned_data['last_call_sent_date'] != e_production_time.date(): + lcsd = form.cleaned_data['last_call_sent_date'] + e.time = e_production_time.replace(year=lcsd.year, month=lcsd.month, day=lcsd.day) # preserves tzinfo + e.expires = datetime_from_date(expiration_date, production_tzinfo()) e.save() events.append(e) From 99a3d95e115a4665e2f93b33e446a12257e5c72d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 29 Aug 2022 18:07:40 -0300 Subject: [PATCH 05/34] test: fix datetime generation in doc.tests_charter --- ietf/doc/tests_charter.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py index 8732b7701d..58e87ab915 100644 --- a/ietf/doc/tests_charter.py +++ b/ietf/doc/tests_charter.py @@ -26,6 +26,8 @@ from ietf.utils.test_utils import TestCase from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.test_utils import login_testing_unauthorized +from ietf.utils.timezone import datetime_today + class ViewCharterTests(TestCase): def test_view_revisions(self): @@ -402,7 +404,7 @@ def test_no_returning_item_for_different_ballot(self): # Make it so that the charter has been through internal review, and passed its external review # ballot on a previous telechat - last_week = datetime.date.today()-datetime.timedelta(days=7) + last_week = datetime_today() - datetime.timedelta(days=7) BallotDocEvent.objects.create(type='created_ballot',by=login,doc=charter, rev=charter.rev, ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), time=last_week) @@ -746,7 +748,7 @@ def test_approve(self): charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev")) - due_date = datetime.date.today() + datetime.timedelta(days=180) + due_date = datetime_today() + datetime.timedelta(days=180) m1 = GroupMilestone.objects.create(group=group, state_id="active", desc="Has been copied", @@ -826,7 +828,7 @@ def test_charter_with_milestones(self): m = GroupMilestone.objects.create(group=charter.group, state_id="active", desc="Test milestone", - due=datetime.date.today(), + due=datetime_today(), resolved="") url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev)) From da463e6b4a8acb6a5dc9bf1eb662d72fce535d75 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 12:47:15 -0300 Subject: [PATCH 06/34] refactor: remove PRODUCTION_TIMEZONE setting Replaces the PRODUCTION_TIMEZONE setting with a constant, DEADLINE_TZINFO, in ietf.utils.timezone. --- ietf/doc/views_ballot.py | 6 +++--- ietf/settings.py | 2 -- ietf/utils/timezone.py | 9 +++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 8e0a4247c8..12451eb22b 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -40,7 +40,7 @@ from ietf.utils.mail import send_mail_text, send_mail_preformatted from ietf.utils.decorators import require_api_key from ietf.utils.response import permission_denied -from ietf.utils.timezone import datetime_from_date, production_tzinfo +from ietf.utils.timezone import datetime_from_date, DEADLINE_TZINFO BALLOT_CHOICES = (("yes", "Yes"), @@ -1057,11 +1057,11 @@ def make_last_call(request, name): e.desc = "The following Last Call announcement was sent out (ends %s):

" % expiration_date e.desc += announcement - e_production_time = e.time.astimezone(production_tzinfo()) + e_production_time = e.time.astimezone(DEADLINE_TZINFO) if form.cleaned_data['last_call_sent_date'] != e_production_time.date(): lcsd = form.cleaned_data['last_call_sent_date'] e.time = e_production_time.replace(year=lcsd.year, month=lcsd.month, day=lcsd.day) # preserves tzinfo - e.expires = datetime_from_date(expiration_date, production_tzinfo()) + e.expires = datetime_from_date(expiration_date, DEADLINE_TZINFO) e.save() events.append(e) diff --git a/ietf/settings.py b/ietf/settings.py index 0beb06a843..d078a12f74 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -1039,8 +1039,6 @@ def skip_unreadable_post(record): # If we need to revert to xmpp # CHAT_ARCHIVE_URL_PATTERN = 'https://www.ietf.org/jabber/logs/{chat_room_name}?C=M;O=D' -PRODUCTION_TIMEZONE = "America/Los_Angeles" - PYFLAKES_DEFAULT_ARGS= ["ietf", ] VULTURE_DEFAULT_ARGS= ["ietf", ] diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index 69114fe4ea..db2f64b5a2 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -7,6 +7,11 @@ from django.conf import settings from django.utils import timezone + +# Default time zone for deadlines / expiration dates. +DEADLINE_TZINFO = ZoneInfo('PST8PDT') + + def local_timezone_to_utc(d): """Takes a naive datetime in the local timezone and returns a naive datetime with the corresponding UTC time.""" @@ -72,7 +77,3 @@ def time_now(tzinfo=None): if tzinfo is None: tzinfo = pytz.utc return timezone.now().astimezone(tzinfo).time() - - -def production_tzinfo(): - return ZoneInfo(settings.PRODUCTION_TIMEZONE) From 14f27508c085ad78e922db2767db8e9df079c7cc Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 13:32:01 -0300 Subject: [PATCH 07/34] test: be more careful about timezone in tests_charter.py --- ietf/doc/tests_charter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py index 58e87ab915..c420fdd0af 100644 --- a/ietf/doc/tests_charter.py +++ b/ietf/doc/tests_charter.py @@ -26,7 +26,7 @@ from ietf.utils.test_utils import TestCase from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.test_utils import login_testing_unauthorized -from ietf.utils.timezone import datetime_today +from ietf.utils.timezone import datetime_today, date_today, DEADLINE_TZINFO class ViewCharterTests(TestCase): @@ -404,7 +404,7 @@ def test_no_returning_item_for_different_ballot(self): # Make it so that the charter has been through internal review, and passed its external review # ballot on a previous telechat - last_week = datetime_today() - datetime.timedelta(days=7) + last_week = datetime_today(DEADLINE_TZINFO) - datetime.timedelta(days=7) BallotDocEvent.objects.create(type='created_ballot',by=login,doc=charter, rev=charter.rev, ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), time=last_week) @@ -748,7 +748,7 @@ def test_approve(self): charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev")) - due_date = datetime_today() + datetime.timedelta(days=180) + due_date = date_today(DEADLINE_TZINFO) + datetime.timedelta(days=180) m1 = GroupMilestone.objects.create(group=group, state_id="active", desc="Has been copied", @@ -828,7 +828,7 @@ def test_charter_with_milestones(self): m = GroupMilestone.objects.create(group=charter.group, state_id="active", desc="Test milestone", - due=datetime_today(), + due=date_today(DEADLINE_TZINFO), resolved="") url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev)) From 703fc9d58b8d41e1b19476a81e2ecb7f953d9dbf Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 13:35:09 -0300 Subject: [PATCH 08/34] test: be more careful about timezone in doc/tests.py --- ietf/doc/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 46385008a8..550e2f8798 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -58,7 +58,7 @@ from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects from ietf.utils.test_utils import TestCase from ietf.utils.text import normalize_text -from ietf.utils.timezone import datetime_today +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO class SearchTests(TestCase): @@ -1842,7 +1842,7 @@ def test_last_call_feed(self): desc="Last call\x0b", # include a control character to be sure it does not break anything type="sent_last_call", by=Person.objects.get(user__username="secretary"), - expires=datetime_today() + datetime.timedelta(days=7)) + expires=datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=7)) r = self.client.get("/feed/last-call/") self.assertEqual(r.status_code, 200) From 4089f6107abf06415c6b788b50cc302c442abe34 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 13:55:48 -0300 Subject: [PATCH 09/34] fix: fix timezone handling affecting doc.tests_draft --- ietf/doc/expire.py | 5 +++-- ietf/doc/tests_draft.py | 7 ++++--- ietf/doc/utils.py | 22 +++++++++++++++++----- ietf/doc/views_draft.py | 6 ++++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py index 328a974042..a2a4a714f0 100644 --- a/ietf/doc/expire.py +++ b/ietf/doc/expire.py @@ -18,6 +18,7 @@ from ietf.meeting.models import Meeting from ietf.doc.utils import add_state_change_event, update_action_holders from ietf.mailtrigger.utils import gather_address_lists +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO nonexpirable_states: Optional[List[State]] = None @@ -53,13 +54,13 @@ def expirable_drafts(queryset=None): def get_soon_to_expire_drafts(days_of_warning): - start_date = datetime.date.today() - datetime.timedelta(1) + start_date = datetime_today(DEADLINE_TZINFO) - datetime.timedelta(1) end_date = start_date + datetime.timedelta(days_of_warning) return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date) def get_expired_drafts(): - return expirable_drafts().filter(expires__lt=datetime.date.today() + datetime.timedelta(1)) + return expirable_drafts().filter(expires__lt=datetime_today(DEADLINE_TZINFO) + datetime.timedelta(1)) def in_draft_expire_freeze(when=None): if when == None: diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 91d0542bab..a4cb02f117 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -34,6 +34,7 @@ from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.test_utils import TestCase +from ietf.utils.timezone import date_today, datetime_from_date class ChangeStateTests(TestCase): @@ -402,11 +403,11 @@ def test_edit_telechat_date(self): # change to a telechat that should cause returning item to be auto-detected # First, make it appear that the previous telechat has already passed - telechat_event.telechat_date = datetime.date.today()-datetime.timedelta(days=7) + telechat_event.telechat_date = date_today() - datetime.timedelta(days=7) telechat_event.save() ad = Person.objects.get(user__username="ad") ballot = create_ballot_if_not_open(None, draft, ad, 'approve') - ballot.time = telechat_event.telechat_date + ballot.time = datetime_from_date(telechat_event.telechat_date) ballot.save() r = self.client.post(url, data) @@ -429,7 +430,7 @@ def test_edit_telechat_date(self): self.assertTrue("Telechat update" in outbox[-1]['Subject']) # Put it on an agenda that's very soon from now - next_week = datetime.date.today()+datetime.timedelta(days=7) + next_week = date_today() + datetime.timedelta(days=7) td = TelechatDate.objects.active()[0] td.date = next_week td.save() diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index c83fa7cf68..d53a0b4225 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -39,6 +39,7 @@ from ietf.utils import draft, log from ietf.utils.mail import send_mail from ietf.mailtrigger.utils import gather_address_lists +from ietf.utils.timezone import date_today, datetime_from_date, datetime_today, DEADLINE_TZINFO from ietf.utils.xmldraft import XMLDraft @@ -637,11 +638,22 @@ def has_same_ballot(doc, date1, date2=None): """ Test if the most recent ballot created before the end of date1 is the same as the most recent ballot created before the end of date 2. """ + datetime1 = datetime_from_date(date1, DEADLINE_TZINFO) if date2 is None: - date2 = datetime.date.today() - ballot1 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date1+datetime.timedelta(days=1)) - ballot2 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date2+datetime.timedelta(days=1)) - return ballot1==ballot2 + datetime2 = datetime_today(DEADLINE_TZINFO) + else: + datetime2 = datetime_from_date(date2, DEADLINE_TZINFO) + ballot1 = doc.latest_event( + BallotDocEvent, + type='created_ballot', + time__lt=datetime1 + datetime.timedelta(days=1), + ) + ballot2 = doc.latest_event( + BallotDocEvent, + type='created_ballot', + time__lt=datetime2 + datetime.timedelta(days=1), + ) + return ballot1 == ballot2 def make_notify_changed_event(request, doc, by, new_notify, time=None): @@ -687,7 +699,7 @@ def update_telechat(request, doc, by, new_telechat_date, new_returning_item=None and on_agenda and prev_agenda and new_telechat_date != prev_telechat - and prev_telechat < datetime.date.today() + and prev_telechat < date_today(DEADLINE_TZINFO) and has_same_ballot(doc,prev.telechat_date) ): returning = True diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 5d445cfc26..a28adcb67c 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -53,6 +53,8 @@ from ietf.utils.textupload import get_cleaned_text_file_content from ietf.utils import log from ietf.utils.response import permission_denied +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO + class ChangeStateForm(forms.Form): state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True) @@ -1477,7 +1479,7 @@ def adopt_draft(request, name): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) + due_date = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) @@ -1668,7 +1670,7 @@ def change_stream_state(request, name, state_type): due_date = None if form.cleaned_data["weeks"] != None: - due_date = datetime.date.today() + datetime.timedelta(weeks=form.cleaned_data["weeks"]) + due_date = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=form.cleaned_data["weeks"]) update_reminder(doc, "stream-s", e, due_date) From c3bbf1c7c2bd78e5e9e72c15fbe893ad1acaa77d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 14:06:14 -0300 Subject: [PATCH 10/34] fix: fix timezone handling affecting tests_irsg_ballot.py --- ietf/doc/tests_irsg_ballot.py | 3 ++- ietf/doc/views_ballot.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ietf/doc/tests_irsg_ballot.py b/ietf/doc/tests_irsg_ballot.py index f178bb4e56..97074197a3 100644 --- a/ietf/doc/tests_irsg_ballot.py +++ b/ietf/doc/tests_irsg_ballot.py @@ -19,6 +19,7 @@ from ietf.person.utils import get_active_irsg, get_active_ads from ietf.group.factories import RoleFactory from ietf.person.models import Person +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO class IssueIRSGBallotTests(TestCase): @@ -254,7 +255,7 @@ def test_edit_ballot_position_permissions(self): irsgmember = get_active_irsg()[0] secr = RoleFactory(group__acronym='secretariat',name_id='secr') wg_ballot = create_ballot_if_not_open(None, wg_draft, ad.person, 'approve') - due = datetime.date.today()+datetime.timedelta(days=14) + due = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=14) rg_ballot = create_ballot_if_not_open(None, rg_draft, secr.person, 'irsg-approve', due) url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=wg_ballot.pk)) diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 12451eb22b..bd6c677378 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -40,7 +40,7 @@ from ietf.utils.mail import send_mail_text, send_mail_preformatted from ietf.utils.decorators import require_api_key from ietf.utils.response import permission_denied -from ietf.utils.timezone import datetime_from_date, DEADLINE_TZINFO +from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO BALLOT_CHOICES = (("yes", "Yes"), @@ -1112,7 +1112,7 @@ def issue_irsg_ballot(request, name): raise Http404 by = request.user.person - fillerdate = datetime.date.today() + datetime.timedelta(weeks=2) + fillerdate = date_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=2) if request.method == 'POST': button = request.POST.get("irsg_button") @@ -1121,7 +1121,7 @@ def issue_irsg_ballot(request, name): e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person) if (duedate == None or duedate==""): duedate = str(fillerdate) - e.duedate = datetime.datetime.strptime(duedate, '%Y-%m-%d') + e.duedate = datetime_from_date(datetime.datetime.strptime(duedate, '%Y-%m-%d'), DEADLINE_TZINFO) e.type = "created_ballot" e.desc = "Created IRSG Ballot" ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve") From 120a1874eb80c7142b7cf38b5684ed7de3de1d30 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 14:44:49 -0300 Subject: [PATCH 11/34] fix: fix timezone handling affecting tests_review.py --- ietf/doc/tests_review.py | 6 +++--- ietf/doc/views_review.py | 8 +++++++- ietf/review/utils.py | 9 +++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py index efe5b7b06f..6ff804af31 100644 --- a/ietf/doc/tests_review.py +++ b/ietf/doc/tests_review.py @@ -10,7 +10,6 @@ from mock import patch from requests import Response - from django.apps import apps from django.urls import reverse as urlreverse from django.conf import settings @@ -39,6 +38,7 @@ from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects from ietf.utils.test_utils import TestCase from ietf.utils.text import strip_prefix, xslugify +from ietf.utils.timezone import DEADLINE_TZINFO from django.utils.html import escape class ReviewTests(TestCase): @@ -734,7 +734,7 @@ def test_complete_review_enter_content_by_secretary(self): # The secretary is allowed to set a custom completion date (#2590) assignment = reload_db_objects(assignment) self.assertEqual(assignment.state_id, "completed") - self.assertEqual(assignment.completed_on, datetime.datetime(2012, 12, 24, 12, 13, 14)) + self.assertEqual(assignment.completed_on, datetime.datetime(2012, 12, 24, 12, 13, 14, tzinfo=DEADLINE_TZINFO)) # There should be two events: # - the event logging when the change when it was entered, i.e. very close to now. @@ -742,7 +742,7 @@ def test_complete_review_enter_content_by_secretary(self): events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-time') event0_time_diff = timezone.now() - events[0].time self.assertLess(event0_time_diff, datetime.timedelta(seconds=10)) - self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14)) + self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14, tzinfo=DEADLINE_TZINFO)) with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f: self.assertEqual(f.read(), "This is a review\nwith two lines") diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py index 19c822ec97..1d901ba8a8 100644 --- a/ietf/doc/views_review.py +++ b/ietf/doc/views_review.py @@ -54,6 +54,8 @@ from ietf.mailtrigger.utils import gather_address_lists from ietf.utils.fields import MultiEmailField from ietf.utils.response import permission_denied +from ietf.utils.timezone import DEADLINE_TZINFO + def clean_doc_revision(doc, rev): if rev: @@ -768,7 +770,11 @@ def complete_review(request, name, assignment_id=None, acronym=None): completion_datetime = timezone.now() if "completion_date" in form.cleaned_data: - completion_datetime = datetime.datetime.combine(form.cleaned_data["completion_date"], form.cleaned_data.get("completion_time") or datetime.time.min) + completion_datetime = datetime.datetime.combine( + form.cleaned_data["completion_date"], + form.cleaned_data.get("completion_time") or datetime.time.min, + tzinfo=DEADLINE_TZINFO, + ) # complete assignment assignment.state = form.cleaned_data["state"] diff --git a/ietf/review/utils.py b/ietf/review/utils.py index f665ba06b9..e8ed632f85 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -32,6 +32,8 @@ from ietf.utils.mail import send_mail from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs from ietf.utils import log +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO + # The origin date is used to have a single reference date for "every X days". # This date is arbitrarily chosen and has no special meaning, but should be consistent. @@ -194,7 +196,10 @@ def positive_days(time_from, time_to): assigned_time = assigned_on closed_time = completed_on - late_days = positive_days(datetime.datetime.combine(deadline, datetime.time.max), closed_time) + late_days = positive_days( + datetime.datetime.combine(deadline, datetime.time.max, tzinfo=DEADLINE_TZINFO), + closed_time, + ) request_to_assignment_days = positive_days(requested_time, assigned_time) assignment_to_closure_days = positive_days(assigned_time, closed_time) request_to_closure_days = positive_days(requested_time, closed_time) @@ -285,7 +290,7 @@ def latest_review_assignments_for_reviewers(team, days_back=365): extracted_data = extract_review_assignment_data( teams=[team], - time_from=datetime.date.today() - datetime.timedelta(days=days_back), + time_from=datetime_today(DEADLINE_TZINFO) - datetime.timedelta(days=days_back), ordering=["reviewer"], ) From fcf51b52f84d2a270df6f2f47b94cdfc806c84d7 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 15:07:16 -0300 Subject: [PATCH 12/34] fix: fix timezone handling affecting last ietf.doc tests --- ietf/doc/tests_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/doc/tests_utils.py b/ietf/doc/tests_utils.py index aef6eb69a7..001bd97370 100644 --- a/ietf/doc/tests_utils.py +++ b/ietf/doc/tests_utils.py @@ -143,7 +143,7 @@ def test_update_action_holders_resets_age(self): doc = self.doc_in_iesg_state('pub-req') doc.action_holders.set([self.ad]) dah = doc.documentactionholder_set.get(person=self.ad) - dah.time_added = datetime.datetime(2020, 1, 1) # arbitrary date in the past + dah.time_added = datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc) # arbitrary date in the past dah.save() self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today()) From b1404902c3025d703fae71d5a5270ea70813405f Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 15:40:07 -0300 Subject: [PATCH 13/34] fix: fix timezone handling affecting last ietf.group tests --- ietf/group/tests_review.py | 31 ++++++++++++++++--------------- ietf/group/views.py | 10 +++++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/ietf/group/tests_review.py b/ietf/group/tests_review.py index 924f6fadee..4930c740c7 100644 --- a/ietf/group/tests_review.py +++ b/ietf/group/tests_review.py @@ -26,6 +26,7 @@ from ietf.doc.factories import DocumentFactory from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory +from ietf.utils.timezone import date_today, datetime_today, DEADLINE_TZINFO from django.utils.html import escape class ReviewTests(TestCase): @@ -156,7 +157,7 @@ def test_reviewer_overview(self): review_request__doc=review_req1.doc, review_request__team=review_req1.team, review_request__type_id="early", - review_request__deadline=datetime.date.today() + datetime.timedelta(days=30), + review_request__deadline=date_today(DEADLINE_TZINFO) + datetime.timedelta(days=30), review_request__state_id="assigned", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "accepted", @@ -166,7 +167,7 @@ def test_reviewer_overview(self): UnavailablePeriod.objects.create( team=review_req1.team, person=reviewer, - start_date=datetime.date.today() - datetime.timedelta(days=10), + start_date=date_today() - datetime.timedelta(days=10), availability="unavailable", ) @@ -211,7 +212,7 @@ def test_reviewer_overview(self): review_request__doc=review_req2.doc, review_request__team=review_req2.team, review_request__type_id="lc", - review_request__deadline=datetime.date.today() - datetime.timedelta(days=30), + review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=30), review_request__state_id="assigned", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "no-response", @@ -232,15 +233,15 @@ def test_reviewer_overview(self): review_req3 = ReviewRequestFactory(state_id='completed', team=team) ReviewAssignmentFactory( review_request__doc=review_req3.doc, - review_request__time=datetime.date.today() - datetime.timedelta(days=30), + review_request__time=datetime_today() - datetime.timedelta(days=30), review_request__team=review_req3.team, review_request__type_id="telechat", - review_request__deadline=datetime.date.today() - datetime.timedelta(days=25), + review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=25), review_request__state_id="completed", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "completed", reviewer=reviewer.email_set.first(), - assigned_on=datetime.date.today() - datetime.timedelta(days=30) + assigned_on=datetime_today() - datetime.timedelta(days=30) ) r = self.client.get(url) self.assertEqual(r.status_code, 200) @@ -253,15 +254,15 @@ def test_reviewer_overview(self): for i in range(10): ReviewAssignmentFactory( review_request__doc=reqs[i].doc, - review_request__time=datetime.date.today() - datetime.timedelta(days=i*30), + review_request__time=datetime_today() - datetime.timedelta(days=i*30), review_request__team=reqs[i].team, review_request__type_id="telechat", - review_request__deadline=datetime.date.today() - datetime.timedelta(days=i*20), + review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=i*20), review_request__state_id="completed", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "completed", reviewer=reviewer.email_set.first(), - assigned_on=datetime.date.today() - datetime.timedelta(days=i*30) + assigned_on=datetime_today() - datetime.timedelta(days=i*30) ) r = self.client.get(url) self.assertEqual(r.status_code, 200) @@ -305,28 +306,28 @@ def test_reviewer_overview(self): review_req4 = ReviewRequestFactory(state_id='completed', team=team) ReviewAssignmentFactory( review_request__doc=review_req4.doc, - review_request__time=datetime.date.today() - datetime.timedelta(days=80), + review_request__time=datetime_today() - datetime.timedelta(days=80), review_request__team=review_req4.team, review_request__type_id="lc", - review_request__deadline=datetime.date.today() - datetime.timedelta(days=60), + review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=60), review_request__state_id="assigned", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "accepted", reviewer=reviewer.email_set.first(), - assigned_on=datetime.date.today() - datetime.timedelta(days=80) + assigned_on=datetime_today() - datetime.timedelta(days=80) ) review_req5 = ReviewRequestFactory(state_id='completed', team=team) ReviewAssignmentFactory( review_request__doc=review_req5.doc, - review_request__time=datetime.date.today() - datetime.timedelta(days=120), + review_request__time=datetime_today() - datetime.timedelta(days=120), review_request__team=review_req5.team, review_request__type_id="lc", - review_request__deadline=datetime.date.today() - datetime.timedelta(days=100), + review_request__deadline=date_today(DEADLINE_TZINFO) - datetime.timedelta(days=100), review_request__state_id="assigned", review_request__requested_by=Person.objects.get(user__username="reviewer"), state_id = "accepted", reviewer=reviewer.email_set.first(), - assigned_on=datetime.date.today() - datetime.timedelta(days=120) + assigned_on=datetime_today() - datetime.timedelta(days=120) ) r = self.client.get(url) self.assertEqual(r.status_code, 200) diff --git a/ietf/group/views.py b/ietf/group/views.py index 338defe5e1..d003c44a92 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -119,6 +119,7 @@ from ietf.utils.response import permission_denied from ietf.utils.text import strip_suffix from ietf.utils import markdown +from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO # --- Helpers ---------------------------------------------------------- @@ -1421,11 +1422,14 @@ def review_requests(request, acronym, group_type=None): }[since] closed_review_requests = closed_review_requests.filter( - Q(reviewrequestdocevent__type='closed_review_request', reviewrequestdocevent__time__gte=datetime.date.today() - date_limit) - | Q(reviewrequestdocevent__isnull=True, time__gte=datetime.date.today() - date_limit) + Q(reviewrequestdocevent__type='closed_review_request', + reviewrequestdocevent__time__gte=datetime_today(DEADLINE_TZINFO) - date_limit) + | Q(reviewrequestdocevent__isnull=True, time__gte=datetime_today(DEADLINE_TZINFO) - date_limit) ).distinct() - closed_review_assignments = closed_review_assignments.filter(completed_on__gte = datetime.date.today() - date_limit) + closed_review_assignments = closed_review_assignments.filter( + completed_on__gte = datetime_today(DEADLINE_TZINFO) - date_limit, + ) return render(request, 'group/review_requests.html', construct_group_menu_context(request, group, "review requests", group_type, { From 9f796682c8d500a638ebacc31b6618820e37b5f3 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 15:51:03 -0300 Subject: [PATCH 14/34] fix: fix timezone handling affecting ietf.iesg tests --- ietf/iesg/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 5d1457cee8..5334e8a85f 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -63,6 +63,7 @@ from ietf.ietfauth.utils import has_role, role_required, user_is_person from ietf.person.models import Person from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date +from ietf.utils.timezone import date_today, datetime_from_date def review_decisions(request, year=None): events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved")) @@ -73,9 +74,9 @@ def review_decisions(request, year=None): year = int(year) events = events.filter(time__year=year) else: - d = datetime.date.today() - datetime.timedelta(days=185) + d = date_today() - datetime.timedelta(days=185) d = datetime.date(d.year, d.month, 1) - events = events.filter(time__gte=d) + events = events.filter(time__gte=datetime_from_date(d)) events = events.select_related("doc", "doc__intended_std_level").order_by("-time", "-id") From 9da86363a929ceff60f665bbe0a8556d5fac9790 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 16:06:06 -0300 Subject: [PATCH 15/34] fix: handle timezones in get_8989_eligibility_querysets --- ietf/nomcom/utils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index b84ae75c96..999ead7a3b 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -31,6 +31,7 @@ from ietf.utils.mail import send_mail_text, send_mail, get_payload_text from ietf.utils.log import log from ietf.person.name import unidecode_name +from ietf.utils.timezone import datetime_from_date, DEADLINE_TZINFO import debug # pyflakes:ignore @@ -536,28 +537,29 @@ def get_8989_eligibility_querysets(date, base_qs): base_qs = Person.objects.all() previous_five = previous_five_meetings(date) + date_as_dt = datetime_from_date(date, DEADLINE_TZINFO) three_of_five_qs = new_three_of_five_eligible(previous_five=previous_five, queryset=base_qs) - three_years_ago = datetime.date(date.year-3,date.month,date.day) + three_years_ago = datetime.datetime(date.year - 3, date.month, date.day, tzinfo=DEADLINE_TZINFO) officer_qs = base_qs.filter( # is currently an officer Q(role__name_id__in=('chair','secr'), role__group__state_id='active', role__group__type_id='wg', - role__group__time__lte=date, + role__group__time__lte=date_as_dt, ) # was an officer since the given date (I think this is wrong - it looks at when roles _start_, not when roles end) | Q(rolehistory__group__time__gte=three_years_ago, - rolehistory__group__time__lte=date, + rolehistory__group__time__lte=date_as_dt, rolehistory__name_id__in=('chair','secr'), rolehistory__group__state_id='active', rolehistory__group__type_id='wg', ) ).distinct() - five_years_ago = datetime.date(date.year-5,date.month,date.day) - rfc_pks = set(DocEvent.objects.filter(type='published_rfc',time__gte=five_years_ago,time__lte=date).values_list('doc__pk',flat=True)) - iesgappr_pks = set(DocEvent.objects.filter(type='iesg_approved',time__gte=five_years_ago,time__lte=date).values_list('doc__pk',flat=True)) + five_years_ago = datetime.datetime(date.year - 5, date.month, date.day, tzinfo=DEADLINE_TZINFO) + rfc_pks = set(DocEvent.objects.filter(type='published_rfc', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk', flat=True)) + iesgappr_pks = set(DocEvent.objects.filter(type='iesg_approved', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk',flat=True)) qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks)) author_qs = base_qs.filter( documentauthor__document__pk__in=qualifying_pks From 024f18eea4cade94b021e4c8801797167625f3fe Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 16:21:31 -0300 Subject: [PATCH 16/34] fix: handle timezones affecting ietfauth tests --- ietf/ietfauth/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 8cf21f52c5..20dcb1b71d 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -34,9 +34,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import datetime import importlib -from datetime import date as Date, datetime as DateTime # needed if we revert to higher barrier for account creation #from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date from collections import defaultdict @@ -79,6 +79,7 @@ from ietf.utils.decorators import person_required from ietf.utils.mail import send_mail from ietf.utils.validators import validate_external_resource_value +from ietf.utils.timezone import date_today, DEADLINE_TZINFO # These are needed if we revert to the higher bar for account creation @@ -224,7 +225,7 @@ def profile(request): emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time') new_email_forms = [] - nc = NomCom.objects.filter(group__acronym__icontains=Date.today().year).first() + nc = NomCom.objects.filter(group__acronym__icontains=date_today().year).first() if nc and nc.volunteer_set.filter(person=person).exists(): volunteer_status = 'volunteered' elif nc and nc.is_accepting_volunteers: @@ -456,7 +457,7 @@ def confirm_password_reset(request, auth): password = data['password'] last_login = None if data['last_login']: - last_login = DateTime.fromtimestamp(data['last_login']) + last_login = datetime.datetime.fromtimestamp(data['last_login'], datetime.timezone.utc) except django.core.signing.BadSignature: raise Http404("Invalid or expired auth") @@ -558,7 +559,7 @@ def review_overview(request): reviewer__person__user=request.user, state__in=["assigned", "accepted"], ) - today = Date.today() + today = date_today(DEADLINE_TZINFO) for r in open_review_assignments: r.due = max(0, (today - r.review_request.deadline).days) From 18d867bc25b6a3b3400ffcf9e01ad1cddf8e944e Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 16:53:39 -0300 Subject: [PATCH 17/34] fix: return tz-aware datetime from utc_from_string --- ietf/ipr/mail.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ietf/ipr/mail.py b/ietf/ipr/mail.py index ea3551d56b..f1d8039db9 100644 --- a/ietf/ipr/mail.py +++ b/ietf/ipr/mail.py @@ -3,13 +3,14 @@ import base64 -import email import datetime from dateutil.tz import tzoffset import os -import pytz import re +from email import message_from_bytes +from email.utils import parsedate_tz + from django.template.loader import render_to_string from django.utils.encoding import force_text, force_bytes @@ -50,7 +51,7 @@ def parsedate_to_datetime(date): http://python.readthedocs.org/en/latest/library/email.util.html """ try: - tuple = email.utils.parsedate_tz(date) + tuple = parsedate_tz(date) if not tuple: return None tz = tuple[-1] @@ -62,10 +63,12 @@ def parsedate_to_datetime(date): def utc_from_string(s): date = parsedate_to_datetime(s) - if is_aware(date): - return date.astimezone(pytz.utc).replace(tzinfo=None) + if date is None: + return None + elif is_aware(date): + return date.astimezone(datetime.timezone.utc) else: - return date + return date.replace(tzinfo=datetime.timezone.utc) # ---------------------------------------------------------------- # Email Functions @@ -174,7 +177,7 @@ def process_response_email(msg): a matching value in the reply_to field, associated to an IPR disclosure through IprEvent. Create a Message object for the incoming message and associate it to the original message via new IprEvent""" - message = email.message_from_bytes(force_bytes(msg)) + message = message_from_bytes(force_bytes(msg)) to = message.get('To', '') # exit if this isn't a response we're interested in (with plus addressing) From 18018027e53de9274d3ee42f3236aa6d1da1ddd7 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 16:58:40 -0300 Subject: [PATCH 18/34] fix: specify timezone for constants in ipr_rfc_number() --- ietf/ipr/views.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index 458186949e..4711857efa 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -145,15 +145,14 @@ def ipr_rfc_number(disclosureDate, thirdPartyDisclosureFlag): # made on 1993-07-23, which is more than a year after RFC 1310. # RFC publication date comes from the RFC Editor announcement - # TODO: These times are tzinfo=pytz.utc, but disclosure times are offset-naive ipr_rfc_pub_datetime = { - 1310 : datetime.datetime(1992, 3, 13, 0, 0), - 1802 : datetime.datetime(1994, 3, 23, 0, 0), - 2026 : datetime.datetime(1996, 10, 29, 0, 0), - 3668 : datetime.datetime(2004, 2, 18, 0, 0), - 3979 : datetime.datetime(2005, 3, 2, 2, 23), - 4879 : datetime.datetime(2007, 4, 10, 18, 21), - 8179 : datetime.datetime(2017, 5, 31, 23, 1), + 1310 : datetime.datetime(1992, 3, 13, 0, 0, tzinfo=datetime.timezone.utc), + 1802 : datetime.datetime(1994, 3, 23, 0, 0, tzinfo=datetime.timezone.utc), + 2026 : datetime.datetime(1996, 10, 29, 0, 0, tzinfo=datetime.timezone.utc), + 3668 : datetime.datetime(2004, 2, 18, 0, 0, tzinfo=datetime.timezone.utc), + 3979 : datetime.datetime(2005, 3, 2, 2, 23, tzinfo=datetime.timezone.utc), + 4879 : datetime.datetime(2007, 4, 10, 18, 21, tzinfo=datetime.timezone.utc), + 8179 : datetime.datetime(2017, 5, 31, 23, 1, tzinfo=datetime.timezone.utc), } if disclosureDate < ipr_rfc_pub_datetime[1310]: From 9db84c94d7c614691516cba5fa62a96de0b6b0e7 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 17:05:27 -0300 Subject: [PATCH 19/34] fix: specify tz for ipr deadlines --- ietf/ipr/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index 4711857efa..f6c85bdee6 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -14,7 +14,6 @@ from django.shortcuts import render, get_object_or_404, redirect from django.template.loader import render_to_string from django.urls import reverse as urlreverse -from django.utils import timezone from django.utils.html import escape import debug # pyflakes:ignore @@ -44,6 +43,7 @@ from ietf.utils.mail import send_mail, send_mail_message from ietf.utils.response import permission_denied from ietf.utils.text import text_to_dict +from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO # ---------------------------------------------------------------- # Globals @@ -393,7 +393,7 @@ def email(request, id): type_id = 'msgout', by = request.user.person, disclosure = ipr, - response_due = form.cleaned_data['response_due'], + response_due = datetime_from_date(form.cleaned_data['response_due'], DEADLINE_TZINFO), message = msg, ) @@ -587,7 +587,7 @@ def notify(request, id, type): type_id = form.cleaned_data['type'], by = request.user.person, disclosure = ipr, - response_due = timezone.now().date() + datetime.timedelta(days=30), + response_due = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=30), message = message, ) messages.success(request,'Notifications sent') From d79f3c535102c8794937ec62c1bd3fb1b2cee4c1 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 30 Aug 2022 18:03:38 -0300 Subject: [PATCH 20/34] fix: handle timezones affecting liaisons tests --- ietf/liaisons/forms.py | 14 +++++++++----- ietf/liaisons/tests.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index c66c27593e..3f46b4320f 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -3,7 +3,7 @@ import io -import datetime, os +import os import operator from typing import Union # pyflakes:ignore @@ -34,6 +34,7 @@ from ietf.person.fields import SearchableEmailField from ietf.doc.models import Document, DocAlias from ietf.utils.fields import DatepickerDateField +from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO from functools import reduce ''' @@ -185,9 +186,12 @@ def get_results(self): end_date = self.cleaned_data.get('end_date') events = None if start_date: - events = LiaisonStatementEvent.objects.filter(type='posted', time__gte=start_date) + events = LiaisonStatementEvent.objects.filter( + type='posted', + time__gte=datetime_from_date(start_date, DEADLINE_TZINFO), + ) if end_date: - events = events.filter(time__lte=end_date) + events = events.filter(time__lte=datetime_from_date(end_date, DEADLINE_TZINFO)) elif end_date: events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date) if events: @@ -222,7 +226,7 @@ class LiaisonModelForm(BetterModelForm): to_groups.widget.attrs['data-minimum-input-length'] = 0 deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True) related_to = SearchableLiaisonStatementsField(label='Related Liaison Statement', required=False) - submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today()) + submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=date_today(DEADLINE_TZINFO)) attachments = CustomModelMultipleChoiceField(queryset=Document.objects,label='Attachments', widget=ShowAttachmentsWidget, required=False) attach_title = forms.CharField(label='Title', required=False) attach_file = forms.FileField(label='File', required=False) @@ -538,7 +542,7 @@ def save(self, *args, **kwargs): super(EditLiaisonForm, self).save(*args,**kwargs) if self.has_changed() and 'submitted_date' in self.changed_data: event = self.instance.liaisonstatementevent_set.filter(type='submitted').first() - event.time = self.cleaned_data.get('submitted_date') + event.time = datetime_from_date(self.cleaned_data.get('submitted_date'), DEADLINE_TZINFO) event.save() return self.instance diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index 0b3116fb4b..f65b18c67c 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -1023,7 +1023,7 @@ def test_search(self): LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),]) # Statement 2 s2 = LiaisonStatementEventFactory(type_id='posted', statement__body="That word does not occur here", statement__title="Nor does it occur here") - s2.time=datetime.datetime(2010,1,1) + s2.time=datetime.datetime(2010, 1, 1, tzinfo=datetime.timezone.utc) s2.save() # test list only, no search filters From 3147be409a4ef3d6984c8371277ba62ae21c5dfa Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 13:39:45 -0300 Subject: [PATCH 21/34] fix: treat leap day in get_8989_eligibility_querysets() Manual cherry-pick of 248d6474 --- ietf/nomcom/utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 999ead7a3b..6f71451773 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -540,14 +540,21 @@ def get_8989_eligibility_querysets(date, base_qs): date_as_dt = datetime_from_date(date, DEADLINE_TZINFO) three_of_five_qs = new_three_of_five_eligible(previous_five=previous_five, queryset=base_qs) - three_years_ago = datetime.datetime(date.year - 3, date.month, date.day, tzinfo=DEADLINE_TZINFO) + # If date is Feb 29, neither 3 nor 5 years ago has a Feb 29. Use Feb 28 instead. + if date.month == 2 and date.day == 29: + three_years_ago = datetime.datetime(date.year - 3, 2, 28, tzinfo=DEADLINE_TZINFO) + five_years_ago = datetime.datetime(date.year - 5, 2, 28, tzinfo=DEADLINE_TZINFO) + else: + three_years_ago = datetime.datetime(date.year - 3, date.month, date.day, tzinfo=DEADLINE_TZINFO) + five_years_ago = datetime.datetime(date.year - 5, date.month, date.day, tzinfo=DEADLINE_TZINFO) + officer_qs = base_qs.filter( # is currently an officer Q(role__name_id__in=('chair','secr'), role__group__state_id='active', role__group__type_id='wg', role__group__time__lte=date_as_dt, - ) + ) # was an officer since the given date (I think this is wrong - it looks at when roles _start_, not when roles end) | Q(rolehistory__group__time__gte=three_years_ago, rolehistory__group__time__lte=date_as_dt, @@ -557,7 +564,6 @@ def get_8989_eligibility_querysets(date, base_qs): ) ).distinct() - five_years_ago = datetime.datetime(date.year - 5, date.month, date.day, tzinfo=DEADLINE_TZINFO) rfc_pks = set(DocEvent.objects.filter(type='published_rfc', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk', flat=True)) iesgappr_pks = set(DocEvent.objects.filter(type='iesg_approved', time__gte=five_years_ago, time__lte=date_as_dt).values_list('doc__pk',flat=True)) qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks)) From 324d9feb51287d08a6b724b47dd7e207987bedb2 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 12:49:17 -0300 Subject: [PATCH 22/34] test: treat leap day properly in nomcom tests --- ietf/nomcom/tests.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 5c4e1ba2d7..0f70aeda8c 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -2453,8 +2453,13 @@ def test_elig_by_office_closed_groups(self): for nomcom in self.nomcoms: elig_date=get_eligibility_date(nomcom) day_before = elig_date-datetime.timedelta(days=1) - year_before = datetime.date(elig_date.year-1,elig_date.month,elig_date.day) - three_years_before = datetime.date(elig_date.year-3,elig_date.month,elig_date.day) + # special case for Feb 29 + if elig_date.month == 2 and elig_date.day == 29: + year_before = datetime.date(elig_date.year - 1, 2, 28) + three_years_before = datetime.date(elig_date.year - 3, 2, 28) + else: + year_before = datetime.date(elig_date.year - 1, elig_date.month, elig_date.day) + three_years_before = datetime.date(elig_date.year - 3, elig_date.month, elig_date.day) just_after_three_years_before = three_years_before + datetime.timedelta(days=1) just_before_three_years_before = three_years_before - datetime.timedelta(days=1) @@ -2514,10 +2519,15 @@ def test_elig_by_author(self): elig_date = get_eligibility_date(nomcom) last_date = elig_date - first_date = datetime.date(last_date.year-5,last_date.month,last_date.day) + # special case for Feb 29 + if last_date.month == 2 and last_date.day == 29: + first_date = datetime.date(last_date.year - 5, 2, 28) + middle_date = datetime.date(last_date.year - 3, 2, 28) + else: + first_date = datetime.date(last_date.year - 5, last_date.month, last_date.day) + middle_date = datetime.date(last_date.year - 3, last_date.month, last_date.day) day_after_last_date = last_date+datetime.timedelta(days=1) day_before_first_date = first_date-datetime.timedelta(days=1) - middle_date = datetime.date(last_date.year-3,last_date.month,last_date.day) eligible = set() ineligible = set() @@ -2664,7 +2674,14 @@ def test_decorate_volunteers_with_qualifications(self): author_person = PersonFactory() for i in range(2): da = WgDocumentAuthorFactory(person=author_person) - DocEventFactory(type='published_rfc',doc=da.document,time=datetime.date(elig_date.year-3,elig_date.month,elig_date.day)) + DocEventFactory( + type='published_rfc', + doc=da.document, + time=datetime.date( + elig_date.year - 3, + elig_date.month, + 28 if elig_date.month == 2 and elig_date.day == 29 else elig_date.day, + )) nomcom.volunteer_set.create(person=author_person) volunteers = nomcom.volunteer_set.all() From 0405eaf182bde3da0eb98cd9ad52780a523417df Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 13:55:04 -0300 Subject: [PATCH 23/34] fix: fix timezone handling affecting nomcom tests --- ietf/nomcom/tests.py | 37 +++++++++++++++++++++---------------- ietf/nomcom/utils.py | 4 ++-- ietf/nomcom/views.py | 4 ++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 0f70aeda8c..5c52fb4659 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -50,6 +50,8 @@ from ietf.stats.factories import MeetingRegistrationFactory from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent +from ietf.utils.timezone import datetime_today, datetime_from_date, DEADLINE_TZINFO + client_test_cert_files = None @@ -1092,7 +1094,7 @@ def setUp(self): rai = Position.objects.get(nomcom=self.nomcom,name='RAI') iab = Position.objects.get(nomcom=self.nomcom,name='IAB') - today = datetime.date.today() + today = datetime_today() t_minus_3 = today - datetime.timedelta(days=3) t_minus_4 = today - datetime.timedelta(days=4) e1 = EmailFactory(address="nominee1@example.org", person=PersonFactory(name="Nominee 1"), origin='test') @@ -2418,7 +2420,8 @@ def test_elig_by_office_active_groups(self): nobody=PersonFactory() for nomcom in self.nomcoms: - before_elig_date = nomcom.first_call_for_volunteers - datetime.timedelta(days=5) + elig_datetime = datetime_from_date(nomcom.first_call_for_volunteers, DEADLINE_TZINFO) + before_elig_date = elig_datetime - datetime.timedelta(days=5) chair = RoleFactory(name_id='chair',group__time=before_elig_date).person @@ -2436,7 +2439,7 @@ def test_elig_by_office_active_groups(self): def test_elig_by_office_edge(self): for nomcom in self.nomcoms: - elig_date=get_eligibility_date(nomcom) + elig_date = datetime_from_date(get_eligibility_date(nomcom), DEADLINE_TZINFO) day_after = elig_date + datetime.timedelta(days=1) two_days_after = elig_date + datetime.timedelta(days=2) @@ -2451,15 +2454,15 @@ def test_elig_by_office_edge(self): def test_elig_by_office_closed_groups(self): for nomcom in self.nomcoms: - elig_date=get_eligibility_date(nomcom) + elig_date=datetime_from_date(get_eligibility_date(nomcom), DEADLINE_TZINFO) day_before = elig_date-datetime.timedelta(days=1) # special case for Feb 29 if elig_date.month == 2 and elig_date.day == 29: - year_before = datetime.date(elig_date.year - 1, 2, 28) - three_years_before = datetime.date(elig_date.year - 3, 2, 28) + year_before = elig_date.replace(year=elig_date.year - 1, day=28) + three_years_before = elig_date.replace(year=elig_date.year - 3, day=28) else: - year_before = datetime.date(elig_date.year - 1, elig_date.month, elig_date.day) - three_years_before = datetime.date(elig_date.year - 3, elig_date.month, elig_date.day) + year_before = elig_date.replace(year=elig_date.year - 1) + three_years_before = elig_date.replace(year=elig_date.year - 3) just_after_three_years_before = three_years_before + datetime.timedelta(days=1) just_before_three_years_before = three_years_before - datetime.timedelta(days=1) @@ -2518,14 +2521,14 @@ def test_elig_by_author(self): for nomcom in self.nomcoms: elig_date = get_eligibility_date(nomcom) - last_date = elig_date + last_date = datetime_from_date(elig_date, DEADLINE_TZINFO) # special case for Feb 29 if last_date.month == 2 and last_date.day == 29: - first_date = datetime.date(last_date.year - 5, 2, 28) - middle_date = datetime.date(last_date.year - 3, 2, 28) + first_date = last_date.replace(year = last_date.year - 5, day=28) + middle_date = last_date.replace(year=first_date.year - 3, day=28) else: - first_date = datetime.date(last_date.year - 5, last_date.month, last_date.day) - middle_date = datetime.date(last_date.year - 3, last_date.month, last_date.day) + first_date = last_date.replace(year=last_date.year - 5) + middle_date = last_date.replace(year=first_date.year - 3) day_after_last_date = last_date+datetime.timedelta(days=1) day_before_first_date = first_date-datetime.timedelta(days=1) @@ -2665,7 +2668,7 @@ def test_decorate_volunteers_with_qualifications(self): office_person = PersonFactory() RoleHistoryFactory( name_id='chair', - group__time= elig_date - datetime.timedelta(days=365), + group__time=datetime_from_date(elig_date) - datetime.timedelta(days=365), group__group__state_id='conclude', person=office_person, ) @@ -2677,11 +2680,13 @@ def test_decorate_volunteers_with_qualifications(self): DocEventFactory( type='published_rfc', doc=da.document, - time=datetime.date( + time=datetime.datetime( elig_date.year - 3, elig_date.month, 28 if elig_date.month == 2 and elig_date.day == 29 else elig_date.day, - )) + tzinfo=datetime.timezone.utc, + ) + ) nomcom.volunteer_set.create(person=author_person) volunteers = nomcom.volunteer_set.all() diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 6f71451773..6fc7dd4543 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -31,7 +31,7 @@ from ietf.utils.mail import send_mail_text, send_mail, get_payload_text from ietf.utils.log import log from ietf.person.name import unidecode_name -from ietf.utils.timezone import datetime_from_date, DEADLINE_TZINFO +from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO import debug # pyflakes:ignore @@ -606,7 +606,7 @@ def get_eligibility_date(nomcom=None, date=None): else: return datetime.date(next_nomcom_year,5,1) else: - return datetime.date(datetime.date.today().year,5,1) + return datetime.date(datetime_today().year,5,1) def previous_five_meetings(date = None): if date is None: diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 5448c1a3b7..fc3b0fb617 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -951,7 +951,7 @@ def view_feedback_topic(request, year, topic_id): feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',]) last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first() - last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1) + last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc) if last_seen: last_seen.save() else: @@ -973,7 +973,7 @@ def view_feedback_nominee(request, year, nominee_id): feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES) last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first() - last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1,month=1,day=1) + last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc) if last_seen: last_seen.save() else: From e7d879336fa28f25eb4a8a738d7440b15bc3b68e Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 14:05:00 -0300 Subject: [PATCH 24/34] test: fix timezone handling in review tests --- ietf/review/tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ietf/review/tests.py b/ietf/review/tests.py index 1ecdb4040f..260a8646d0 100644 --- a/ietf/review/tests.py +++ b/ietf/review/tests.py @@ -5,6 +5,7 @@ from ietf.group.factories import RoleFactory from ietf.utils.mail import empty_outbox, get_payload_text, outbox from ietf.utils.test_utils import TestCase, reload_db_objects +from ietf.utils.timezone import datetime_from_date from .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory from .mailarch import hash_list_message_id from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod @@ -408,7 +409,7 @@ def test_send_review_reminder_overdue_assignment(self): review_request__state_id='assigned', review_request__deadline=self.deadline, state_id='assigned', - assigned_on=self.deadline, + assigned_on=datetime_from_date(self.deadline), reviewer=self.reviewer.email_set.first(), ).review_request.team second_team.reviewteamsettings.delete() # prevent it from being sent reminders @@ -420,7 +421,7 @@ def test_send_review_reminder_overdue_assignment(self): review_request__state_id='assigned', review_request__deadline=not_overdue, state_id='assigned', - assigned_on=not_overdue, + assigned_on=datetime_from_date(not_overdue), reviewer=self.reviewer.email_set.first(), ) ReviewAssignmentFactory( @@ -428,7 +429,7 @@ def test_send_review_reminder_overdue_assignment(self): review_request__state_id='assigned', review_request__deadline=not_overdue, state_id='assigned', - assigned_on=not_overdue, + assigned_on=datetime_from_date(not_overdue), reviewer=self.reviewer.email_set.first(), ) @@ -439,7 +440,7 @@ def test_send_review_reminder_overdue_assignment(self): review_request__state_id='assigned', review_request__deadline=in_grace_period, state_id='assigned', - assigned_on=in_grace_period, + assigned_on=datetime_from_date(in_grace_period), reviewer=self.reviewer.email_set.first(), ) ReviewAssignmentFactory( @@ -447,7 +448,7 @@ def test_send_review_reminder_overdue_assignment(self): review_request__state_id='assigned', review_request__deadline=in_grace_period, state_id='assigned', - assigned_on=in_grace_period, + assigned_on=datetime_from_date(in_grace_period), reviewer=self.reviewer.email_set.first(), ) From ac4603a1352420a2361f4ef59c7318793d1e8bbb Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 15:12:16 -0300 Subject: [PATCH 25/34] fix: fix timezone handling affecting secr.meetings tests --- ietf/secr/meetings/tests.py | 13 ++++++++----- ietf/secr/meetings/views.py | 24 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ietf/secr/meetings/tests.py b/ietf/secr/meetings/tests.py index 2e90a23dc0..bf052abe42 100644 --- a/ietf/secr/meetings/tests.py +++ b/ietf/secr/meetings/tests.py @@ -138,7 +138,10 @@ def test_edit_meeting(self): "Edit Meeting" meeting = make_meeting_test_data() url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number}) - post_data = dict(number=meeting.number,date='2014-07-20',city='Toronto', + post_data = dict(number=meeting.number, + date='2014-07-20', + city='Toronto', + time_zone='America/Toronto', days=7, idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_01=20, @@ -286,7 +289,7 @@ def test_meetings_times_delete(self): url = reverse('ietf.secr.meetings.views.times_delete',kwargs={ 'meeting_id':meeting.number, 'schedule_name':meeting.schedule.name, - 'time':qs.first().time.strftime("%Y:%m:%d:%H:%M") + 'time':qs.first().time.astimezone(meeting.tz()).strftime("%Y:%m:%d:%H:%M") }) redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={ 'meeting_id':meeting.number, @@ -306,7 +309,7 @@ def test_meetings_times_edit(self): url = reverse('ietf.secr.meetings.views.times_edit',kwargs={ 'meeting_id':72, 'schedule_name':'test-schedule', - 'time':timeslot.time.strftime("%Y:%m:%d:%H:%M") + 'time':timeslot.time.astimezone(meeting.tz()).strftime("%Y:%m:%d:%H:%M") }) self.client.login(username="secretary", password="secretary+password") response = self.client.post(url, { @@ -372,7 +375,7 @@ def test_meetings_misc_session_edit(self): timeslot = session.official_timeslotassignment().timeslot url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk}) redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'}) - new_time = timeslot.time + datetime.timedelta(days=1) + new_time = (timeslot.time + datetime.timedelta(days=1)).astimezone(meeting.tz()) self.client.login(username="secretary", password="secretary+password") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -390,7 +393,7 @@ def test_meetings_misc_session_edit(self): }) self.assertRedirects(response, redirect_url) timeslot = session.official_timeslotassignment().timeslot - self.assertEqual(timeslot.time,new_time) + self.assertEqual(timeslot.time, new_time) def test_meetings_misc_session_delete(self): meeting = make_meeting_test_data() diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index cf6252cc01..64d3af7206 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -342,7 +342,7 @@ def edit_meeting(request, meeting_id): else: form = MeetingModelForm(instance=meeting) - + debug.show('form.errors') return render(request, 'meetings/edit_meeting.html', { 'meeting': meeting, 'form' : form, }, @@ -799,7 +799,13 @@ def get_timeslot_time(form, meeting): day = form.cleaned_data['day'] date = meeting.date + datetime.timedelta(days=int(day)) - return datetime.datetime(date.year,date.month,date.day,time.hour,time.minute) + # accept either pytz or zoneinfo tzinfos until we get rid of pytz + tz = meeting.tz() + if hasattr(tz, 'localize'): + return tz.localize(datetime.datetime(date.year,date.month,date.day,time.hour,time.minute)) + else: + return datetime.datetime(date.year,date.month,date.day,time.hour,time.minute,tzinfo=meeting.tz()) + @role_required('Secretariat') def times_edit(request, meeting_id, schedule_name, time): @@ -810,7 +816,12 @@ def times_edit(request, meeting_id, schedule_name, time): schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name) parts = [ int(x) for x in time.split(':') ] - dtime = datetime.datetime(*parts) + # accept either pytz or zoneinfo tzinfos until we get rid of pytz + tz = meeting.tz() + if hasattr(tz, 'localize'): + dtime = tz.localize(datetime.datetime(*parts)) + else: + dtime = datetime.datetime(*parts, tzinfo=tz) timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime) if request.method == 'POST': @@ -861,7 +872,12 @@ def times_delete(request, meeting_id, schedule_name, time): meeting = get_object_or_404(Meeting, number=meeting_id) parts = [ int(x) for x in time.split(':') ] - dtime = datetime.datetime(*parts) + # accept either pytz or zoneinfo tzinfos until we get rid of pytz + tz = meeting.tz() + if hasattr(tz, 'localize'): + dtime = tz.localize(datetime.datetime(*parts)) + else: + dtime = datetime.datetime(*parts, tzinfo=tz) status = SessionStatusName.objects.get(slug='schedw') if request.method == 'POST' and request.POST['post'] == 'yes': From 8ff4c822a02f13b45f44c2af9e7cb1e3887fc605 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 15:39:28 -0300 Subject: [PATCH 26/34] fix: handle both pytz and zoneinfo timezones in ietf.utils.timezone --- ietf/utils/timezone.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index db2f64b5a2..682f6d5b55 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -12,6 +12,17 @@ DEADLINE_TZINFO = ZoneInfo('PST8PDT') +def make_aware(dt, tzinfo): + """Assign timezone to a naive datetime + + Helper to deal with both pytz and zoneinfo type time zones. Can go away when pytz is removed. + """ + if hasattr(tzinfo, 'localize'): + return tzinfo.localize(dt) # pytz-style + else: + return dt.replace(tzinfo=tzinfo) # zoneinfo- / datetime.timezone-style + + def local_timezone_to_utc(d): """Takes a naive datetime in the local timezone and returns a naive datetime with the corresponding UTC time.""" @@ -44,7 +55,8 @@ def email_time_to_local_timezone(date_string): def datetime_from_date(date, tz=pytz.utc): """Get datetime at midnight on a given date""" - return datetime.datetime(date.year, date.month, date.day, tzinfo=tz) + # accept either pytz or zoneinfo tzinfos until we get rid of pytz + return make_aware(datetime.datetime(date.year, date.month, date.day), tz) def datetime_today(tzinfo=None): From c495e7833d0c678fbb9c2775fe508c9d883e6f8d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 31 Aug 2022 15:44:29 -0300 Subject: [PATCH 27/34] fix: fix timezone handling affecting secr.proceedings tests --- ietf/secr/proceedings/proc_utils.py | 5 +++-- ietf/secr/proceedings/reports.py | 10 ++++++---- ietf/secr/proceedings/tests.py | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py index f1fcae1321..3961f907f8 100644 --- a/ietf/secr/proceedings/proc_utils.py +++ b/ietf/secr/proceedings/proc_utils.py @@ -25,6 +25,7 @@ from ietf.person.models import Person from ietf.utils.log import log from ietf.utils.mail import send_mail +from ietf.utils.timezone import make_aware AUDIO_FILE_RE = re.compile(r'ietf(?P[\d]+)-(?P.*)-(?P