fix: fix failing tests and eliminate naive datetime warnings (#4402)

* test: fix timestamp construction in several doc tests

* refactor: rename date2datetime to datetime_from_date and clarify code

* chore: helper to get tzinfo for PRODUCTION_TIMEZONE

* fix: fix timezone handling in make_last_call()

* test: fix datetime generation in doc.tests_charter

* refactor: remove PRODUCTION_TIMEZONE setting

Replaces the PRODUCTION_TIMEZONE setting with a constant,
DEADLINE_TZINFO, in ietf.utils.timezone.

* test: be more careful about timezone in tests_charter.py

* test: be more careful about timezone in doc/tests.py

* fix: fix timezone handling affecting doc.tests_draft

* fix: fix timezone handling affecting tests_irsg_ballot.py

* fix: fix timezone handling affecting tests_review.py

* fix: fix timezone handling affecting last ietf.doc tests

* fix: fix timezone handling affecting last ietf.group tests

* fix: fix timezone handling affecting ietf.iesg tests

* fix: handle timezones in get_8989_eligibility_querysets

* fix: handle timezones affecting ietfauth tests

* fix: return tz-aware datetime from utc_from_string

* fix: specify timezone for constants in ipr_rfc_number()

* fix: specify tz for ipr deadlines

* fix: handle timezones affecting liaisons tests

* fix: treat leap day in get_8989_eligibility_querysets()

Manual cherry-pick of 248d6474

* test: treat leap day properly in nomcom tests

* fix: fix timezone handling affecting nomcom tests

* test: fix timezone handling in review tests

* fix: fix timezone handling affecting secr.meetings tests

* fix: handle both pytz and zoneinfo timezones in ietf.utils.timezone

* fix: fix timezone handling affecting secr.proceedings tests

* refactor: use make_aware() helper in secr.meetings tests

* test: fix timezone handling in secr.telechat tests

* fix: fix timezone handling affecting stats tests

* fix: eliminate tz-naive helpers affecting sync email parsing

* fix: include timezone data when serializing DeletedEvent data

* fix: fix timezone handling affecting sync tests

* style: remove unused import
This commit is contained in:
Jennifer Richards 2022-09-01 13:07:28 -03:00 committed by GitHub
parent a4ecccb061
commit 32054111df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 346 additions and 175 deletions

View file

@ -18,6 +18,7 @@ from ietf.person.models import Person
from ietf.meeting.models import Meeting from ietf.meeting.models import Meeting
from ietf.doc.utils import add_state_change_event, update_action_holders from ietf.doc.utils import add_state_change_event, update_action_holders
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
nonexpirable_states: Optional[List[State]] = None nonexpirable_states: Optional[List[State]] = None
@ -53,13 +54,13 @@ def expirable_drafts(queryset=None):
def get_soon_to_expire_drafts(days_of_warning): 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) end_date = start_date + datetime.timedelta(days_of_warning)
return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date) return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date)
def get_expired_drafts(): 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): def in_draft_expire_freeze(when=None):
if when == None: if when == None:

View file

@ -0,0 +1,61 @@
# Generated by Django 2.2.28 on 2022-08-31 20:26
import datetime
import json
from zoneinfo import ZoneInfo
from django.db import migrations
TZ_BEFORE = ZoneInfo('PST8PDT')
def forward(apps, schema_editor):
DeletedEvent = apps.get_model('doc', 'DeletedEvent')
for deleted_event in DeletedEvent.objects.all():
fields = json.loads(deleted_event.json)
replacements = {}
for k, v in fields.items():
if isinstance(v, str):
try:
dt = datetime.datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
except:
pass
else:
replacements[k] = dt.replace(tzinfo=TZ_BEFORE).astimezone(datetime.timezone.utc).isoformat()
if len(replacements) > 0:
fields.update(replacements)
deleted_event.json = json.dumps(fields)
deleted_event.save()
def reverse(apps, schema_editor):
DeletedEvent = apps.get_model('doc', 'DeletedEvent')
for deleted_event in DeletedEvent.objects.all():
fields = json.loads(deleted_event.json)
replacements = {}
for k, v in fields.items():
if isinstance(v, str) and 'T' in v:
try:
dt = datetime.datetime.fromisoformat(v)
except:
pass
else:
replacements[k] = dt.astimezone(TZ_BEFORE).replace(tzinfo=None).strftime('%Y-%m-%d %H:%M:%S')
if len(replacements) > 0:
fields.update(replacements)
deleted_event.json = json.dumps(fields)
deleted_event.save()
class Migration(migrations.Migration):
dependencies = [
('doc', '0045_use_timezone_now_for_doc_models'),
('utils', '0003_pause_to_change_use_tz'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -18,6 +18,7 @@ from pyquery import PyQuery
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from collections import defaultdict from collections import defaultdict
from zoneinfo import ZoneInfo
from django.core.management import call_command from django.core.management import call_command
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
@ -57,6 +58,8 @@ from ietf.utils.mail import outbox, empty_outbox
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.text import normalize_text from ietf.utils.text import normalize_text
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
class SearchTests(TestCase): class SearchTests(TestCase):
def test_search(self): def test_search(self):
@ -1428,6 +1431,8 @@ Man Expires September 22, 2015 [Page 3]
def test_draft_group_link(self): def test_draft_group_link(self):
"""Link to group 'about' page should have correct format""" """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']: for group_type_id in ['wg', 'rg', 'ag']:
group = GroupFactory(type_id=group_type_id) group = GroupFactory(type_id=group_type_id)
draft = WgDraftFactory(name='draft-document-%s' % group_type_id, group=group) draft = WgDraftFactory(name='draft-document-%s' % group_type_id, group=group)
@ -1436,7 +1441,7 @@ Man Expires September 22, 2015 [Page 3]
self.assert_correct_wg_group_link(r, group) self.assert_correct_wg_group_link(r, group)
rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=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 # get the rfc name to avoid a redirect
rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name 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))) r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
@ -1451,7 +1456,7 @@ Man Expires September 22, 2015 [Page 3]
self.assert_correct_non_wg_group_link(r, group) self.assert_correct_non_wg_group_link(r, group)
rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=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 # get the rfc name to avoid a redirect
rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name 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))) r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
@ -1837,7 +1842,7 @@ class DocTestCase(TestCase):
desc="Last call\x0b", # include a control character to be sure it does not break anything desc="Last call\x0b", # include a control character to be sure it does not break anything
type="sent_last_call", type="sent_last_call",
by=Person.objects.get(user__username="secretary"), by=Person.objects.get(user__username="secretary"),
expires=datetime.date.today() + datetime.timedelta(days=7)) expires=datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=7))
r = self.client.get("/feed/last-call/") r = self.client.get("/feed/last-call/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -1885,10 +1890,14 @@ class DocTestCase(TestCase):
#other_aliases = ['rfc6020',], #other_aliases = ['rfc6020',],
states = [('draft','rfc'),('draft-iesg','pub')], states = [('draft','rfc'),('draft-iesg','pub')],
std_level_id = 'ps', 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() 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)) url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name))
r = self.client.get(url) r = self.client.get(url)
@ -1906,10 +1915,14 @@ class DocTestCase(TestCase):
stream_id = 'ise', stream_id = 'ise',
states = [('draft','rfc'),('draft-iesg','pub')], states = [('draft','rfc'),('draft-iesg','pub')],
std_level_id = 'inf', 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() 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)) url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name))
r = self.client.get(url) r = self.client.get(url)
@ -2044,7 +2057,9 @@ class GenerateDraftAliasesTests(TestCase):
super().tearDown() super().tearDown()
def testManagementCommand(self): 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 ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
shepherd = PersonFactory() shepherd = PersonFactory()
author1 = PersonFactory() author1 = PersonFactory()
@ -2059,9 +2074,9 @@ class GenerateDraftAliasesTests(TestCase):
doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad) doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad)
doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], 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) 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")) 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)) 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 = '2010-10-10') DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=tz))
doc5 = IndividualDraftFactory(authors=[author6]) doc5 = IndividualDraftFactory(authors=[author6])
args = [ ] args = [ ]

View file

@ -26,6 +26,8 @@ from ietf.person.models import Person
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.timezone import datetime_today, date_today, DEADLINE_TZINFO
class ViewCharterTests(TestCase): class ViewCharterTests(TestCase):
def test_view_revisions(self): def test_view_revisions(self):
@ -402,7 +404,7 @@ class EditCharterTests(TestCase):
# Make it so that the charter has been through internal review, and passed its external review # Make it so that the charter has been through internal review, and passed its external review
# ballot on a previous telechat # ballot on a previous telechat
last_week = datetime.date.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, 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'), ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'),
time=last_week) time=last_week)
@ -746,7 +748,7 @@ class EditCharterTests(TestCase):
charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev")) charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev"))
due_date = datetime.date.today() + datetime.timedelta(days=180) due_date = date_today(DEADLINE_TZINFO) + datetime.timedelta(days=180)
m1 = GroupMilestone.objects.create(group=group, m1 = GroupMilestone.objects.create(group=group,
state_id="active", state_id="active",
desc="Has been copied", desc="Has been copied",
@ -826,7 +828,7 @@ class EditCharterTests(TestCase):
m = GroupMilestone.objects.create(group=charter.group, m = GroupMilestone.objects.create(group=charter.group,
state_id="active", state_id="active",
desc="Test milestone", desc="Test milestone",
due=datetime.date.today(), due=date_today(DEADLINE_TZINFO),
resolved="") resolved="")
url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev)) url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev))

View file

@ -34,6 +34,7 @@ from ietf.iesg.models import TelechatDate
from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.timezone import date_today, datetime_from_date
class ChangeStateTests(TestCase): class ChangeStateTests(TestCase):
@ -402,11 +403,11 @@ class EditInfoTests(TestCase):
# change to a telechat that should cause returning item to be auto-detected # change to a telechat that should cause returning item to be auto-detected
# First, make it appear that the previous telechat has already passed # 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() telechat_event.save()
ad = Person.objects.get(user__username="ad") ad = Person.objects.get(user__username="ad")
ballot = create_ballot_if_not_open(None, draft, ad, 'approve') 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() ballot.save()
r = self.client.post(url, data) r = self.client.post(url, data)
@ -429,7 +430,7 @@ class EditInfoTests(TestCase):
self.assertTrue("Telechat update" in outbox[-1]['Subject']) self.assertTrue("Telechat update" in outbox[-1]['Subject'])
# Put it on an agenda that's very soon from now # 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 = TelechatDate.objects.active()[0]
td.date = next_week td.date = next_week
td.save() td.save()

View file

@ -19,6 +19,7 @@ from ietf.doc.utils import create_ballot_if_not_open, close_ballot
from ietf.person.utils import get_active_irsg, get_active_ads from ietf.person.utils import get_active_irsg, get_active_ads
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
class IssueIRSGBallotTests(TestCase): class IssueIRSGBallotTests(TestCase):
@ -254,7 +255,7 @@ class IssueIRSGBallotTests(TestCase):
irsgmember = get_active_irsg()[0] irsgmember = get_active_irsg()[0]
secr = RoleFactory(group__acronym='secretariat',name_id='secr') secr = RoleFactory(group__acronym='secretariat',name_id='secr')
wg_ballot = create_ballot_if_not_open(None, wg_draft, ad.person, 'approve') 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) 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)) url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=wg_ballot.pk))

View file

@ -10,7 +10,6 @@ import email.mime.multipart, email.mime.text, email.utils
from mock import patch from mock import patch
from requests import Response from requests import Response
from django.apps import apps from django.apps import apps
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.conf import settings from django.conf import settings
@ -39,6 +38,7 @@ from ietf.utils.mail import outbox, empty_outbox, parseaddr, on_behalf_of, get_p
from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.text import strip_prefix, xslugify from ietf.utils.text import strip_prefix, xslugify
from ietf.utils.timezone import DEADLINE_TZINFO
from django.utils.html import escape from django.utils.html import escape
class ReviewTests(TestCase): class ReviewTests(TestCase):
@ -734,7 +734,7 @@ class ReviewTests(TestCase):
# The secretary is allowed to set a custom completion date (#2590) # The secretary is allowed to set a custom completion date (#2590)
assignment = reload_db_objects(assignment) assignment = reload_db_objects(assignment)
self.assertEqual(assignment.state_id, "completed") 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: # There should be two events:
# - the event logging when the change when it was entered, i.e. very close to now. # - the event logging when the change when it was entered, i.e. very close to now.
@ -742,7 +742,7 @@ class ReviewTests(TestCase):
events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-time') events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-time')
event0_time_diff = timezone.now() - events[0].time event0_time_diff = timezone.now() - events[0].time
self.assertLess(event0_time_diff, datetime.timedelta(seconds=10)) 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: 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") self.assertEqual(f.read(), "This is a review\nwith two lines")

View file

@ -143,7 +143,7 @@ class ActionHoldersTests(TestCase):
doc = self.doc_in_iesg_state('pub-req') doc = self.doc_in_iesg_state('pub-req')
doc.action_holders.set([self.ad]) doc.action_holders.set([self.ad])
dah = doc.documentactionholder_set.get(person=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() dah.save()
self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today()) self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today())

View file

@ -39,6 +39,7 @@ from ietf.review.models import ReviewWish
from ietf.utils import draft, log from ietf.utils import draft, log
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.mailtrigger.utils import gather_address_lists 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 from ietf.utils.xmldraft import XMLDraft
@ -637,10 +638,21 @@ def has_same_ballot(doc, date1, date2=None):
""" Test if the most recent ballot created before the end of date1 """ Test if the most recent ballot created before the end of date1
is the same as the most recent ballot created before the is the same as the most recent ballot created before the
end of date 2. """ end of date 2. """
datetime1 = datetime_from_date(date1, DEADLINE_TZINFO)
if date2 is None: if date2 is None:
date2 = datetime.date.today() datetime2 = datetime_today(DEADLINE_TZINFO)
ballot1 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date1+datetime.timedelta(days=1)) else:
ballot2 = doc.latest_event(BallotDocEvent,type='created_ballot',time__lt=date2+datetime.timedelta(days=1)) 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 return ballot1 == ballot2
def make_notify_changed_event(request, doc, by, new_notify, time=None): 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 on_agenda
and prev_agenda and prev_agenda
and new_telechat_date != prev_telechat 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) and has_same_ballot(doc,prev.telechat_date)
): ):
returning = True returning = True

View file

@ -40,6 +40,8 @@ from ietf.person.models import Person
from ietf.utils.mail import send_mail_text, send_mail_preformatted from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.utils.decorators import require_api_key from ietf.utils.decorators import require_api_key
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO
BALLOT_CHOICES = (("yes", "Yes"), BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"), ("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):<br><br>" % expiration_date e.desc = "The following Last Call announcement was sent out (ends %s):<br><br>" % expiration_date
e.desc += announcement e.desc += announcement
if form.cleaned_data['last_call_sent_date'] != e.time.date(): e_production_time = e.time.astimezone(DEADLINE_TZINFO)
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) if form.cleaned_data['last_call_sent_date'] != e_production_time.date():
e.expires = expiration_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, DEADLINE_TZINFO)
e.save() e.save()
events.append(e) events.append(e)
@ -1108,7 +1112,7 @@ def issue_irsg_ballot(request, name):
raise Http404 raise Http404
by = request.user.person 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': if request.method == 'POST':
button = request.POST.get("irsg_button") button = request.POST.get("irsg_button")
@ -1117,7 +1121,7 @@ def issue_irsg_ballot(request, name):
e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person) e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person)
if (duedate == None or duedate==""): if (duedate == None or duedate==""):
duedate = str(fillerdate) 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.type = "created_ballot"
e.desc = "Created IRSG Ballot" e.desc = "Created IRSG Ballot"
ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve") ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve")

View file

@ -53,6 +53,8 @@ from ietf.utils.mail import send_mail, send_mail_message, on_behalf_of
from ietf.utils.textupload import get_cleaned_text_file_content from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.utils import log from ietf.utils import log
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
class ChangeStateForm(forms.Form): class ChangeStateForm(forms.Form):
state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True) 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 due_date = None
if form.cleaned_data["weeks"] != 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) update_reminder(doc, "stream-s", e, due_date)
@ -1668,7 +1670,7 @@ def change_stream_state(request, name, state_type):
due_date = None due_date = None
if form.cleaned_data["weeks"] != 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) update_reminder(doc, "stream-s", e, due_date)

View file

@ -54,6 +54,8 @@ from ietf.utils.mail import send_mail_message
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.fields import MultiEmailField from ietf.utils.fields import MultiEmailField
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import DEADLINE_TZINFO
def clean_doc_revision(doc, rev): def clean_doc_revision(doc, rev):
if rev: if rev:
@ -768,7 +770,11 @@ def complete_review(request, name, assignment_id=None, acronym=None):
completion_datetime = timezone.now() completion_datetime = timezone.now()
if "completion_date" in form.cleaned_data: 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 # complete assignment
assignment.state = form.cleaned_data["state"] assignment.state = form.cleaned_data["state"]

View file

@ -26,6 +26,7 @@ from ietf.person.factories import PersonFactory, EmailFactory
from ietf.doc.factories import DocumentFactory from ietf.doc.factories import DocumentFactory
from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory 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 from django.utils.html import escape
class ReviewTests(TestCase): class ReviewTests(TestCase):
@ -156,7 +157,7 @@ class ReviewTests(TestCase):
review_request__doc=review_req1.doc, review_request__doc=review_req1.doc,
review_request__team=review_req1.team, review_request__team=review_req1.team,
review_request__type_id="early", 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__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
@ -166,7 +167,7 @@ class ReviewTests(TestCase):
UnavailablePeriod.objects.create( UnavailablePeriod.objects.create(
team=review_req1.team, team=review_req1.team,
person=reviewer, person=reviewer,
start_date=datetime.date.today() - datetime.timedelta(days=10), start_date=date_today() - datetime.timedelta(days=10),
availability="unavailable", availability="unavailable",
) )
@ -211,7 +212,7 @@ class ReviewTests(TestCase):
review_request__doc=review_req2.doc, review_request__doc=review_req2.doc,
review_request__team=review_req2.team, review_request__team=review_req2.team,
review_request__type_id="lc", 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__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "no-response", state_id = "no-response",
@ -232,15 +233,15 @@ class ReviewTests(TestCase):
review_req3 = ReviewRequestFactory(state_id='completed', team=team) review_req3 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req3.doc, 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__team=review_req3.team,
review_request__type_id="telechat", 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__state_id="completed",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "completed", state_id = "completed",
reviewer=reviewer.email_set.first(), 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -253,15 +254,15 @@ class ReviewTests(TestCase):
for i in range(10): for i in range(10):
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=reqs[i].doc, 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__team=reqs[i].team,
review_request__type_id="telechat", 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__state_id="completed",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "completed", state_id = "completed",
reviewer=reviewer.email_set.first(), 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -305,28 +306,28 @@ class ReviewTests(TestCase):
review_req4 = ReviewRequestFactory(state_id='completed', team=team) review_req4 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req4.doc, 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__team=review_req4.team,
review_request__type_id="lc", 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__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
reviewer=reviewer.email_set.first(), 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) review_req5 = ReviewRequestFactory(state_id='completed', team=team)
ReviewAssignmentFactory( ReviewAssignmentFactory(
review_request__doc=review_req5.doc, 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__team=review_req5.team,
review_request__type_id="lc", 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__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"), review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted", state_id = "accepted",
reviewer=reviewer.email_set.first(), 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)

View file

@ -119,6 +119,7 @@ from ietf.settings import MAILING_LIST_INFO_URL
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.text import strip_suffix from ietf.utils.text import strip_suffix
from ietf.utils import markdown from ietf.utils import markdown
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
# --- Helpers ---------------------------------------------------------- # --- Helpers ----------------------------------------------------------
@ -1421,11 +1422,14 @@ def review_requests(request, acronym, group_type=None):
}[since] }[since]
closed_review_requests = closed_review_requests.filter( closed_review_requests = closed_review_requests.filter(
Q(reviewrequestdocevent__type='closed_review_request', reviewrequestdocevent__time__gte=datetime.date.today() - date_limit) Q(reviewrequestdocevent__type='closed_review_request',
| Q(reviewrequestdocevent__isnull=True, time__gte=datetime.date.today() - date_limit) reviewrequestdocevent__time__gte=datetime_today(DEADLINE_TZINFO) - date_limit)
| Q(reviewrequestdocevent__isnull=True, time__gte=datetime_today(DEADLINE_TZINFO) - date_limit)
).distinct() ).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', return render(request, 'group/review_requests.html',
construct_group_menu_context(request, group, "review requests", group_type, { construct_group_menu_context(request, group, "review requests", group_type, {

View file

@ -63,6 +63,7 @@ from ietf.iesg.utils import telechat_page_count
from ietf.ietfauth.utils import has_role, role_required, user_is_person from ietf.ietfauth.utils import has_role, role_required, user_is_person
from ietf.person.models import Person from ietf.person.models import Person
from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date 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): def review_decisions(request, year=None):
events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved")) events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved"))
@ -73,9 +74,9 @@ def review_decisions(request, year=None):
year = int(year) year = int(year)
events = events.filter(time__year=year) events = events.filter(time__year=year)
else: else:
d = datetime.date.today() - datetime.timedelta(days=185) d = date_today() - datetime.timedelta(days=185)
d = datetime.date(d.year, d.month, 1) 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") events = events.select_related("doc", "doc__intended_std_level").order_by("-time", "-id")

View file

@ -34,9 +34,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import datetime
import importlib import importlib
from datetime import date as Date, datetime as DateTime
# needed if we revert to higher barrier for account creation # needed if we revert to higher barrier for account creation
#from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date #from datetime import datetime as DateTime, timedelta as TimeDelta, date as Date
from collections import defaultdict from collections import defaultdict
@ -79,6 +79,7 @@ from ietf.doc.fields import SearchableDocumentField
from ietf.utils.decorators import person_required from ietf.utils.decorators import person_required
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.utils.validators import validate_external_resource_value 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 # 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') emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time')
new_email_forms = [] 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(): if nc and nc.volunteer_set.filter(person=person).exists():
volunteer_status = 'volunteered' volunteer_status = 'volunteered'
elif nc and nc.is_accepting_volunteers: elif nc and nc.is_accepting_volunteers:
@ -456,7 +457,7 @@ def confirm_password_reset(request, auth):
password = data['password'] password = data['password']
last_login = None last_login = None
if data['last_login']: 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: except django.core.signing.BadSignature:
raise Http404("Invalid or expired auth") raise Http404("Invalid or expired auth")
@ -558,7 +559,7 @@ def review_overview(request):
reviewer__person__user=request.user, reviewer__person__user=request.user,
state__in=["assigned", "accepted"], state__in=["assigned", "accepted"],
) )
today = Date.today() today = date_today(DEADLINE_TZINFO)
for r in open_review_assignments: for r in open_review_assignments:
r.due = max(0, (today - r.review_request.deadline).days) r.due = max(0, (today - r.review_request.deadline).days)

View file

@ -3,13 +3,14 @@
import base64 import base64
import email
import datetime import datetime
from dateutil.tz import tzoffset from dateutil.tz import tzoffset
import os import os
import pytz
import re import re
from email import message_from_bytes
from email.utils import parsedate_tz
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import force_text, force_bytes 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 http://python.readthedocs.org/en/latest/library/email.util.html
""" """
try: try:
tuple = email.utils.parsedate_tz(date) tuple = parsedate_tz(date)
if not tuple: if not tuple:
return None return None
tz = tuple[-1] tz = tuple[-1]
@ -62,10 +63,12 @@ def parsedate_to_datetime(date):
def utc_from_string(s): def utc_from_string(s):
date = parsedate_to_datetime(s) date = parsedate_to_datetime(s)
if is_aware(date): if date is None:
return date.astimezone(pytz.utc).replace(tzinfo=None) return None
elif is_aware(date):
return date.astimezone(datetime.timezone.utc)
else: else:
return date return date.replace(tzinfo=datetime.timezone.utc)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# Email Functions # 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 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 IprEvent. Create a Message object for the incoming message and associate it to
the original message via new IprEvent""" 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', '') to = message.get('To', '')
# exit if this isn't a response we're interested in (with plus addressing) # exit if this isn't a response we're interested in (with plus addressing)

View file

@ -14,7 +14,6 @@ from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -44,6 +43,7 @@ from ietf.utils.draft_search import normalize_draftname
from ietf.utils.mail import send_mail, send_mail_message from ietf.utils.mail import send_mail, send_mail_message
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.text import text_to_dict from ietf.utils.text import text_to_dict
from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# Globals # Globals
@ -145,15 +145,14 @@ def ipr_rfc_number(disclosureDate, thirdPartyDisclosureFlag):
# made on 1993-07-23, which is more than a year after RFC 1310. # made on 1993-07-23, which is more than a year after RFC 1310.
# RFC publication date comes from the RFC Editor announcement # 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 = { ipr_rfc_pub_datetime = {
1310 : datetime.datetime(1992, 3, 13, 0, 0), 1310 : datetime.datetime(1992, 3, 13, 0, 0, tzinfo=datetime.timezone.utc),
1802 : datetime.datetime(1994, 3, 23, 0, 0), 1802 : datetime.datetime(1994, 3, 23, 0, 0, tzinfo=datetime.timezone.utc),
2026 : datetime.datetime(1996, 10, 29, 0, 0), 2026 : datetime.datetime(1996, 10, 29, 0, 0, tzinfo=datetime.timezone.utc),
3668 : datetime.datetime(2004, 2, 18, 0, 0), 3668 : datetime.datetime(2004, 2, 18, 0, 0, tzinfo=datetime.timezone.utc),
3979 : datetime.datetime(2005, 3, 2, 2, 23), 3979 : datetime.datetime(2005, 3, 2, 2, 23, tzinfo=datetime.timezone.utc),
4879 : datetime.datetime(2007, 4, 10, 18, 21), 4879 : datetime.datetime(2007, 4, 10, 18, 21, tzinfo=datetime.timezone.utc),
8179 : datetime.datetime(2017, 5, 31, 23, 1), 8179 : datetime.datetime(2017, 5, 31, 23, 1, tzinfo=datetime.timezone.utc),
} }
if disclosureDate < ipr_rfc_pub_datetime[1310]: if disclosureDate < ipr_rfc_pub_datetime[1310]:
@ -394,7 +393,7 @@ def email(request, id):
type_id = 'msgout', type_id = 'msgout',
by = request.user.person, by = request.user.person,
disclosure = ipr, disclosure = ipr,
response_due = form.cleaned_data['response_due'], response_due = datetime_from_date(form.cleaned_data['response_due'], DEADLINE_TZINFO),
message = msg, message = msg,
) )
@ -588,7 +587,7 @@ def notify(request, id, type):
type_id = form.cleaned_data['type'], type_id = form.cleaned_data['type'],
by = request.user.person, by = request.user.person,
disclosure = ipr, disclosure = ipr,
response_due = timezone.now().date() + datetime.timedelta(days=30), response_due = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(days=30),
message = message, message = message,
) )
messages.success(request,'Notifications sent') messages.success(request,'Notifications sent')

View file

@ -3,7 +3,7 @@
import io import io
import datetime, os import os
import operator import operator
from typing import Union # pyflakes:ignore from typing import Union # pyflakes:ignore
@ -34,6 +34,7 @@ from ietf.person.models import Email
from ietf.person.fields import SearchableEmailField from ietf.person.fields import SearchableEmailField
from ietf.doc.models import Document, DocAlias from ietf.doc.models import Document, DocAlias
from ietf.utils.fields import DatepickerDateField from ietf.utils.fields import DatepickerDateField
from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO
from functools import reduce from functools import reduce
''' '''
@ -185,9 +186,12 @@ class SearchLiaisonForm(forms.Form):
end_date = self.cleaned_data.get('end_date') end_date = self.cleaned_data.get('end_date')
events = None events = None
if start_date: 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: 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: elif end_date:
events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date) events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date)
if events: if events:
@ -222,7 +226,7 @@ class LiaisonModelForm(BetterModelForm):
to_groups.widget.attrs['data-minimum-input-length'] = 0 to_groups.widget.attrs['data-minimum-input-length'] = 0
deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True) deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True)
related_to = SearchableLiaisonStatementsField(label='Related Liaison Statement', required=False) 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) attachments = CustomModelMultipleChoiceField(queryset=Document.objects,label='Attachments', widget=ShowAttachmentsWidget, required=False)
attach_title = forms.CharField(label='Title', required=False) attach_title = forms.CharField(label='Title', required=False)
attach_file = forms.FileField(label='File', required=False) attach_file = forms.FileField(label='File', required=False)
@ -538,7 +542,7 @@ class EditLiaisonForm(LiaisonModelForm):
super(EditLiaisonForm, self).save(*args,**kwargs) super(EditLiaisonForm, self).save(*args,**kwargs)
if self.has_changed() and 'submitted_date' in self.changed_data: if self.has_changed() and 'submitted_date' in self.changed_data:
event = self.instance.liaisonstatementevent_set.filter(type='submitted').first() 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() event.save()
return self.instance return self.instance

View file

@ -1023,7 +1023,7 @@ class LiaisonManagementTests(TestCase):
LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),]) LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),])
# Statement 2 # Statement 2
s2 = LiaisonStatementEventFactory(type_id='posted', statement__body="That word does not occur here", statement__title="Nor does it occur here") 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() s2.save()
# test list only, no search filters # test list only, no search filters

View file

@ -41,7 +41,7 @@ from ietf.person.models import Person
from ietf.utils.decorators import memoize from ietf.utils.decorators import memoize
from ietf.utils.storage import NoLocationMigrationFileSystemStorage from ietf.utils.storage import NoLocationMigrationFileSystemStorage
from ietf.utils.text import xslugify 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.models import ForeignKey
from ietf.utils.validators import ( from ietf.utils.validators import (
MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type, MaxImageSizeValidator, WrappedValidator, validate_file_size, validate_mime_type,
@ -152,7 +152,7 @@ class Meeting(models.Model):
cutoff_date = importantdate.date cutoff_date = importantdate.date
else: else:
cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) 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 return cutoff_time
def get_01_cutoff(self): def get_01_cutoff(self):
@ -164,7 +164,7 @@ class Meeting(models.Model):
cutoff_date = importantdate.date cutoff_date = importantdate.date
else: else:
cutoff_date = start_date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) 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 return cutoff_time
def get_reopen_time(self): def get_reopen_time(self):

View file

@ -50,6 +50,8 @@ from ietf.stats.models import MeetingRegistration
from ietf.stats.factories import MeetingRegistrationFactory from ietf.stats.factories import MeetingRegistrationFactory
from ietf.utils.mail import outbox, empty_outbox, get_payload_text 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.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 client_test_cert_files = None
@ -1092,7 +1094,7 @@ class ReminderTest(TestCase):
rai = Position.objects.get(nomcom=self.nomcom,name='RAI') rai = Position.objects.get(nomcom=self.nomcom,name='RAI')
iab = Position.objects.get(nomcom=self.nomcom,name='IAB') 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_3 = today - datetime.timedelta(days=3)
t_minus_4 = today - datetime.timedelta(days=4) t_minus_4 = today - datetime.timedelta(days=4)
e1 = EmailFactory(address="nominee1@example.org", person=PersonFactory(name="Nominee 1"), origin='test') e1 = EmailFactory(address="nominee1@example.org", person=PersonFactory(name="Nominee 1"), origin='test')
@ -2418,7 +2420,8 @@ class rfc8989EligibilityTests(TestCase):
nobody=PersonFactory() nobody=PersonFactory()
for nomcom in self.nomcoms: 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 chair = RoleFactory(name_id='chair',group__time=before_elig_date).person
@ -2436,7 +2439,7 @@ class rfc8989EligibilityTests(TestCase):
def test_elig_by_office_edge(self): def test_elig_by_office_edge(self):
for nomcom in self.nomcoms: 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) day_after = elig_date + datetime.timedelta(days=1)
two_days_after = elig_date + datetime.timedelta(days=2) two_days_after = elig_date + datetime.timedelta(days=2)
@ -2451,10 +2454,15 @@ class rfc8989EligibilityTests(TestCase):
def test_elig_by_office_closed_groups(self): def test_elig_by_office_closed_groups(self):
for nomcom in self.nomcoms: 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) day_before = elig_date-datetime.timedelta(days=1)
year_before = datetime.date(elig_date.year-1,elig_date.month,elig_date.day) # special case for Feb 29
three_years_before = datetime.date(elig_date.year-3,elig_date.month,elig_date.day) if elig_date.month == 2 and elig_date.day == 29:
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 = 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_after_three_years_before = three_years_before + datetime.timedelta(days=1)
just_before_three_years_before = three_years_before - datetime.timedelta(days=1) just_before_three_years_before = three_years_before - datetime.timedelta(days=1)
@ -2513,11 +2521,16 @@ class rfc8989EligibilityTests(TestCase):
for nomcom in self.nomcoms: for nomcom in self.nomcoms:
elig_date = get_eligibility_date(nomcom) elig_date = get_eligibility_date(nomcom)
last_date = elig_date last_date = datetime_from_date(elig_date, DEADLINE_TZINFO)
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 = 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 = 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_after_last_date = last_date+datetime.timedelta(days=1)
day_before_first_date = first_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() eligible = set()
ineligible = set() ineligible = set()
@ -2655,7 +2668,7 @@ class VolunteerDecoratorUnitTests(TestCase):
office_person = PersonFactory() office_person = PersonFactory()
RoleHistoryFactory( RoleHistoryFactory(
name_id='chair', 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', group__group__state_id='conclude',
person=office_person, person=office_person,
) )
@ -2664,7 +2677,16 @@ class VolunteerDecoratorUnitTests(TestCase):
author_person = PersonFactory() author_person = PersonFactory()
for i in range(2): for i in range(2):
da = WgDocumentAuthorFactory(person=author_person) 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.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) nomcom.volunteer_set.create(person=author_person)
volunteers = nomcom.volunteer_set.all() volunteers = nomcom.volunteer_set.all()

View file

@ -31,6 +31,7 @@ from ietf.utils.pipe import pipe
from ietf.utils.mail import send_mail_text, send_mail, get_payload_text from ietf.utils.mail import send_mail_text, send_mail, get_payload_text
from ietf.utils.log import log from ietf.utils.log import log
from ietf.person.name import unidecode_name from ietf.person.name import unidecode_name
from ietf.utils.timezone import datetime_from_date, datetime_today, DEADLINE_TZINFO
import debug # pyflakes:ignore import debug # pyflakes:ignore
@ -536,28 +537,35 @@ def get_8989_eligibility_querysets(date, base_qs):
base_qs = Person.objects.all() base_qs = Person.objects.all()
previous_five = previous_five_meetings(date) 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_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) # 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( officer_qs = base_qs.filter(
# is currently an officer # is currently an officer
Q(role__name_id__in=('chair','secr'), Q(role__name_id__in=('chair','secr'),
role__group__state_id='active', role__group__state_id='active',
role__group__type_id='wg', 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) # 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, | 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__name_id__in=('chair','secr'),
rolehistory__group__state_id='active', rolehistory__group__state_id='active',
rolehistory__group__type_id='wg', rolehistory__group__type_id='wg',
) )
).distinct() ).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_as_dt).values_list('doc__pk', flat=True))
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_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).values_list('doc__pk',flat=True))
qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks)) qualifying_pks = rfc_pks.union(iesgappr_pks.difference(rfc_pks))
author_qs = base_qs.filter( author_qs = base_qs.filter(
documentauthor__document__pk__in=qualifying_pks documentauthor__document__pk__in=qualifying_pks
@ -598,7 +606,7 @@ def get_eligibility_date(nomcom=None, date=None):
else: else:
return datetime.date(next_nomcom_year,5,1) return datetime.date(next_nomcom_year,5,1)
else: else:
return datetime.date(datetime.date.today().year,5,1) return datetime.date(datetime_today().year,5,1)
def previous_five_meetings(date = None): def previous_five_meetings(date = None):
if date is None: if date is None:

View file

@ -951,7 +951,7 @@ def view_feedback_topic(request, year, topic_id):
feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',]) feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',])
last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first() 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: if last_seen:
last_seen.save() last_seen.save()
else: else:
@ -973,7 +973,7 @@ def view_feedback_nominee(request, year, nominee_id):
feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES) 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 = 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: if last_seen:
last_seen.save() last_seen.save()
else: else:

View file

@ -5,6 +5,7 @@ import datetime
from ietf.group.factories import RoleFactory from ietf.group.factories import RoleFactory
from ietf.utils.mail import empty_outbox, get_payload_text, outbox from ietf.utils.mail import empty_outbox, get_payload_text, outbox
from ietf.utils.test_utils import TestCase, reload_db_objects 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 .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory
from .mailarch import hash_list_message_id from .mailarch import hash_list_message_id
from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod
@ -408,7 +409,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=self.deadline, review_request__deadline=self.deadline,
state_id='assigned', state_id='assigned',
assigned_on=self.deadline, assigned_on=datetime_from_date(self.deadline),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
).review_request.team ).review_request.team
second_team.reviewteamsettings.delete() # prevent it from being sent reminders second_team.reviewteamsettings.delete() # prevent it from being sent reminders
@ -420,7 +421,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=not_overdue, review_request__deadline=not_overdue,
state_id='assigned', state_id='assigned',
assigned_on=not_overdue, assigned_on=datetime_from_date(not_overdue),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
ReviewAssignmentFactory( ReviewAssignmentFactory(
@ -428,7 +429,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=not_overdue, review_request__deadline=not_overdue,
state_id='assigned', state_id='assigned',
assigned_on=not_overdue, assigned_on=datetime_from_date(not_overdue),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
@ -439,7 +440,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=in_grace_period, review_request__deadline=in_grace_period,
state_id='assigned', state_id='assigned',
assigned_on=in_grace_period, assigned_on=datetime_from_date(in_grace_period),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )
ReviewAssignmentFactory( ReviewAssignmentFactory(
@ -447,7 +448,7 @@ class ReviewAssignmentReminderTests(TestCase):
review_request__state_id='assigned', review_request__state_id='assigned',
review_request__deadline=in_grace_period, review_request__deadline=in_grace_period,
state_id='assigned', state_id='assigned',
assigned_on=in_grace_period, assigned_on=datetime_from_date(in_grace_period),
reviewer=self.reviewer.email_set.first(), reviewer=self.reviewer.email_set.first(),
) )

View file

@ -32,6 +32,8 @@ from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewRequestSt
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs
from ietf.utils import log 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". # 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. # This date is arbitrarily chosen and has no special meaning, but should be consistent.
@ -194,7 +196,10 @@ def extract_review_assignment_data(teams=None, reviewers=None, time_from=None, t
assigned_time = assigned_on assigned_time = assigned_on
closed_time = completed_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) request_to_assignment_days = positive_days(requested_time, assigned_time)
assignment_to_closure_days = positive_days(assigned_time, closed_time) assignment_to_closure_days = positive_days(assigned_time, closed_time)
request_to_closure_days = positive_days(requested_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( extracted_data = extract_review_assignment_data(
teams=[team], 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"], ordering=["reviewer"],
) )

View file

@ -138,7 +138,10 @@ class SecrMeetingTestCase(TestCase):
"Edit Meeting" "Edit Meeting"
meeting = make_meeting_test_data() meeting = make_meeting_test_data()
url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number}) 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, days=7,
idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_00=13,
idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_day_offset_01=20,
@ -286,7 +289,7 @@ class SecrMeetingTestCase(TestCase):
url = reverse('ietf.secr.meetings.views.times_delete',kwargs={ url = reverse('ietf.secr.meetings.views.times_delete',kwargs={
'meeting_id':meeting.number, 'meeting_id':meeting.number,
'schedule_name':meeting.schedule.name, '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={ redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={
'meeting_id':meeting.number, 'meeting_id':meeting.number,
@ -306,7 +309,7 @@ class SecrMeetingTestCase(TestCase):
url = reverse('ietf.secr.meetings.views.times_edit',kwargs={ url = reverse('ietf.secr.meetings.views.times_edit',kwargs={
'meeting_id':72, 'meeting_id':72,
'schedule_name':'test-schedule', '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") self.client.login(username="secretary", password="secretary+password")
response = self.client.post(url, { response = self.client.post(url, {
@ -372,7 +375,7 @@ class SecrMeetingTestCase(TestCase):
timeslot = session.official_timeslotassignment().timeslot 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}) 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'}) 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") self.client.login(username="secretary", password="secretary+password")
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View file

@ -31,6 +31,7 @@ from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm,
from ietf.secr.sreq.views import get_initial_session from ietf.secr.sreq.views import get_initial_session
from ietf.secr.utils.meeting import get_session, get_timeslot from ietf.secr.utils.meeting import get_session, get_timeslot
from ietf.mailtrigger.utils import gather_address_lists from ietf.mailtrigger.utils import gather_address_lists
from ietf.utils.timezone import make_aware
# prep for agenda changes # prep for agenda changes
@ -342,7 +343,7 @@ def edit_meeting(request, meeting_id):
else: else:
form = MeetingModelForm(instance=meeting) form = MeetingModelForm(instance=meeting)
debug.show('form.errors')
return render(request, 'meetings/edit_meeting.html', { return render(request, 'meetings/edit_meeting.html', {
'meeting': meeting, 'meeting': meeting,
'form' : form, }, 'form' : form, },
@ -799,7 +800,8 @@ def get_timeslot_time(form, meeting):
day = form.cleaned_data['day'] day = form.cleaned_data['day']
date = meeting.date + datetime.timedelta(days=int(day)) date = meeting.date + datetime.timedelta(days=int(day))
return datetime.datetime(date.year,date.month,date.day,time.hour,time.minute) return make_aware(datetime.datetime(date.year,date.month,date.day,time.hour,time.minute), meeting.tz())
@role_required('Secretariat') @role_required('Secretariat')
def times_edit(request, meeting_id, schedule_name, time): def times_edit(request, meeting_id, schedule_name, time):
@ -810,7 +812,7 @@ def times_edit(request, meeting_id, schedule_name, time):
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name) schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
parts = [ int(x) for x in time.split(':') ] parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts) dtime = make_aware(datetime.datetime(*parts), tzinfo=meeting.tz())
timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime) timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime)
if request.method == 'POST': if request.method == 'POST':
@ -861,7 +863,7 @@ def times_delete(request, meeting_id, schedule_name, time):
meeting = get_object_or_404(Meeting, number=meeting_id) meeting = get_object_or_404(Meeting, number=meeting_id)
parts = [ int(x) for x in time.split(':') ] parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts) dtime = make_aware(datetime.datetime(*parts), tzinfo=meeting.tz())
status = SessionStatusName.objects.get(slug='schedw') status = SessionStatusName.objects.get(slug='schedw')
if request.method == 'POST' and request.POST['post'] == 'yes': if request.method == 'POST' and request.POST['post'] == 'yes':

View file

@ -25,6 +25,7 @@ from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTim
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.log import log from ietf.utils.log import log
from ietf.utils.mail import send_mail from ietf.utils.mail import send_mail
from ietf.utils.timezone import make_aware
AUDIO_FILE_RE = re.compile(r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})') AUDIO_FILE_RE = re.compile(r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})')
VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})-(?P<time>\d{4})') VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})-(?P<time>\d{4})')
@ -33,7 +34,7 @@ VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})
def _get_session(number,name,date,time): def _get_session(number,name,date,time):
'''Lookup session using data from video title''' '''Lookup session using data from video title'''
meeting = Meeting.objects.get(number=number) meeting = Meeting.objects.get(number=number)
timeslot_time = datetime.datetime.strptime(date + time,'%Y%m%d%H%M') timeslot_time = make_aware(datetime.datetime.strptime(date + time,'%Y%m%d%H%M'), meeting.tz())
try: try:
assignment = SchedTimeSessAssignment.objects.get( assignment = SchedTimeSessAssignment.objects.get(
schedule__in = [meeting.schedule, meeting.schedule.base], schedule__in = [meeting.schedule, meeting.schedule.base],
@ -103,7 +104,7 @@ def get_timeslot_for_filename(filename):
try: try:
meeting = Meeting.objects.get(number=match.groupdict()['number']) meeting = Meeting.objects.get(number=match.groupdict()['number'])
room_mapping = {normalize_room_name(room.name): room.name for room in meeting.room_set.all()} room_mapping = {normalize_room_name(room.name): room.name for room in meeting.room_set.all()}
time = datetime.datetime.strptime(match.groupdict()['time'],'%Y%m%d-%H%M') time = make_aware(datetime.datetime.strptime(match.groupdict()['time'],'%Y%m%d-%H%M'), meeting.tz())
slots = TimeSlot.objects.filter( slots = TimeSlot.objects.filter(
meeting=meeting, meeting=meeting,
location__name=room_mapping[match.groupdict()['room']], location__name=room_mapping[match.groupdict()['room']],

View file

@ -6,6 +6,8 @@ from django.utils import timezone
from ietf.meeting.models import Meeting from ietf.meeting.models import Meeting
from ietf.doc.models import DocEvent, Document from ietf.doc.models import DocEvent, Document
from ietf.secr.proceedings.proc_utils import get_progress_stats from ietf.secr.proceedings.proc_utils import get_progress_stats
from ietf.utils.timezone import datetime_from_date
def report_id_activity(start,end): def report_id_activity(start,end):
@ -13,8 +15,8 @@ def report_id_activity(start,end):
meeting = Meeting.objects.filter(date__lt=timezone.now(),type='ietf').order_by('-date')[0] meeting = Meeting.objects.filter(date__lt=timezone.now(),type='ietf').order_by('-date')[0]
syear,smonth,sday = start.split('-') syear,smonth,sday = start.split('-')
eyear,emonth,eday = end.split('-') eyear,emonth,eday = end.split('-')
sdate = datetime.datetime(int(syear),int(smonth),int(sday)) sdate = datetime_from_date(datetime.date(int(syear),int(smonth),int(sday)), meeting.tz())
edate = datetime.datetime(int(eyear),int(emonth),int(eday)) edate = datetime_from_date(datetime.date(int(eyear),int(emonth),int(eday)), meeting.tz())
#queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time')) #queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time'))
new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision', new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
@ -45,7 +47,7 @@ def report_id_activity(start,end):
approved = events.filter(type='iesg_approved').count() approved = events.filter(type='iesg_approved').count()
# get 4 weeks # get 4 weeks
monday = Meeting.get_current_meeting().get_ietf_monday() monday = datetime_from_date(Meeting.get_current_meeting().get_ietf_monday(), meeting.tz())
cutoff = monday + datetime.timedelta(days=3) cutoff = monday + datetime.timedelta(days=3)
ff1_date = cutoff - datetime.timedelta(days=28) ff1_date = cutoff - datetime.timedelta(days=28)
#ff2_date = cutoff - datetime.timedelta(days=21) #ff2_date = cutoff - datetime.timedelta(days=21)

View file

@ -50,8 +50,9 @@ class VideoRecordingTestCase(TestCase):
meeting = session.meeting meeting = session.meeting
number = meeting.number number = meeting.number
name = session.group.acronym name = session.group.acronym
date = session.official_timeslotassignment().timeslot.time.strftime('%Y%m%d') ts_time = session.official_timeslotassignment().timeslot.local_start_time()
time = session.official_timeslotassignment().timeslot.time.strftime('%H%M') date = ts_time.strftime('%Y%m%d')
time = ts_time.strftime('%H%M')
self.assertEqual(_get_session(number,name,date,time),session) self.assertEqual(_get_session(number,name,date,time),session)
def test_get_urls_from_json(self): def test_get_urls_from_json(self):
@ -113,7 +114,7 @@ class RecordingTestCase(TestCase):
return "{prefix}-{room}-{date}.mp3".format( return "{prefix}-{room}-{date}.mp3".format(
prefix=timeslot.meeting.type.slug + timeslot.meeting.number, prefix=timeslot.meeting.type.slug + timeslot.meeting.number,
room=normalize_room_name(timeslot.location.name), room=normalize_room_name(timeslot.location.name),
date=timeslot.time.strftime('%Y%m%d-%H%M')) date=timeslot.local_start_time().strftime('%Y%m%d-%H%M'))
def test_import_audio_files_shared_timeslot(self): def test_import_audio_files_shared_timeslot(self):
meeting = MeetingFactory(type_id='ietf',number='72') meeting = MeetingFactory(type_id='ietf',number='72')

View file

@ -15,6 +15,7 @@ from ietf.doc.factories import (WgDraftFactory, IndividualRfcFactory, CharterFac
from ietf.doc.models import BallotDocEvent, BallotType, BallotPositionDocEvent, State, Document from ietf.doc.models import BallotDocEvent, BallotType, BallotPositionDocEvent, State, Document
from ietf.doc.utils import update_telechat, create_ballot_if_not_open from ietf.doc.utils import update_telechat, create_ballot_if_not_open
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.timezone import datetime_today
from ietf.iesg.models import TelechatDate from ietf.iesg.models import TelechatDate
from ietf.person.models import Person from ietf.person.models import Person
from ietf.person.factories import PersonFactory from ietf.person.factories import PersonFactory
@ -120,7 +121,7 @@ class SecrTelechatTestCase(TestCase):
def test_doc_detail_charter(self): def test_doc_detail_charter(self):
by=Person.objects.get(name="(System)") by=Person.objects.get(name="(System)")
charter = CharterFactory(states=[('charter','intrev')]) charter = CharterFactory(states=[('charter','intrev')])
last_week = datetime.date.today()-datetime.timedelta(days=7) last_week = datetime_today()-datetime.timedelta(days=7)
BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev, BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev,
ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'),
time=last_week) time=last_week)
@ -149,7 +150,7 @@ class SecrTelechatTestCase(TestCase):
def test_doc_detail_post_update_ballot(self): def test_doc_detail_post_update_ballot(self):
by=Person.objects.get(name="(System)") by=Person.objects.get(name="(System)")
charter = CharterFactory(states=[('charter','intrev')]) charter = CharterFactory(states=[('charter','intrev')])
last_week = datetime.date.today()-datetime.timedelta(days=7) last_week = datetime_today()-datetime.timedelta(days=7)
BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev, BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev,
ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'),
time=last_week) time=last_week)
@ -187,7 +188,7 @@ class SecrTelechatTestCase(TestCase):
def test_doc_detail_post_update_state(self): def test_doc_detail_post_update_state(self):
by=Person.objects.get(name="(System)") by=Person.objects.get(name="(System)")
charter = CharterFactory(states=[('charter','intrev')]) charter = CharterFactory(states=[('charter','intrev')])
last_week = datetime.date.today()-datetime.timedelta(days=7) last_week = datetime_today()-datetime.timedelta(days=7)
BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev, BallotDocEvent.objects.create(type='created_ballot',by=by,doc=charter, rev=charter.rev,
ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'),
time=last_week) time=last_week)
@ -215,7 +216,7 @@ class SecrTelechatTestCase(TestCase):
ad=Person.objects.get(user__username='ad'), ad=Person.objects.get(user__username='ad'),
authors=PersonFactory.create_batch(3), authors=PersonFactory.create_batch(3),
) )
last_week = datetime.date.today()-datetime.timedelta(days=7) last_week = datetime_today()-datetime.timedelta(days=7)
BallotDocEvent.objects.create(type='created_ballot',by=by,doc=draft, rev=draft.rev, BallotDocEvent.objects.create(type='created_ballot',by=by,doc=draft, rev=draft.rev,
ballot_type=BallotType.objects.get(doc_type=draft.type,slug='approve'), ballot_type=BallotType.objects.get(doc_type=draft.type,slug='approve'),
time=last_week) time=last_week)

View file

@ -1039,8 +1039,6 @@ CHAT_URL_PATTERN = 'https://zulip.ietf.org/#narrow/stream/{chat_room_name}'
# If we need to revert to xmpp # If we need to revert to xmpp
# CHAT_ARCHIVE_URL_PATTERN = 'https://www.ietf.org/jabber/logs/{chat_room_name}?C=M;O=D' # 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", ] PYFLAKES_DEFAULT_ARGS= ["ietf", ]
VULTURE_DEFAULT_ARGS= ["ietf", ] VULTURE_DEFAULT_ARGS= ["ietf", ]

View file

@ -40,6 +40,8 @@ from ietf.stats.models import MeetingRegistration, CountryAlias
from ietf.stats.utils import get_aliased_affiliations, get_aliased_countries, compute_hirsch_index from ietf.stats.utils import get_aliased_affiliations, get_aliased_countries, compute_hirsch_index
from ietf.ietfauth.utils import has_role from ietf.ietfauth.utils import has_role
from ietf.utils.response import permission_denied from ietf.utils.response import permission_denied
from ietf.utils.timezone import date_today, DEADLINE_TZINFO
def stats_index(request): def stats_index(request):
return render(request, "stats/index.html") return render(request, "stats/index.html")
@ -1066,12 +1068,12 @@ def review_stats(request, stats_type=None, acronym=None):
except ValueError: except ValueError:
return None return None
today = datetime.date.today() today = date_today(DEADLINE_TZINFO)
from_date = parse_date(request.GET.get("from")) or today - dateutil.relativedelta.relativedelta(years=1) from_date = parse_date(request.GET.get("from")) or today - dateutil.relativedelta.relativedelta(years=1)
to_date = parse_date(request.GET.get("to")) or today to_date = parse_date(request.GET.get("to")) or today
from_time = datetime.datetime.combine(from_date, datetime.time.min) from_time = datetime.datetime.combine(from_date, datetime.time.min, tzinfo=DEADLINE_TZINFO)
to_time = datetime.datetime.combine(to_date, datetime.time.max) to_time = datetime.datetime.combine(to_date, datetime.time.max, tzinfo=DEADLINE_TZINFO)
# teams/reviewers # teams/reviewers
teams = list(Group.objects.exclude(reviewrequest=None).distinct().order_by("name")) teams = list(Group.objects.exclude(reviewrequest=None).distinct().order_by("name"))

View file

@ -9,6 +9,8 @@ import json
import re import re
import requests import requests
from email.utils import parsedate_to_datetime
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import smart_bytes, force_str from django.utils.encoding import smart_bytes, force_str
@ -22,7 +24,7 @@ from ietf.doc.utils import add_state_change_event
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.log import log from ietf.utils.log import log
from ietf.utils.mail import parseaddr, get_payload_text from ietf.utils.mail import parseaddr, get_payload_text
from ietf.utils.timezone import local_timezone_to_utc, email_time_to_local_timezone, utc_to_local_timezone from ietf.utils.timezone import local_timezone_to_utc
#PROTOCOLS_URL = "https://www.iana.org/protocols/" #PROTOCOLS_URL = "https://www.iana.org/protocols/"
@ -160,8 +162,7 @@ def update_history_with_changes(changes, send_email=True):
for c in changes: for c in changes:
docname = c['doc'] docname = c['doc']
timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S") timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S",).replace(tzinfo=datetime.timezone.utc)
timestamp = utc_to_local_timezone(timestamp) # timestamps are in UTC
if c['type'] in ("iana_state", "iana_review"): if c['type'] in ("iana_state", "iana_review"):
if c['type'] == "iana_state": if c['type'] == "iana_state":
@ -244,7 +245,10 @@ def parse_review_email(text):
# date # date
review_time = timezone.now() review_time = timezone.now()
if "Date" in msg: if "Date" in msg:
review_time = email_time_to_local_timezone(msg["Date"]) review_time = parsedate_to_datetime(msg["Date"])
# parsedate_to_datetime() may return a naive timezone - treat as UTC
if review_time.tzinfo is None or review_time.tzinfo.utcoffset(review_time) is None:
review_time = review_time.replace(tzinfo=datetime.timezone.utc)
# by # by
by = None by = None

View file

@ -25,6 +25,7 @@ from ietf.name.models import StdLevelName, StreamName
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.log import log from ietf.utils.log import log
from ietf.utils.mail import send_mail_text from ietf.utils.mail import send_mail_text
from ietf.utils.timezone import datetime_from_date
#QUEUE_URL = "https://www.rfc-editor.org/queue2.xml" #QUEUE_URL = "https://www.rfc-editor.org/queue2.xml"
#INDEX_URL = "https://www.rfc-editor.org/rfc/rfc-index.xml" #INDEX_URL = "https://www.rfc-editor.org/rfc/rfc-index.xml"
@ -372,7 +373,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in index_data: for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in index_data:
if skip_older_than_date and rfc_published_date < skip_older_than_date: if skip_older_than_date and datetime_from_date(rfc_published_date) < datetime_from_date(skip_older_than_date):
# speed up the process by skipping old entries # speed up the process by skipping old entries
continue continue
@ -443,7 +444,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
# unfortunately, rfc_published_date doesn't include the correct day # unfortunately, rfc_published_date doesn't include the correct day
# at the moment because the data only has month/year, so # at the moment because the data only has month/year, so
# try to deduce it # try to deduce it
d = datetime.datetime.combine(rfc_published_date, datetime.time()) d = datetime_from_date(rfc_published_date)
synthesized = timezone.now() synthesized = timezone.now()
if abs(d - synthesized) > datetime.timedelta(days=60): if abs(d - synthesized) > datetime.timedelta(days=60):
synthesized = d synthesized = d

View file

@ -23,6 +23,7 @@ from ietf.sync import iana, rfceditor
from ietf.utils.mail import outbox, empty_outbox from ietf.utils.mail import outbox, empty_outbox
from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.test_utils import TestCase from ietf.utils.test_utils import TestCase
from ietf.utils.timezone import date_today
class IANASyncTests(TestCase): class IANASyncTests(TestCase):
@ -191,7 +192,7 @@ ICANN
doc_name, review_time, by, comment = iana.parse_review_email(msg.encode('utf-8')) doc_name, review_time, by, comment = iana.parse_review_email(msg.encode('utf-8'))
self.assertEqual(doc_name, draft.name) self.assertEqual(doc_name, draft.name)
self.assertEqual(review_time, datetime.datetime(2012, 5, 10, 5, 0, rtime)) self.assertEqual(review_time, datetime.datetime(2012, 5, 10, 12, 0, rtime, tzinfo=datetime.timezone.utc))
self.assertEqual(by, Person.objects.get(user__username="iana")) self.assertEqual(by, Person.objects.get(user__username="iana"))
self.assertIn("there are no IANA Actions", comment.replace("\n", "")) self.assertIn("there are no IANA Actions", comment.replace("\n", ""))
@ -238,7 +239,7 @@ class RFCSyncTests(TestCase):
DocAlias.objects.create(name=updated_doc.name).docs.add(updated_doc) DocAlias.objects.create(name=updated_doc.name).docs.add(updated_doc)
DocAlias.objects.create(name="rfc123").docs.add(updated_doc) DocAlias.objects.create(name="rfc123").docs.add(updated_doc)
today = datetime.date.today() today = date_today()
t = '''<?xml version="1.0" encoding="UTF-8"?> t = '''<?xml version="1.0" encoding="UTF-8"?>
<rfc-index xmlns="http://www.rfc-editor.org/rfc-index" <rfc-index xmlns="http://www.rfc-editor.org/rfc-index"

View file

@ -1,3 +1,5 @@
import datetime
from django.db import models from django.db import models
def object_as_shallow_dict(obj): def object_as_shallow_dict(obj):
@ -14,7 +16,7 @@ def object_as_shallow_dict(obj):
if isinstance(f, models.ManyToManyField): if isinstance(f, models.ManyToManyField):
v = list(v.values_list("pk", flat=True)) v = list(v.values_list("pk", flat=True))
elif isinstance(f, models.DateTimeField): elif isinstance(f, models.DateTimeField):
v = v.strftime('%Y-%m-%d %H:%M:%S') v = v.astimezone(datetime.timezone.utc).isoformat()
elif isinstance(f, models.DateField): elif isinstance(f, models.DateField):
v = v.strftime('%Y-%m-%d') v = v.strftime('%Y-%m-%d')

View file

@ -1,10 +1,27 @@
import pytz import pytz
import email.utils
import datetime import datetime
from zoneinfo import ZoneInfo
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
# Default time zone for deadlines / expiration dates.
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): def local_timezone_to_utc(d):
"""Takes a naive datetime in the local timezone and returns a """Takes a naive datetime in the local timezone and returns a
naive datetime with the corresponding UTC time.""" naive datetime with the corresponding UTC time."""
@ -14,29 +31,11 @@ def local_timezone_to_utc(d):
return d.replace(tzinfo=None) return d.replace(tzinfo=None)
def utc_to_local_timezone(d):
"""Takes a naive datetime UTC and returns a naive datetime in the
local time zone."""
local_timezone = pytz.timezone(settings.TIME_ZONE)
d = local_timezone.normalize(d.replace(tzinfo=pytz.utc).astimezone(local_timezone)) def datetime_from_date(date, tz=pytz.utc):
"""Get datetime at midnight on a given date"""
return d.replace(tzinfo=None) # 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 email_time_to_local_timezone(date_string):
"""Takes a time string from an email and returns a naive datetime
in the local time zone."""
t = email.utils.parsedate_tz(date_string)
d = datetime.datetime(*t[:6])
if t[7] != None:
d += datetime.timedelta(seconds=t[9])
return utc_to_local_timezone(d)
def date2datetime(date, tz=pytz.utc):
return datetime.datetime(*(date.timetuple()[:6]), tzinfo=tz)
def datetime_today(tzinfo=None): def datetime_today(tzinfo=None):