From ebebdbed3ebadf2564a6eb2aaeadb852a2fc2132 Mon Sep 17 00:00:00 2001
From: Jennifer Richards
Date: Thu, 25 Aug 2022 13:45:16 -0300
Subject: [PATCH 01/19] refactor: replace datetime.now and datetime.today with
timezone.now (#4211)
* refactor: replace datetime.now with timezone.now
* refactor: migrate model fields to use timezone.now as default
* refactor: replace datetime.today with timezone.now
datetime.datetime.today() is equivalent to datetime.datetime.now(); both
return a naive datetime with the current local time.
* refactor: rephrase datetime.now(tz) as timezone.now().astimezone(tz)
This is effectively the same, but is less likely to encourage accidental
use of naive datetimes.
* refactor: revert datetime.today() change to old migrations
* refactor: change a missed datetime.now to timezone.now
* chore: renumber timezone_now migration
* chore: renumber migrations
---
ietf/api/management/commands/makeresources.py | 4 +-
ietf/bin/send-scheduled-mail | 5 ++-
ietf/community/views.py | 5 ++-
ietf/doc/expire.py | 3 +-
ietf/doc/factories.py | 8 ++--
ietf/doc/feeds.py | 3 +-
ietf/doc/mails.py | 5 ++-
.../commands/generate_draft_aliases.py | 3 +-
.../commands/generate_draft_bibxml_files.py | 3 +-
.../0045_use_timezone_now_for_doc_models.py | 39 +++++++++++++++++
ietf/doc/models.py | 11 ++---
ietf/doc/templatetags/ietf_filters.py | 11 ++---
ietf/doc/tests.py | 19 ++++----
ietf/doc/tests_ballot.py | 43 ++++++++++---------
ietf/doc/tests_bofreq.py | 3 +-
ietf/doc/tests_draft.py | 23 +++++-----
ietf/doc/tests_material.py | 4 +-
ietf/doc/tests_review.py | 31 ++++++-------
ietf/doc/utils.py | 5 ++-
ietf/doc/utils_charter.py | 7 +--
ietf/doc/views_charter.py | 9 ++--
ietf/doc/views_draft.py | 3 +-
ietf/doc/views_review.py | 14 +++---
ietf/doc/views_search.py | 5 ++-
ietf/group/factories.py | 4 +-
.../commands/generate_group_aliases.py | 3 +-
.../0059_use_timezone_now_for_group_models.py | 29 +++++++++++++
ietf/group/models.py | 5 ++-
ietf/group/tests.py | 5 ++-
ietf/group/tests_info.py | 2 +-
ietf/group/tests_review.py | 5 ++-
ietf/group/views.py | 11 ++---
ietf/idindex/index.py | 3 +-
ietf/idindex/tests.py | 3 +-
.../commands/send_apikey_usage_emails.py | 3 +-
ietf/ietfauth/tests.py | 7 +--
ietf/ipr/factories.py | 3 +-
ietf/ipr/models.py | 5 +--
ietf/ipr/tests.py | 9 ++--
ietf/ipr/views.py | 3 +-
ietf/liaisons/tests.py | 6 ++-
ietf/meeting/helpers.py | 5 ++-
...056_use_timezone_now_for_meeting_models.py | 19 ++++++++
ietf/meeting/models.py | 5 ++-
ietf/meeting/tests_js.py | 18 ++++----
ietf/meeting/tests_views.py | 34 +++++++--------
ietf/meeting/utils.py | 3 +-
ietf/meeting/views.py | 16 +++----
.../management/commands/show_messages.py | 3 +-
...012_use_timezone_now_for_message_models.py | 24 +++++++++++
ietf/message/models.py | 6 +--
ietf/message/tests.py | 5 ++-
ietf/message/utils.py | 5 ++-
ietf/nomcom/tests.py | 3 +-
.../purge_old_personal_api_key_events.py | 5 ++-
ietf/person/management/commands/tests.py | 5 ++-
...0025_use_timezone_now_for_person_models.py | 34 +++++++++++++++
ietf/person/models.py | 8 ++--
ietf/person/tests.py | 3 +-
ietf/review/factories.py | 6 ++-
...0029_use_timezone_now_for_review_models.py | 29 +++++++++++++
ietf/review/models.py | 5 ++-
ietf/review/utils.py | 6 ++-
ietf/secr/meetings/tests.py | 5 ++-
ietf/secr/meetings/views.py | 3 +-
ietf/secr/proceedings/reports.py | 3 +-
ietf/secr/proceedings/tests_reports.py | 6 ++-
ietf/secr/telechat/tests.py | 5 ++-
.../commands/fetch_meeting_attendance.py | 4 +-
ietf/stats/tests.py | 5 ++-
ietf/stats/views.py | 3 +-
ietf/submit/forms.py | 3 +-
...0011_use_timezone_now_for_submit_models.py | 29 +++++++++++++
ietf/submit/models.py | 7 +--
ietf/submit/tests.py | 16 +++----
ietf/submit/utils.py | 5 ++-
ietf/sync/iana.py | 3 +-
ietf/sync/rfceditor.py | 3 +-
ietf/sync/tests.py | 5 ++-
ietf/sync/views.py | 5 ++-
ietf/utils/decorators.py | 5 ++-
ietf/utils/mail.py | 8 ++--
.../check_draft_event_revision_integrity.py | 3 +-
.../commands/fix_ambiguous_timestamps.py | 3 +-
.../commands/send_gdpr_consent_request.py | 3 +-
ietf/utils/test_data.py | 11 ++---
ietf/utils/test_runner.py | 18 ++++----
ietf/utils/tests_meetecho.py | 37 ++++++++--------
88 files changed, 547 insertions(+), 270 deletions(-)
create mode 100644 ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py
create mode 100644 ietf/group/migrations/0059_use_timezone_now_for_group_models.py
create mode 100644 ietf/meeting/migrations/0056_use_timezone_now_for_meeting_models.py
create mode 100644 ietf/message/migrations/0012_use_timezone_now_for_message_models.py
create mode 100644 ietf/person/migrations/0025_use_timezone_now_for_person_models.py
create mode 100644 ietf/review/migrations/0029_use_timezone_now_for_review_models.py
create mode 100644 ietf/submit/migrations/0011_use_timezone_now_for_submit_models.py
diff --git a/ietf/api/management/commands/makeresources.py b/ietf/api/management/commands/makeresources.py
index 07f8402e7..889b2cdfb 100644
--- a/ietf/api/management/commands/makeresources.py
+++ b/ietf/api/management/commands/makeresources.py
@@ -3,7 +3,6 @@
import os
-import datetime
import collections
import io
@@ -14,6 +13,7 @@ import debug # pyflakes:ignore
from django.core.management.base import AppCommand
from django.db import models
from django.template import Template, Context
+from django.utils import timezone
from tastypie.resources import ModelResource
@@ -89,7 +89,7 @@ class Command(AppCommand):
info = dict(
app=app.name,
app_label=app.label,
- date=datetime.datetime.now()
+ date=timezone.now()
)
new_models = {}
for model, rclass_name in missing_resources:
diff --git a/ietf/bin/send-scheduled-mail b/ietf/bin/send-scheduled-mail
index c6a2dbf50..d3474db09 100755
--- a/ietf/bin/send-scheduled-mail
+++ b/ietf/bin/send-scheduled-mail
@@ -3,7 +3,7 @@
# This script requires that the proper virtual python environment has been
# invoked before start
-import datetime, os, sys
+import os, sys
import syslog
# boilerplate
@@ -16,6 +16,7 @@ syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER)
import django
django.setup()
+from django.utils import timezone
from ietf.utils.mail import log_smtp_exception, send_error_email
from smtplib import SMTPException
@@ -32,7 +33,7 @@ from ietf.message.models import SendQueue
mode = sys.argv[1]
-now = datetime.datetime.now()
+now = timezone.now()
needs_sending = SendQueue.objects.filter(sent_at=None).select_related("message")
if mode == "specific":
diff --git a/ietf/community/views.py b/ietf/community/views.py
index c67f992d6..b0646424a 100644
--- a/ietf/community/views.py
+++ b/ietf/community/views.py
@@ -10,6 +10,7 @@ import uuid
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404, render
from django.contrib.auth.decorators import login_required
+from django.utils import timezone
from django.utils.html import strip_tags
import debug # pyflakes:ignore
@@ -218,7 +219,7 @@ def feed(request, username=None, acronym=None, group_type=None):
significant = request.GET.get('significant', '') == '1'
documents = docs_tracked_by_community_list(clist).values_list('pk', flat=True)
- since = datetime.datetime.now() - datetime.timedelta(days=14)
+ since = timezone.now() - datetime.timedelta(days=14)
events = DocEvent.objects.filter(
doc__id__in=documents,
@@ -243,7 +244,7 @@ def feed(request, username=None, acronym=None, group_type=None):
'title': title,
'subtitle': subtitle,
'id': feed_id.urn,
- 'updated': datetime.datetime.now(),
+ 'updated': timezone.now(),
}, content_type='text/xml')
diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py
index b780c73bd..328a97404 100644
--- a/ietf/doc/expire.py
+++ b/ietf/doc/expire.py
@@ -4,6 +4,7 @@
from django.conf import settings
+from django.utils import timezone
import datetime, os, shutil, glob, re
from pathlib import Path
@@ -62,7 +63,7 @@ def get_expired_drafts():
def in_draft_expire_freeze(when=None):
if when == None:
- when = datetime.datetime.now()
+ when = timezone.now()
meeting = Meeting.objects.filter(type='ietf', date__gte=when-datetime.timedelta(days=7)).order_by('date').first()
diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py
index 8fd6feca7..568e79cc7 100644
--- a/ietf/doc/factories.py
+++ b/ietf/doc/factories.py
@@ -10,6 +10,7 @@ import datetime
from typing import Optional # pyflakes:ignore
from django.conf import settings
+from django.utils import timezone
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
@@ -38,7 +39,7 @@ class BaseDocumentFactory(factory.django.DjangoModelFactory):
rev = '00'
std_level_id = None # type: Optional[str]
intended_std_level_id = None
- time = datetime.datetime.now()
+ time = timezone.now()
expires = factory.LazyAttribute(lambda o: o.time+datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE))
pages = factory.fuzzy.FuzzyInteger(2,400)
@@ -357,7 +358,8 @@ class TelechatDocEventFactory(DocEventFactory):
class Meta:
model = TelechatDocEvent
- telechat_date = datetime.datetime.today()+datetime.timedelta(days=14)
+ # note: this is evaluated at import time and not updated - all events will have the same telechat_date
+ telechat_date = timezone.now()+datetime.timedelta(days=14)
type = 'scheduled_for_telechat'
class NewRevisionDocEventFactory(DocEventFactory):
@@ -410,7 +412,7 @@ class IRSGBallotDocEventFactory(BallotDocEventFactory):
class Meta:
model = IRSGBallotDocEvent
- duedate = datetime.datetime.now() + datetime.timedelta(days=14)
+ duedate = timezone.now() + datetime.timedelta(days=14)
ballot_type = factory.SubFactory(BallotTypeFactory, slug='irsg-approve')
class BallotPositionDocEventFactory(DocEventFactory):
diff --git a/ietf/doc/feeds.py b/ietf/doc/feeds.py
index 1169db105..cddc3329b 100644
--- a/ietf/doc/feeds.py
+++ b/ietf/doc/feeds.py
@@ -10,6 +10,7 @@ from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
from django.urls import reverse as urlreverse
from django.template.defaultfilters import truncatewords, truncatewords_html, date as datefilter
from django.template.defaultfilters import linebreaks # type: ignore
+from django.utils import timezone
from django.utils.html import strip_tags
from ietf.doc.models import Document, State, LastCallDocEvent, DocEvent
@@ -135,7 +136,7 @@ class RfcFeed(Feed):
if self.year:
rfc_events = DocEvent.objects.filter(type='published_rfc',time__year=self.year).order_by('-time')
else:
- cutoff = datetime.datetime.now() - datetime.timedelta(days=8)
+ cutoff = timezone.now() - datetime.timedelta(days=8)
rfc_events = DocEvent.objects.filter(type='published_rfc',time__gte=cutoff).order_by('-time')
results = [(e.doc, e.time) for e in rfc_events]
for doc,time in results:
diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py
index 0f344f015..ddc669ed8 100644
--- a/ietf/doc/mails.py
+++ b/ietf/doc/mails.py
@@ -10,6 +10,7 @@ from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.encoding import force_text
import debug # pyflakes:ignore
@@ -418,7 +419,7 @@ def generate_issue_ballot_mail(request, doc, ballot):
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
last_call_expires = e.expires if e else None
- last_call_has_expired = last_call_expires and last_call_expires < datetime.datetime.now()
+ last_call_has_expired = last_call_expires and last_call_expires < timezone.now()
return render_to_string("doc/mail/issue_iesg_ballot_mail.txt",
dict(doc=doc,
@@ -437,7 +438,7 @@ def _send_irsg_ballot_email(request, doc, ballot, subject, template):
(to, cc) = gather_address_lists('irsg_ballot_issued', doc=doc)
sender = 'IESG Secretary '
- ballot_expired = ballot.duedate < datetime.datetime.now()
+ ballot_expired = ballot.duedate < timezone.now()
active_ballot = doc.active_ballot()
if active_ballot is None:
needed_bps = ''
diff --git a/ietf/doc/management/commands/generate_draft_aliases.py b/ietf/doc/management/commands/generate_draft_aliases.py
index 796e3db63..88f4aa98c 100755
--- a/ietf/doc/management/commands/generate_draft_aliases.py
+++ b/ietf/doc/management/commands/generate_draft_aliases.py
@@ -16,6 +16,7 @@ from tempfile import mkstemp
from django.conf import settings
from django.core.management.base import BaseCommand
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -101,7 +102,7 @@ class Command(BaseCommand):
'that have seen activity in the last %s years.' % (DEFAULT_YEARS))
def handle(self, *args, **options):
- show_since = datetime.datetime.now() - datetime.timedelta(DEFAULT_YEARS*365)
+ show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365)
date = time.strftime("%Y-%m-%d_%H:%M:%S")
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)
diff --git a/ietf/doc/management/commands/generate_draft_bibxml_files.py b/ietf/doc/management/commands/generate_draft_bibxml_files.py
index 8bdaa0a86..218430806 100644
--- a/ietf/doc/management/commands/generate_draft_bibxml_files.py
+++ b/ietf/doc/management/commands/generate_draft_bibxml_files.py
@@ -11,6 +11,7 @@ import sys
from django.conf import settings
from django.core.management.base import BaseCommand
from django.template.loader import render_to_string
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -68,7 +69,7 @@ class Command(BaseCommand):
if process_all:
doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft')
else:
- start = datetime.datetime.now() - datetime.timedelta(days=days)
+ start = timezone.now() - datetime.timedelta(days=days)
doc_events = NewRevisionDocEvent.objects.filter(type='new_revision', doc__type_id='draft', time__gte=start)
doc_events = doc_events.order_by('time')
diff --git a/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py b/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py
new file mode 100644
index 000000000..152f0d4c2
--- /dev/null
+++ b/ietf/doc/migrations/0045_use_timezone_now_for_doc_models.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('doc', '0044_procmaterials_states'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deletedevent',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='docevent',
+ name='time',
+ field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, help_text='When the event happened'),
+ ),
+ migrations.AlterField(
+ model_name='dochistory',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='documentactionholder',
+ name='time_added',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 23a2403bf..b0e7fca25 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -18,6 +18,7 @@ from django.core.validators import URLValidator, RegexValidator
from django.urls import reverse as urlreverse
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
+from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.html import mark_safe # type:ignore
@@ -85,7 +86,7 @@ IESG_SUBSTATE_TAGS = ('ad-f-up', 'need-rev', 'extpty')
class DocumentInfo(models.Model):
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement"""
- time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True
+ time = models.DateTimeField(default=timezone.now) # should probably have auto_now=True
type = ForeignKey(DocTypeName, blank=True, null=True) # Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki, External ...
title = models.CharField(max_length=255, validators=[validate_no_control_chars, ])
@@ -682,7 +683,7 @@ class DocumentActionHolder(models.Model):
"""Action holder for a document"""
document = ForeignKey('Document')
person = ForeignKey(Person)
- time_added = models.DateTimeField(default=datetime.datetime.now)
+ time_added = models.DateTimeField(default=timezone.now)
CLEAR_ACTION_HOLDERS_STATES = ['approved', 'ann', 'rfcqueue', 'pub', 'dead'] # draft-iesg state slugs
GROUP_ROLES_OF_INTEREST = ['chair', 'techadv', 'editor', 'secr']
@@ -838,7 +839,7 @@ class Document(DocumentInfo):
def previous_telechat_date(self):
"Return the most recent telechat date in the past, if any (even if there's another in the future)"
- e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat", telechat_date__lt=datetime.datetime.now())
+ e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat", telechat_date__lt=timezone.now())
return e.telechat_date if e else None
def request_closed_time(self, review_req):
@@ -1208,7 +1209,7 @@ EVENT_TYPES = [
class DocEvent(models.Model):
"""An occurrence for a document, used for tracking who, when and what."""
- time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened", db_index=True)
+ time = models.DateTimeField(default=timezone.now, help_text="When the event happened", db_index=True)
type = models.CharField(max_length=50, choices=EVENT_TYPES)
by = ForeignKey(Person)
doc = ForeignKey(Document)
@@ -1388,7 +1389,7 @@ class DeletedEvent(models.Model):
content_type = ForeignKey(ContentType)
json = models.TextField(help_text="Deleted object in JSON format, with attribute names chosen to be suitable for passing into the relevant create method.")
by = ForeignKey(Person)
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
def __str__(self):
return u"%s by %s %s" % (self.content_type, self.by, self.time)
diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index d2450fa62..2277e478d 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -18,6 +18,7 @@ from django.urls import reverse as urlreverse
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.urls import NoReverseMatch
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -318,7 +319,7 @@ def timesince_days(date):
"""Returns the number of days since 'date' (relative to now)"""
if date.__class__ is not datetime.datetime:
date = datetime.datetime(date.year, date.month, date.day)
- delta = datetime.datetime.now() - date
+ delta = timezone.now() - date
return delta.days
@register.filter
@@ -637,19 +638,19 @@ def action_holder_badge(action_holder):
>>> action_holder_badge(DocumentActionHolderFactory())
''
- >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=15)))
+ >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=15)))
''
- >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=16)))
+ >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=16)))
' 16'
- >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=30)))
+ >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=30)))
' 30'
>>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit
"""
age_limit = settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS
- age = (datetime.datetime.now() - action_holder.time_added).days
+ age = (timezone.now() - action_holder.time_added).days
if age > age_limit:
return mark_safe(
' %d'
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index e01b5e0bd..c201e7f2d 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -25,6 +25,7 @@ from django.conf import settings
from django.forms import Form
from django.utils.html import escape
from django.test import override_settings
+from django.utils import timezone
from django.utils.text import slugify
from tastypie.test import ResourceTestCaseMixin
@@ -385,13 +386,13 @@ class SearchTests(TestCase):
# Three drafts to show with various warnings
drafts = WgDraftFactory.create_batch(3,states=[('draft','active'),('draft-iesg','ad-eval')])
for index, draft in enumerate(drafts):
- StateDocEventFactory(doc=draft, state=('draft-iesg','ad-eval'), time=datetime.datetime.now()-datetime.timedelta(days=[1,15,29][index]))
+ StateDocEventFactory(doc=draft, state=('draft-iesg','ad-eval'), time=timezone.now()-datetime.timedelta(days=[1,15,29][index]))
draft.action_holders.set([PersonFactory()])
# And one draft that should not show (with the default of 7 days to view)
old = WgDraftFactory()
- old.docevent_set.filter(newrevisiondocevent__isnull=False).update(time=datetime.datetime.now()-datetime.timedelta(days=8))
- StateDocEventFactory(doc=old, time=datetime.datetime.now()-datetime.timedelta(days=8))
+ old.docevent_set.filter(newrevisiondocevent__isnull=False).update(time=timezone.now()-datetime.timedelta(days=8))
+ StateDocEventFactory(doc=old, time=timezone.now()-datetime.timedelta(days=8))
url = urlreverse('ietf.doc.views_search.recent_drafts')
r = self.client.get(url)
@@ -764,7 +765,7 @@ Man Expires September 22, 2015 [Page 3]
replacement = WgDraftFactory(
name="draft-ietf-replacement",
- time=datetime.datetime.now(),
+ time=timezone.now(),
title="Replacement Draft",
stream_id=draft.stream_id, group_id=draft.group_id, abstract=draft.abstract,stream=draft.stream, rev=draft.rev,
pages=draft.pages, intended_std_level_id=draft.intended_std_level_id,
@@ -1580,7 +1581,7 @@ class DocTestCase(TestCase):
name = "session-72-mars-1",
meeting = Meeting.objects.get(number='72'),
group = Group.objects.get(acronym='mars'),
- modified = datetime.datetime.now(),
+ modified = timezone.now(),
add_to_schedule=False,
)
SchedulingEvent.objects.create(
@@ -1610,7 +1611,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position",
pos_id="yes",
comment="Looks fine to me",
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)"))
@@ -1644,7 +1645,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position",
pos_id="noobj",
comment="Still looks okay to me",
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)"))
@@ -1666,7 +1667,7 @@ class DocTestCase(TestCase):
type="changed_ballot_position",
pos_id="yes",
comment="Looks fine to me",
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
balloter=Person.objects.get(user__username="ad"),
by=Person.objects.get(name="(System)"))
@@ -2043,7 +2044,7 @@ class GenerateDraftAliasesTests(TestCase):
super().tearDown()
def testManagementCommand(self):
- a_month_ago = datetime.datetime.now() - datetime.timedelta(30)
+ a_month_ago = timezone.now() - datetime.timedelta(30)
ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
shepherd = PersonFactory()
author1 = PersonFactory()
diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py
index 317e4e3a1..163a98a46 100644
--- a/ietf/doc/tests_ballot.py
+++ b/ietf/doc/tests_ballot.py
@@ -12,6 +12,7 @@ import debug # pyflakes:ignore
from django.test import RequestFactory
from django.utils.text import slugify
from django.urls import reverse as urlreverse
+from django.utils import timezone
from ietf.doc.models import (Document, State, DocEvent,
BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, TelechatDocEvent)
@@ -105,7 +106,7 @@ class EditPositionTests(TestCase):
draft = WgDraftFactory(ad=ad)
url = urlreverse('ietf.doc.views_ballot.api_set_position')
create_ballot_if_not_open(None, draft, ad, 'approve')
- ad.user.last_login = datetime.datetime.now()
+ ad.user.last_login = timezone.now()
ad.user.save()
apikey = PersonalApiKey.objects.create(endpoint=url, person=ad)
@@ -238,9 +239,9 @@ class EditPositionTests(TestCase):
doc=draft, rev=draft.rev, type="changed_ballot_position",
by=ad, balloter=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"),
discuss="This draft seems to be lacking a clearer title?",
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
comment="Test!",
- comment_time=datetime.datetime.now())
+ comment_time=timezone.now())
url = urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=draft.name,
ballot_id=ballot.pk))
@@ -466,7 +467,7 @@ class BallotWriteupsTests(TestCase):
doc=draft,
rev=draft.rev,
desc='issued last call',
- expires = datetime.datetime.now()+datetime.timedelta(days = 1 if case=='future' else -1)
+ expires = timezone.now()+datetime.timedelta(days = 1 if case=='future' else -1)
)
url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "ad", url)
@@ -791,7 +792,7 @@ class ApproveBallotTests(TestCase):
doc=draft,
rev=draft.rev,
desc='issued last call',
- expires = datetime.datetime.now()-datetime.timedelta(days=14) )
+ expires = timezone.now()-datetime.timedelta(days=14) )
WriteupDocEvent.objects.create(
by=Person.objects.get(name='(System)'),
doc=draft,
@@ -1117,7 +1118,7 @@ class RegenerateLastCallTestCase(TestCase):
class BallotContentTests(TestCase):
def test_ballotpositiondocevent_any_email_sent(self):
- now = datetime.datetime.now() # be sure event timestamps are at distinct times
+ now = timezone.now() # be sure event timestamps are at distinct times
bpde_with_null_send_email = BallotPositionDocEventFactory(
time=now - datetime.timedelta(minutes=30),
send_email=None,
@@ -1219,7 +1220,7 @@ class BallotContentTests(TestCase):
balloter=balloters[0],
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=True,
)
BallotPositionDocEventFactory(
@@ -1227,7 +1228,7 @@ class BallotContentTests(TestCase):
balloter=balloters[1],
pos_id='noobj',
comment='Commentary',
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
send_email=True,
)
@@ -1237,7 +1238,7 @@ class BallotContentTests(TestCase):
balloter=balloters[2],
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=False,
)
BallotPositionDocEventFactory(
@@ -1245,7 +1246,7 @@ class BallotContentTests(TestCase):
balloter=balloters[3],
pos_id='noobj',
comment='Commentary',
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
send_email=False,
)
@@ -1255,7 +1256,7 @@ class BallotContentTests(TestCase):
balloter=balloters[4],
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now() - datetime.timedelta(days=1),
+ discuss_time=timezone.now() - datetime.timedelta(days=1),
send_email=True,
)
BallotPositionDocEventFactory(
@@ -1263,7 +1264,7 @@ class BallotContentTests(TestCase):
balloter=balloters[4],
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=False,
)
BallotPositionDocEventFactory(
@@ -1271,7 +1272,7 @@ class BallotContentTests(TestCase):
balloter=balloters[5],
pos_id='noobj',
comment='Commentary',
- comment_time=datetime.datetime.now() - datetime.timedelta(days=1),
+ comment_time=timezone.now() - datetime.timedelta(days=1),
send_email=True,
)
BallotPositionDocEventFactory(
@@ -1279,7 +1280,7 @@ class BallotContentTests(TestCase):
balloter=balloters[5],
pos_id='noobj',
comment='Commentary',
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
send_email=False,
)
@@ -1296,7 +1297,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name1',
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=False,
).balloter
send_email_balloter = BallotPositionDocEventFactory(
@@ -1304,7 +1305,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name2',
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=True,
).balloter
prev_send_email_balloter = BallotPositionDocEventFactory(
@@ -1312,7 +1313,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name3',
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now() - datetime.timedelta(days=1),
+ discuss_time=timezone.now() - datetime.timedelta(days=1),
send_email=True,
).balloter
BallotPositionDocEventFactory(
@@ -1320,7 +1321,7 @@ class BallotContentTests(TestCase):
balloter=prev_send_email_balloter,
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=False,
)
@@ -1351,7 +1352,7 @@ class BallotContentTests(TestCase):
balloter=balloters[0],
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=None,
)
BallotPositionDocEventFactory(
@@ -1359,7 +1360,7 @@ class BallotContentTests(TestCase):
balloter=balloters[1],
pos_id='noobj',
comment='Commentary',
- comment_time=datetime.datetime.now(),
+ comment_time=timezone.now(),
send_email=None,
)
old_balloter = BallotPositionDocEventFactory(
@@ -1367,7 +1368,7 @@ class BallotContentTests(TestCase):
balloter__plain='plain name', # ensure plain name is slugifiable
pos_id='discuss',
discuss='Discussion text',
- discuss_time=datetime.datetime.now(),
+ discuss_time=timezone.now(),
send_email=None,
).balloter
diff --git a/ietf/doc/tests_bofreq.py b/ietf/doc/tests_bofreq.py
index 4d02108d0..9925ec3d1 100644
--- a/ietf/doc/tests_bofreq.py
+++ b/ietf/doc/tests_bofreq.py
@@ -14,6 +14,7 @@ from html import unescape
from django.conf import settings
from django.urls import reverse as urlreverse
from django.template.loader import render_to_string
+from django.utils import timezone
from ietf.group.factories import RoleFactory
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
@@ -48,7 +49,7 @@ This test section has some text.
states = State.objects.filter(type_id='bofreq')
self.assertTrue(states.count()>0)
for i in range(3*len(states)):
- BofreqFactory(states=[('bofreq',states[i%len(states)].slug)],newrevisiondocevent__time=datetime.datetime.today()-datetime.timedelta(days=randint(0,20)))
+ BofreqFactory(states=[('bofreq',states[i%len(states)].slug)],newrevisiondocevent__time=timezone.now()-datetime.timedelta(days=randint(0,20)))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py
index 3a0f0315c..91d0542ba 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -13,6 +13,7 @@ from pyquery import PyQuery
from django.urls import reverse as urlreverse
from django.conf import settings
+from django.utils import timezone
from django.utils.html import escape
import debug # pyflakes:ignore
@@ -618,7 +619,7 @@ class ResurrectTests(DraftFileMixin, TestCase):
self.assertEqual(draft.docevent_set.count(), events_before + 1)
self.assertEqual(draft.latest_event().type, "completed_resurrect")
self.assertEqual(draft.get_state_slug(), "active")
- self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1))
+ self.assertTrue(draft.expires >= timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1))
self.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue('Resurrection Completed' in outbox[-1]['Subject'])
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
@@ -659,7 +660,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# hack into expirable state
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
- draft.expires = datetime.datetime.now() + datetime.timedelta(days=10)
+ draft.expires = timezone.now() + datetime.timedelta(days=10)
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 1)
@@ -698,7 +699,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# hack into expirable state
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
- draft.expires = datetime.datetime.now()
+ draft.expires = timezone.now()
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_expired_drafts())), 1)
@@ -741,7 +742,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
draft.delete()
- rgdraft = RgDraftFactory(expires=datetime.datetime.now())
+ rgdraft = RgDraftFactory(expires=timezone.now())
self.assertEqual(len(list(get_expired_drafts())), 1)
for slug in ('iesg-rev','irsgpoll'):
rgdraft.set_state(State.objects.get(type_id='draft-stream-irtf',slug=slug))
@@ -791,7 +792,7 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
# expire draft
draft.set_state(State.objects.get(used=True, type="draft", slug="expired"))
- draft.expires = datetime.datetime.now() - datetime.timedelta(days=1)
+ draft.expires = timezone.now() - datetime.timedelta(days=1)
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
e = DocEvent(doc=draft, rev=draft.rev, type= "expired_document", time=draft.expires,
@@ -824,7 +825,7 @@ class ExpireLastCallTests(TestCase):
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent"
- e.expires = datetime.datetime.now() + datetime.timedelta(days=14)
+ e.expires = timezone.now() + datetime.timedelta(days=14)
e.save()
self.assertEqual(len(list(get_expired_last_calls())), 0)
@@ -832,7 +833,7 @@ class ExpireLastCallTests(TestCase):
# test expired
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent"
- e.expires = datetime.datetime.now()
+ e.expires = timezone.now()
e.save()
drafts = list(get_expired_last_calls())
@@ -866,7 +867,7 @@ class ExpireLastCallTests(TestCase):
e = LastCallDocEvent(doc=draft, rev=draft.rev, type="sent_last_call", by=secretary)
e.text = "Last call sent"
e.desc = "Blah, blah, blah.\n\nThis document makes the following downward references (downrefs):\n ** Downref: Normative reference to an Experimental RFC: RFC 4764"
- e.expires = datetime.datetime.now()
+ e.expires = timezone.now()
e.save()
drafts = list(get_expired_last_calls())
@@ -1730,7 +1731,7 @@ class ChangeStreamStateTests(TestCase):
self.assertEqual(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEqual(len(reminder), 1)
- due = datetime.datetime.now() + datetime.timedelta(weeks=10)
+ due = timezone.now() + datetime.timedelta(weeks=10)
self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEqual(len(outbox), 1)
self.assertTrue("state changed" in outbox[0]["Subject"].lower())
@@ -1775,7 +1776,7 @@ class ChangeStreamStateTests(TestCase):
self.assertEqual(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEqual(len(reminder), 1)
- due = datetime.datetime.now() + datetime.timedelta(weeks=10)
+ due = timezone.now() + datetime.timedelta(weeks=10)
self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEqual(len(outbox), 1)
self.assertTrue("state changed" in outbox[0]["Subject"].lower())
@@ -1826,7 +1827,7 @@ class ChangeReplacesTests(TestCase):
name="draft-test-base-b",
title="Base B",
group=mars_wg,
- expires = datetime.datetime.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
+ expires = timezone.now() - datetime.timedelta(days = 365 - settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
)
p = PersonFactory(name="baseb_author")
e = Email.objects.create(address="baseb_author@example.com", person=p, origin=p.user.username)
diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py
index 1e922197d..05bbc2078 100644
--- a/ietf/doc/tests_material.py
+++ b/ietf/doc/tests_material.py
@@ -4,7 +4,6 @@
import os
import shutil
-import datetime
import io
from pathlib import Path
@@ -14,6 +13,7 @@ import debug # pyflakes:ignore
from django.conf import settings
from django.urls import reverse as urlreverse
+from django.utils import timezone
from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
from ietf.group.factories import RoleFactory
@@ -155,7 +155,7 @@ class GroupMaterialTests(TestCase):
name = "session-42-mars-1",
meeting = Meeting.objects.get(number='42'),
group = Group.objects.get(acronym='mars'),
- modified = datetime.datetime.now(),
+ modified = timezone.now(),
)
SchedulingEvent.objects.create(
session=session,
diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py
index 7e902514d..efe5b7b06 100644
--- a/ietf/doc/tests_review.py
+++ b/ietf/doc/tests_review.py
@@ -14,6 +14,7 @@ from requests import Response
from django.apps import apps
from django.urls import reverse as urlreverse
from django.conf import settings
+from django.utils import timezone
from pyquery import PyQuery
@@ -67,7 +68,7 @@ class ReviewTests(TestCase):
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
RoleFactory(group=review_team3,person__user__username='reviewsecretary3',person__user__email='reviewsecretary3@example.com',name_id='secr')
- req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request = req, reviewer = rev_role.person.email_set.first(), state_id='accepted')
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
@@ -145,7 +146,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01')
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted')
# move the review request to a doubly-replaced document to
@@ -166,7 +167,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01', authors=[author])
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted')
url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@@ -195,7 +196,7 @@ class ReviewTests(TestCase):
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
RoleFactory(group=review_team,person__user__username='reviewsecretary2',person__user__email='reviewsecretary2@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request=review_req, state_id='accepted', reviewer=rev_role.person.email_set.first())
close_url = urlreverse('ietf.doc.views_review.close_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@@ -260,7 +261,7 @@ class ReviewTests(TestCase):
# previous review
req = ReviewRequestFactory(
- time=datetime.datetime.now() - datetime.timedelta(days=100),
+ time=timezone.now() - datetime.timedelta(days=100),
requested_by=Person.objects.get(name="(System)"),
doc=doc,
type_id='early',
@@ -372,7 +373,7 @@ class ReviewTests(TestCase):
doc = WgDraftFactory(group__acronym='mars',rev='01')
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned', reviewer=rev_role.person.email_set.first())
url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
@@ -395,7 +396,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
assignment = ReviewAssignmentFactory(review_request = review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted')
reject_url = urlreverse('ietf.doc.views_review.reject_reviewer_assignment', kwargs={ "name": doc.name, "assignment_id": assignment.pk })
@@ -495,7 +496,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
assignment = ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted')
# test URL construction
@@ -587,7 +588,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
assignment = ReviewAssignmentFactory(review_request=review_req, state_id='accepted', reviewer=rev_role.person.email_set.first())
for r in ReviewResultName.objects.filter(slug__in=("issues", "ready")):
review_req.team.reviewteamsettings.review_results.add(r)
@@ -699,7 +700,7 @@ class ReviewTests(TestCase):
assignment = reload_db_objects(assignment)
self.assertEqual(assignment.state_id, "completed")
# Completed time should be close to now, but will not be exactly, so check within 10s margin
- completed_time_diff = datetime.datetime.now() - assignment.completed_on
+ completed_time_diff = timezone.now() - assignment.completed_on
self.assertLess(completed_time_diff, datetime.timedelta(seconds=10))
with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f:
@@ -739,7 +740,7 @@ class ReviewTests(TestCase):
# - the event logging when the change when it was entered, i.e. very close to now.
# - the completion of the review, set to the provided date/time
events = ReviewAssignmentDocEvent.objects.filter(doc=assignment.review_request.doc).order_by('-time')
- event0_time_diff = datetime.datetime.now() - events[0].time
+ event0_time_diff = timezone.now() - events[0].time
self.assertLess(event0_time_diff, datetime.timedelta(seconds=10))
self.assertEqual(events[1].time, datetime.datetime(2012, 12, 24, 12, 13, 14))
@@ -985,7 +986,7 @@ class ReviewTests(TestCase):
self.assertEqual(assignment.state_id, "completed")
# The revision event time should be the date the revision was submitted, i.e. not backdated
event1 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent)
- event_time_diff = datetime.datetime.now() - event1.time
+ event_time_diff = timezone.now() - event1.time
self.assertLess(event_time_diff, datetime.timedelta(seconds=10))
self.assertTrue('revised' in event1.desc.lower())
@@ -1012,7 +1013,7 @@ class ReviewTests(TestCase):
assignment = reload_db_objects(assignment)
self.assertEqual(assignment.review.rev, "01")
event2 = assignment.review_request.doc.latest_event(ReviewAssignmentDocEvent)
- event_time_diff = datetime.datetime.now() - event2.time
+ event_time_diff = timezone.now() - event2.time
self.assertLess(event_time_diff, datetime.timedelta(seconds=10))
# Ensure that a new event was created for the new revision (#2590)
self.assertNotEqual(event1.id, event2.id)
@@ -1024,7 +1025,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted')
url = urlreverse('ietf.doc.views_review.edit_comment', kwargs={ "name": doc.name, "request_id": review_req.pk })
@@ -1046,7 +1047,7 @@ class ReviewTests(TestCase):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr')
- review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,deadline=datetime.datetime.now()+datetime.timedelta(days=20))
+ review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20))
ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted')
url = urlreverse('ietf.doc.views_review.edit_deadline', kwargs={ "name": doc.name, "request_id": review_req.pk })
diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py
index f9ea81387..c83fa7cf6 100644
--- a/ietf/doc/utils.py
+++ b/ietf/doc/utils.py
@@ -18,6 +18,7 @@ from django.conf import settings
from django.contrib import messages
from django.forms import ValidationError
from django.template.loader import render_to_string
+from django.utils import timezone
from django.utils.html import escape
from django.urls import reverse as urlreverse
@@ -807,7 +808,7 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, com
cc.update(other_addrs.cc)
RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete()
if not RelatedDocument.objects.filter(target=d, relationship=relationship):
- s = 'active' if d.document.expires > datetime.datetime.now() else 'expired'
+ s = 'active' if d.document.expires > timezone.now() else 'expired'
d.document.set_state(State.objects.get(type='draft', slug=s))
for d in new_replaces:
@@ -1118,7 +1119,7 @@ def build_doc_meta_block(doc, path):
lines[i] = line
return lines
#
- now = datetime.datetime.now()
+ now = timezone.now()
draft_state = doc.get_state('draft')
block = ''
meta = {}
diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py
index ce9552106..43982deab 100644
--- a/ietf/doc/utils_charter.py
+++ b/ietf/doc/utils_charter.py
@@ -11,6 +11,7 @@ import shutil
from django.conf import settings
from django.urls import reverse as urlreverse
from django.template.loader import render_to_string
+from django.utils import timezone
from django.utils.encoding import smart_text, force_text
import debug # pyflakes:ignore
@@ -73,7 +74,7 @@ def change_group_state_after_charter_approval(group, by):
save_group_in_history(group)
group.state = new_state
- group.time = datetime.datetime.now()
+ group.time = timezone.now()
group.save()
# create an event for the group state change, too
@@ -132,7 +133,7 @@ def historic_milestones_for_charter(charter, rev):
# revision (when approving a charter)
just_before_next_rev = e[0].time - datetime.timedelta(seconds=5)
else:
- just_before_next_rev = datetime.datetime.now()
+ just_before_next_rev = timezone.now()
res = []
if hasattr(charter, 'chartered_group'):
@@ -197,7 +198,7 @@ def derive_new_work_text(review_text,group):
return smart_text(m.as_string())
def default_review_text(group, charter, by):
- now = datetime.datetime.now()
+ now = timezone.now()
addrs = gather_address_lists('charter_external_review',group=group).as_strings(compact=False)
e1 = WriteupDocEvent(doc=charter, rev=charter.rev, by=by)
diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py
index c2b88ac47..3f85a19ce 100644
--- a/ietf/doc/views_charter.py
+++ b/ietf/doc/views_charter.py
@@ -16,6 +16,7 @@ from django.utils.safestring import mark_safe
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
+from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.html import escape
@@ -77,7 +78,7 @@ def change_state(request, name, option=None):
chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
- if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering":
+ if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < timezone.now()) or chartering_type == "rechartering":
initial_review = None
by = request.user.person
@@ -183,7 +184,7 @@ def change_state(request, name, option=None):
if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
e = InitialReviewDocEvent(type="initial_review", by=by, doc=charter, rev=charter.rev)
- e.expires = datetime.datetime.now() + datetime.timedelta(weeks=clean["initial_time"])
+ e.expires = timezone.now() + datetime.timedelta(weeks=clean["initial_time"])
e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
e.save()
@@ -506,7 +507,7 @@ def review_announcement_text(request, name):
existing_new_work.type = "changed_new_work_text"
existing_new_work.desc = "%s review text was changed" % group.type.name
existing_new_work.text = derive_new_work_text(existing.text,group)
- existing_new_work.time = datetime.datetime.now()
+ existing_new_work.time = timezone.now()
form = ReviewAnnouncementTextForm(initial=dict(announcement_text=escape(existing.text),new_work_text=escape(existing_new_work.text)))
@@ -514,7 +515,7 @@ def review_announcement_text(request, name):
form = ReviewAnnouncementTextForm(request.POST)
if "save_text" in request.POST and form.is_valid():
- now = datetime.datetime.now()
+ now = timezone.now()
events = []
t = form.cleaned_data['announcement_text']
diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py
index e0d73855b..5d445cfc2 100644
--- a/ietf/doc/views_draft.py
+++ b/ietf/doc/views_draft.py
@@ -19,6 +19,7 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string
from django.forms.utils import ErrorList
from django.template.defaultfilters import pluralize
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -857,7 +858,7 @@ def resurrect(request, name):
events.append(e)
doc.set_state(State.objects.get(used=True, type="draft", slug="active"))
- doc.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
+ doc.expires = timezone.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
doc.save_with_history(events)
restore_draft_file(request, doc)
diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py
index c7d8e5804..19c822ec9 100644
--- a/ietf/doc/views_review.py
+++ b/ietf/doc/views_review.py
@@ -10,7 +10,9 @@ import datetime
import requests
import email.utils
+from django.utils import timezone
from django.utils.http import is_safe_url
+
from simple_history.utils import update_change_reason
import debug # pyflakes:ignore
@@ -117,7 +119,7 @@ def request_review(request, name):
if not can_request_review_of_doc(request.user, doc):
permission_denied(request, "You do not have permission to perform this action")
- now = datetime.datetime.now()
+ now = timezone.now()
lc_ends = None
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
@@ -364,7 +366,7 @@ def reject_reviewer_assignment(request, name, assignment_id):
if form.is_valid():
# reject the assignment
review_assignment.state = ReviewAssignmentStateName.objects.get(slug="rejected")
- review_assignment.completed_on = datetime.datetime.now()
+ review_assignment.completed_on = timezone.now()
review_assignment.save()
descr = "Assignment of request for {} review by {} to {} was rejected".format(
@@ -731,13 +733,13 @@ def complete_review(request, name, assignment_id=None, acronym=None):
review_request=review_request,
state_id='assigned',
reviewer=form.cleaned_data['reviewer'].role_email('reviewer', group=team),
- assigned_on=datetime.datetime.now(),
+ assigned_on=timezone.now(),
review = review,
)
review.rev = "00" if not review.rev else "{:02}".format(int(review.rev) + 1)
review.title = "{} Review of {}-{}".format(assignment.review_request.type.name, assignment.review_request.doc.name, form.cleaned_data["reviewed_rev"])
- review.time = datetime.datetime.now()
+ review.time = timezone.now()
if review_submission == "link":
review.external_url = form.cleaned_data['review_url']
@@ -764,7 +766,7 @@ def complete_review(request, name, assignment_id=None, acronym=None):
with io.open(filename, 'w', encoding='utf-8') as destination:
destination.write(content)
- completion_datetime = datetime.datetime.now()
+ completion_datetime = timezone.now()
if "completion_date" in form.cleaned_data:
completion_datetime = datetime.datetime.combine(form.cleaned_data["completion_date"], form.cleaned_data.get("completion_time") or datetime.time.min)
@@ -799,7 +801,7 @@ def complete_review(request, name, assignment_id=None, acronym=None):
close_event.by = request.user.person
close_event.desc = desc
close_event.state = assignment.state
- close_event.time = datetime.datetime.now()
+ close_event.time = timezone.now()
close_event.save()
# If the completion date is different, record when the initial review was made too.
diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py
index 057f568b3..74bb30587 100644
--- a/ietf/doc/views_search.py
+++ b/ietf/doc/views_search.py
@@ -44,6 +44,7 @@ from django.urls import reverse as urlreverse
from django.db.models import Q
from django.http import Http404, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect, QueryDict
from django.shortcuts import render
+from django.utils import timezone
from django.utils.cache import _generate_cache_key # type: ignore
@@ -419,7 +420,7 @@ def ad_dashboard_sort_key(doc):
ageseconds = 0
changetime= doc.latest_event(type='changed_document')
if changetime:
- ad = (datetime.datetime.now()-doc.latest_event(type='changed_document').time)
+ ad = (timezone.now()-doc.latest_event(type='changed_document').time)
ageseconds = (ad.microseconds + (ad.seconds + ad.days * 24 * 3600) * 10**6) / 10**6
return "1%d%s%s%010d" % (state[0].order,seed,doc.type.slug,ageseconds)
@@ -627,7 +628,7 @@ def recent_drafts(request, days=7):
cache_key = f'recentdraftsview{days}'
cached_val = slowcache.get(cache_key)
if not cached_val:
- since = datetime.datetime.now()-datetime.timedelta(days=days)
+ since = timezone.now()-datetime.timedelta(days=days)
state = State.objects.get(type='draft', slug='active')
events = NewRevisionDocEvent.objects.filter(time__gt=since)
names = [ e.doc.name for e in events ]
diff --git a/ietf/group/factories.py b/ietf/group/factories.py
index d8d927d80..e7fbef59a 100644
--- a/ietf/group/factories.py
+++ b/ietf/group/factories.py
@@ -5,6 +5,8 @@ import factory
from typing import List # pyflakes:ignore
+from django.utils import timezone
+
from ietf.group.models import Group, Role, GroupEvent, GroupMilestone, \
GroupHistory, RoleHistory
from ietf.review.factories import ReviewTeamSettingsFactory
@@ -66,7 +68,7 @@ class BaseGroupMilestoneFactory(factory.django.DjangoModelFactory):
class DatedGroupMilestoneFactory(BaseGroupMilestoneFactory):
group = factory.SubFactory(GroupFactory, uses_milestone_dates=True)
- due = datetime.datetime.today()+datetime.timedelta(days=180)
+ due = timezone.now()+datetime.timedelta(days=180)
class DatelessGroupMilestoneFactory(BaseGroupMilestoneFactory):
group = factory.SubFactory(GroupFactory, uses_milestone_dates=False)
diff --git a/ietf/group/management/commands/generate_group_aliases.py b/ietf/group/management/commands/generate_group_aliases.py
index 50802b3cf..132fef753 100755
--- a/ietf/group/management/commands/generate_group_aliases.py
+++ b/ietf/group/management/commands/generate_group_aliases.py
@@ -15,6 +15,7 @@ from tempfile import mkstemp
from django.conf import settings
from django.core.management.base import BaseCommand
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -39,7 +40,7 @@ class Command(BaseCommand):
'have seen activity in the last %s years.' % (DEFAULT_YEARS))
def handle(self, *args, **options):
- show_since = datetime.datetime.now() - datetime.timedelta(DEFAULT_YEARS*365)
+ show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365)
date = time.strftime("%Y-%m-%d_%H:%M:%S")
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)
diff --git a/ietf/group/migrations/0059_use_timezone_now_for_group_models.py b/ietf/group/migrations/0059_use_timezone_now_for_group_models.py
new file mode 100644
index 000000000..24c083855
--- /dev/null
+++ b/ietf/group/migrations/0059_use_timezone_now_for_group_models.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('group', '0058_alter_has_default_chat'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='groupevent',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'),
+ ),
+ migrations.AlterField(
+ model_name='grouphistory',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/ietf/group/models.py b/ietf/group/models.py
index 899c88241..fd665e6b3 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -13,6 +13,7 @@ from django.core.validators import RegexValidator
from django.db import models
from django.db.models.deletion import CASCADE, PROTECT
from django.dispatch import receiver
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -27,7 +28,7 @@ from ietf.utils.validators import JSONForeignKeyListValidator
class GroupInfo(models.Model):
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
name = models.CharField(max_length=80)
state = ForeignKey(GroupStateName, null=True)
type = ForeignKey(GroupTypeName, null=True)
@@ -353,7 +354,7 @@ GROUP_EVENT_CHOICES = [
class GroupEvent(models.Model):
"""An occurrence for a group, used for tracking who, when and what."""
group = ForeignKey(Group)
- time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
+ time = models.DateTimeField(default=timezone.now, help_text="When the event happened")
type = models.CharField(max_length=50, choices=GROUP_EVENT_CHOICES)
by = ForeignKey(Person)
desc = models.TextField()
diff --git a/ietf/group/tests.py b/ietf/group/tests.py
index b168e4d3b..2c63ff95d 100644
--- a/ietf/group/tests.py
+++ b/ietf/group/tests.py
@@ -13,6 +13,7 @@ from django.conf import settings
from django.urls import reverse as urlreverse
from django.db.models import Q
from django.test import Client
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -115,8 +116,8 @@ class GenerateGroupAliasesTests(TestCase):
super().tearDown()
def testManagementCommand(self):
- a_month_ago = datetime.datetime.now() - datetime.timedelta(30)
- a_decade_ago = datetime.datetime.now() - datetime.timedelta(3650)
+ a_month_ago = timezone.now() - datetime.timedelta(30)
+ a_decade_ago = timezone.now() - datetime.timedelta(3650)
role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active')
area = role1.group
ad = role1.person
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 0b3ead2c7..932b0594e 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -1900,7 +1900,7 @@ class StatusUpdateTests(TestCase):
def test_view_status_update_for_meeting(self):
chair = RoleFactory(name_id='chair',group__type_id='wg')
GroupEventFactory(type='status_update',group=chair.group)
- sess = SessionFactory.create(meeting__type_id='ietf',group=chair.group,meeting__date=datetime.datetime.today()-datetime.timedelta(days=1))
+ sess = SessionFactory.create(meeting__type_id='ietf',group=chair.group,meeting__date=timezone.now()-datetime.timedelta(days=1))
url = urlreverse('ietf.group.views.group_about_status_meeting',kwargs={'acronym':chair.group.acronym,'num':sess.meeting.number})
response = self.client.get(url)
self.assertEqual(response.status_code,200)
diff --git a/ietf/group/tests_review.py b/ietf/group/tests_review.py
index ee4ae96fe..924f6fade 100644
--- a/ietf/group/tests_review.py
+++ b/ietf/group/tests_review.py
@@ -8,6 +8,7 @@ import debug # pyflakes:ignore
from pyquery import PyQuery
from django.urls import reverse as urlreverse
+from django.utils import timezone
from ietf.review.policies import get_reviewer_queue_policy
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, reload_db_objects
@@ -131,7 +132,7 @@ class ReviewTests(TestCase):
doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True))
LastCallDocEvent.objects.create(
doc=doc,
- expires=datetime.datetime.now() + datetime.timedelta(days=365),
+ expires=timezone.now() + datetime.timedelta(days=365),
by=Person.objects.get(name="(System)"),
rev=doc.rev
)
@@ -429,7 +430,7 @@ class ReviewTests(TestCase):
doc.states.add(State.objects.get(type="draft-iesg", slug="lc", used=True))
LastCallDocEvent.objects.create(
doc=doc,
- expires=datetime.datetime.now() + datetime.timedelta(days=365),
+ expires=timezone.now() + datetime.timedelta(days=365),
by=Person.objects.get(name="(System)"),
rev=doc.rev
)
diff --git a/ietf/group/views.py b/ietf/group/views.py
index 3a4b9f3e0..338defe5e 100644
--- a/ietf/group/views.py
+++ b/ietf/group/views.py
@@ -53,6 +53,7 @@ from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonRespons
from django.shortcuts import render, redirect, get_object_or_404
from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.html import escape
from django.views.decorators.cache import cache_page, cache_control
@@ -566,7 +567,7 @@ def all_status(request):
if e:
wg_reports.append(e)
- wg_reports.sort(key=lambda x: (x.group.parent.acronym,datetime.datetime.now()-x.time))
+ wg_reports.sort(key=lambda x: (x.group.parent.acronym,timezone.now()-x.time))
rg_reports = []
for rg in rgs:
@@ -808,7 +809,7 @@ def email_aliases(request, acronym=None, group_type=None):
def meetings(request, acronym=None, group_type=None):
group = get_group_or_404(acronym,group_type) if acronym else None
- four_years_ago = datetime.datetime.now()-datetime.timedelta(days=4*365)
+ four_years_ago = timezone.now()-datetime.timedelta(days=4*365)
sessions = add_event_info_to_session_qs(
group.session_set.filter(
@@ -972,7 +973,7 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None):
try:
group = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(group)
- group.time = datetime.datetime.now()
+ group.time = timezone.now()
group.save()
except Group.DoesNotExist:
group = Group.objects.create(name=clean["name"],
@@ -1071,7 +1072,7 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None):
)
))
- group.time = datetime.datetime.now()
+ group.time = timezone.now()
if changes and not new_group:
for attr, new, desc in changes:
@@ -1509,7 +1510,7 @@ def reviewer_overview(request, acronym, group_type=None):
int(math.ceil(d.assignment_to_closure_days)) if d.assignment_to_closure_days is not None else None))
if d.state in ["completed", "completed_in_time", "completed_late"]:
if d.assigned_time is not None:
- delta = datetime.datetime.now() - d.assigned_time
+ delta = timezone.now() - d.assigned_time
if d.assignment_to_closure_days is not None:
days = int(delta.days - d.assignment_to_closure_days)
if days_since > days: days_since = days
diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py
index 3d17b197c..782e8912e 100644
--- a/ietf/idindex/index.py
+++ b/ietf/idindex/index.py
@@ -11,6 +11,7 @@ import pytz
from django.conf import settings
from django.template.loader import render_to_string
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -296,6 +297,6 @@ def id_index_txt(with_abstracts=False):
return render_to_string("idindex/id_index.txt", {
'groups': groups,
- 'time': datetime.datetime.now(pytz.UTC).strftime("%Y-%m-%d %H:%M:%S %Z"),
+ 'time': timezone.now().astimezone(pytz.utc).strftime("%Y-%m-%d %H:%M:%S %Z"),
'with_abstracts': with_abstracts,
})
diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py
index 413e3a4ed..f207fa562 100644
--- a/ietf/idindex/tests.py
+++ b/ietf/idindex/tests.py
@@ -7,6 +7,7 @@ import datetime
from pathlib import Path
from django.conf import settings
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -120,7 +121,7 @@ class IndexTests(TestCase):
draft.set_state(State.objects.get(type="draft", slug="active"))
draft.set_state(State.objects.get(type="draft-iesg", slug="lc"))
- e = LastCallDocEvent.objects.create(doc=draft, rev=draft.rev, type="sent_last_call", expires=datetime.datetime.now() + datetime.timedelta(days=14), by=draft.ad)
+ e = LastCallDocEvent.objects.create(doc=draft, rev=draft.rev, type="sent_last_call", expires=timezone.now() + datetime.timedelta(days=14), by=draft.ad)
t = get_fields(all_id2_txt())
self.assertEqual(t[11], e.expires.strftime("%Y-%m-%d"))
diff --git a/ietf/ietfauth/management/commands/send_apikey_usage_emails.py b/ietf/ietfauth/management/commands/send_apikey_usage_emails.py
index 4aa4e5524..d3fce1bcc 100644
--- a/ietf/ietfauth/management/commands/send_apikey_usage_emails.py
+++ b/ietf/ietfauth/management/commands/send_apikey_usage_emails.py
@@ -8,6 +8,7 @@ from textwrap import dedent
from django.conf import settings
from django.core.management.base import BaseCommand
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -37,7 +38,7 @@ class Command(BaseCommand):
keys = PersonalApiKey.objects.filter(valid=True)
for key in keys:
- earliest = datetime.datetime.now() - datetime.timedelta(days=days)
+ earliest = timezone.now() - datetime.timedelta(days=days)
events = PersonApiKeyEvent.objects.filter(key=key, time__gt=earliest)
count = events.count()
events = events[:32]
diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py
index 254c6524a..f0e01f37b 100644
--- a/ietf/ietfauth/tests.py
+++ b/ietf/ietfauth/tests.py
@@ -28,6 +28,7 @@ from django.urls import reverse as urlreverse
from django.contrib.auth.models import User
from django.conf import settings
from django.template.loader import render_to_string
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -750,11 +751,11 @@ class IetfAuthTests(TestCase):
self.assertContains(r, 'Invalid apikey', status_code=403)
# too long since regular login
- person.user.last_login = datetime.datetime.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS+1)
+ person.user.last_login = timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS+1)
person.user.save()
r = self.client.post(key.endpoint, {'apikey':key.hash(), 'dummy':'dummy',})
self.assertContains(r, 'Too long since last regular login', status_code=400)
- person.user.last_login = datetime.datetime.now()
+ person.user.last_login = timezone.now()
person.user.save()
# endpoint mismatch
@@ -783,7 +784,7 @@ class IetfAuthTests(TestCase):
# apikey usage will be registered)
count = 2
# avoid usage across dates
- if datetime.datetime.now().time() > datetime.time(hour=23, minute=59, second=58):
+ if timezone.now().time() > datetime.time(hour=23, minute=59, second=58):
time.sleep(2)
for i in range(count):
for key in person.apikeys.all():
diff --git a/ietf/ipr/factories.py b/ietf/ipr/factories.py
index da1262912..e32090a36 100644
--- a/ietf/ipr/factories.py
+++ b/ietf/ipr/factories.py
@@ -5,6 +5,7 @@
import datetime
import factory
+from django.utils import timezone
from ietf.ipr.models import (
IprDisclosureBase, HolderIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure,
@@ -13,7 +14,7 @@ from ietf.ipr.models import (
def _fake_patent_info():
return "Date: %s\nNotes: %s\nTitle: %s\nNumber: %s\nInventor: %s\n" % (
- (datetime.datetime.today()-datetime.timedelta(days=365)).strftime("%Y-%m-%d"),
+ (timezone.now()-datetime.timedelta(days=365)).strftime("%Y-%m-%d"),
factory.Faker('paragraph'),
factory.Faker('sentence', nb_words=8),
'US9999999',
diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py
index 282f33568..2eb588b8b 100644
--- a/ietf/ipr/models.py
+++ b/ietf/ipr/models.py
@@ -2,11 +2,10 @@
# -*- coding: utf-8 -*-
-import datetime
-
from django.conf import settings
from django.db import models
from django.urls import reverse
+from django.utils import timezone
from ietf.doc.models import DocAlias, DocEvent
from ietf.name.models import DocRelationshipName,IprDisclosureStateName,IprLicenseTypeName,IprEventTypeName
@@ -220,7 +219,7 @@ class IprEvent(models.Model):
"""Returns true if it's beyond the response_due date and no response has been
received"""
qs = IprEvent.objects.filter(disclosure=self.disclosure,in_reply_to=self.message)
- if not qs and datetime.datetime.now().date() > self.response_due.date():
+ if not qs and timezone.now().date() > self.response_due.date():
return True
else:
return False
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index 5b3896832..8fb11c39b 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -9,6 +9,7 @@ from pyquery import PyQuery
from urllib.parse import quote, urlparse
from django.urls import reverse as urlreverse
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -636,7 +637,7 @@ I would like to revoke this declaration.
message_string.format(
to=addrs.to,
cc=addrs.cc,
- date=datetime.datetime.now().ctime()
+ date=timezone.now().ctime()
)
)
self.assertIsNone(result)
@@ -646,7 +647,7 @@ I would like to revoke this declaration.
From: joe@test.com
Date: {}
Subject: test
-""".format(reply_to, datetime.datetime.now().ctime())
+""".format(reply_to, timezone.now().ctime())
result = process_response_email(message_string)
self.assertIsInstance(result, Message)
@@ -660,7 +661,7 @@ Subject: test
From: joe@test.com
Date: {}
Subject: test
-""".format(reply_to, datetime.datetime.now().ctime())
+""".format(reply_to, timezone.now().ctime())
message_bytes = message_string.encode('utf8') + b'\nInvalid stuff: \xfe\xff\n'
result = process_response_email(message_bytes)
self.assertIsInstance(result, Message)
@@ -676,7 +677,7 @@ Subject: test
message_bytes = message_string.format(
to=addrs.to,
cc=addrs.cc,
- date=datetime.datetime.now().ctime(),
+ date=timezone.now().ctime(),
).encode('utf8') + b'\nInvalid stuff: \xfe\xff\n'
result = process_response_email(message_bytes)
self.assertIsNone(result)
diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py
index e290bf14d..458186949 100644
--- a/ietf/ipr/views.py
+++ b/ietf/ipr/views.py
@@ -14,6 +14,7 @@ from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.html import escape
import debug # pyflakes:ignore
@@ -587,7 +588,7 @@ def notify(request, id, type):
type_id = form.cleaned_data['type'],
by = request.user.person,
disclosure = ipr,
- response_due = datetime.datetime.now().date() + datetime.timedelta(days=30),
+ response_due = timezone.now().date() + datetime.timedelta(days=30),
message = message,
)
messages.success(request,'Notifications sent')
diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py
index b08832ae5..0b3116fb4 100644
--- a/ietf/liaisons/tests.py
+++ b/ietf/liaisons/tests.py
@@ -14,6 +14,8 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.urls import reverse as urlreverse
from django.db.models import Q
+from django.utils import timezone
+
from io import StringIO
from pyquery import PyQuery
@@ -50,7 +52,7 @@ def get_liaison_post_data(type='incoming'):
to_contacts='to_contacts@example.com',
purpose="info",
title="title",
- submitted_date=datetime.datetime.today().strftime("%Y-%m-%d"),
+ submitted_date=timezone.now().strftime("%Y-%m-%d"),
body="body",
send="1" )
@@ -385,7 +387,7 @@ class LiaisonManagementTests(TestCase):
def test_edit_liaison(self):
liaison = LiaisonStatementFactory(deadline=datetime.date.today()+datetime.timedelta(days=1))
- LiaisonStatementEventFactory(statement=liaison,type_id='submitted', time=datetime.datetime.now()-datetime.timedelta(days=1))
+ LiaisonStatementEventFactory(statement=liaison,type_id='submitted', time=timezone.now()-datetime.timedelta(days=1))
LiaisonStatementEventFactory(statement=liaison,type_id='posted')
from_group = liaison.from_groups.first()
to_group = liaison.to_groups.first()
diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py
index 4fd4579c9..5f463b269 100644
--- a/ietf/meeting/helpers.py
+++ b/ietf/meeting/helpers.py
@@ -17,6 +17,7 @@ from django.contrib.auth.models import AnonymousUser
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -42,7 +43,7 @@ def get_meeting(num=None,type_in=['ietf',],days=28):
if type_in:
meetings = meetings.filter(type__in=type_in)
if num == None:
- meetings = meetings.filter(date__gte=datetime.datetime.today()-datetime.timedelta(days=days)).order_by('date')
+ meetings = meetings.filter(date__gte=timezone.now()-datetime.timedelta(days=days)).order_by('date')
else:
meetings = meetings.filter(number=num)
if meetings.exists():
@@ -51,7 +52,7 @@ def get_meeting(num=None,type_in=['ietf',],days=28):
raise Http404("No such meeting found: %s" % num)
def get_current_ietf_meeting():
- meetings = Meeting.objects.filter(type='ietf',date__gte=datetime.datetime.today()-datetime.timedelta(days=31)).order_by('date')
+ meetings = Meeting.objects.filter(type='ietf',date__gte=timezone.now()-datetime.timedelta(days=31)).order_by('date')
return meetings.first()
def get_current_ietf_meeting_num():
diff --git a/ietf/meeting/migrations/0056_use_timezone_now_for_meeting_models.py b/ietf/meeting/migrations/0056_use_timezone_now_for_meeting_models.py
new file mode 100644
index 000000000..83d20fd57
--- /dev/null
+++ b/ietf/meeting/migrations/0056_use_timezone_now_for_meeting_models.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('meeting', '0055_pytz_2022_2_1'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='schedulingevent',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'),
+ ),
+ ]
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 417a6ac9b..d7294f70a 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -24,6 +24,7 @@ from django.db.models import Max, Subquery, OuterRef, TextField, Value, Q
from django.db.models.functions import Coalesce
from django.conf import settings
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.text import slugify
from django.utils.safestring import mark_safe
@@ -176,7 +177,7 @@ class Meeting(models.Model):
@classmethod
def get_current_meeting(cls, type="ietf"):
- return cls.objects.filter(type=type, date__gte=datetime.datetime.today()-datetime.timedelta(days=7) ).order_by('date').first()
+ return cls.objects.filter(type=type, date__gte=timezone.now()-datetime.timedelta(days=7) ).order_by('date').first()
def get_first_cut_off(self):
return self.get_00_cutoff()
@@ -1280,7 +1281,7 @@ class Session(models.Model):
class SchedulingEvent(models.Model):
session = ForeignKey(Session)
- time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
+ time = models.DateTimeField(default=timezone.now, help_text="When the event happened")
status = ForeignKey(SessionStatusName)
by = ForeignKey(Person)
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
index 94e41f5ed..f62e41388 100644
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -11,8 +11,8 @@ from unittest import skipIf
import urllib.parse
import django
+from django.utils import timezone
from django.utils.text import slugify
-from django.utils.timezone import now
from django.db.models import F
import pytz
@@ -307,7 +307,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
room = RoomFactory(meeting=meeting)
# get current time in meeting time zone
- right_now = now().astimezone(
+ right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone)
)
if not settings.USE_TZ:
@@ -394,11 +394,11 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
def test_past_swap_days_buttons(self):
"""Swap days buttons should be hidden for past items"""
wait = WebDriverWait(self.driver, 2)
- meeting = MeetingFactory(type_id='ietf', date=datetime.datetime.today() - datetime.timedelta(days=3), days=7)
+ meeting = MeetingFactory(type_id='ietf', date=timezone.now() - datetime.timedelta(days=3), days=7)
room = RoomFactory(meeting=meeting)
# get current time in meeting time zone
- right_now = now().astimezone(
+ right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone)
)
if not settings.USE_TZ:
@@ -518,11 +518,11 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
def test_past_swap_timeslot_col_buttons(self):
"""Swap timeslot column buttons should be hidden for past items"""
wait = WebDriverWait(self.driver, 2)
- meeting = MeetingFactory(type_id='ietf', date=datetime.datetime.today() - datetime.timedelta(days=3), days=7)
+ meeting = MeetingFactory(type_id='ietf', date=timezone.now() - datetime.timedelta(days=3), days=7)
room = RoomFactory(meeting=meeting)
# get current time in meeting time zone
- right_now = now().astimezone(
+ right_now = timezone.now().astimezone(
pytz.timezone(meeting.time_zone)
)
if not settings.USE_TZ:
@@ -2048,7 +2048,7 @@ class InterimTests(IetfSeleniumTestCase):
Session.objects.filter(
meeting__type_id='interim',
timeslotassignments__schedule=F('meeting__schedule'),
- timeslotassignments__timeslot__time__gte=datetime.datetime.today()
+ timeslotassignments__timeslot__time__gte=timezone.now()
)
).filter(current_status__in=('sched','canceled'))
meetings = []
@@ -2061,7 +2061,7 @@ class InterimTests(IetfSeleniumTestCase):
def all_ietf_meetings(self):
meetings = Meeting.objects.filter(
type_id='ietf',
- date__gte=datetime.datetime.today()-datetime.timedelta(days=7)
+ date__gte=timezone.now()-datetime.timedelta(days=7)
)
for m in meetings:
m.calendar_label = 'IETF %s' % m.number
@@ -2609,7 +2609,7 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
self.meeting: Meeting = MeetingFactory(
type_id='ietf',
number=120,
- date=datetime.datetime.today() + datetime.timedelta(days=10),
+ date=timezone.now() + datetime.timedelta(days=10),
populate_schedule=False,
)
self.edit_timeslot_url = self.absreverse(
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index b5df0809b..2b6b20d1c 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -29,8 +29,8 @@ from django.test import Client, override_settings
from django.db.models import F, Max
from django.http import QueryDict, FileResponse
from django.template import Context, Template
+from django.utils import timezone
from django.utils.text import slugify
-from django.utils.timezone import now
import debug # pyflakes:ignore
@@ -604,7 +604,7 @@ class MeetingTests(BaseMeetingTestCase):
def test_interim_materials(self):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
- date = datetime.datetime.today() - datetime.timedelta(days=10)
+ date = timezone.now() - datetime.timedelta(days=10)
meeting = make_interim_meeting(group=group, date=date, status='sched')
session = meeting.session_set.first()
@@ -1529,7 +1529,7 @@ class EditMeetingScheduleTests(TestCase):
@staticmethod
def _right_now_in(tzname):
- right_now = now().astimezone(pytz.timezone(tzname))
+ right_now = timezone.now().astimezone(pytz.timezone(tzname))
if not settings.USE_TZ:
right_now = right_now.replace(tzinfo=None)
return right_now
@@ -1538,7 +1538,7 @@ class EditMeetingScheduleTests(TestCase):
"""Allow assignment to future timeslots only for official schedule"""
meeting = MeetingFactory(
type_id='ietf',
- date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(),
+ date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
@@ -1598,7 +1598,7 @@ class EditMeetingScheduleTests(TestCase):
"""Do not allow assignment of past sessions for official schedule"""
meeting = MeetingFactory(
type_id='ietf',
- date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(),
+ date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
@@ -1733,7 +1733,7 @@ class EditMeetingScheduleTests(TestCase):
"""Allow unassignment only of future timeslots for official schedule"""
meeting = MeetingFactory(
type_id='ietf',
- date=(datetime.datetime.today() - datetime.timedelta(days=1)).date(),
+ date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
right_now = self._right_now_in(meeting.time_zone)
@@ -1857,7 +1857,7 @@ class EditTimeslotsTests(TestCase):
return MeetingFactory(
type_id='ietf',
number=number,
- date=datetime.datetime.today() + datetime.timedelta(days=10),
+ date=timezone.now() + datetime.timedelta(days=10),
populate_schedule=False,
)
@@ -4680,7 +4680,7 @@ class InterimTests(TestCase):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
duration = datetime.timedelta(hours=3)
remote_instructions = 'Use webex'
@@ -4751,7 +4751,7 @@ class InterimTests(TestCase):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
date = datetime.date.today() + datetime.timedelta(days=30)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
duration = datetime.timedelta(hours=3)
city = 'San Francisco'
@@ -4799,7 +4799,7 @@ class InterimTests(TestCase):
make_meeting_test_data()
date = datetime.date.today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=1)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
dt2 = datetime.datetime.combine(date2, time)
duration = datetime.timedelta(hours=3)
@@ -4865,7 +4865,7 @@ class InterimTests(TestCase):
make_meeting_test_data()
date = datetime.date.today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=2)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
group = Group.objects.get(acronym='mars')
city = 'San Francisco'
country = 'US'
@@ -4933,7 +4933,7 @@ class InterimTests(TestCase):
if date.year != date2.year:
date += datetime.timedelta(days=1)
date2 += datetime.timedelta(days=1)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
dt2 = datetime.datetime.combine(date2, time)
duration = datetime.timedelta(hours=3)
@@ -5615,7 +5615,7 @@ class InterimTests(TestCase):
def test_send_interim_minutes_reminder(self):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
- date = datetime.datetime.today() - datetime.timedelta(days=10)
+ date = timezone.now() - datetime.timedelta(days=10)
meeting = make_interim_meeting(group=group, date=date, status='sched')
length_before = len(outbox)
send_interim_minutes_reminder(meeting=meeting)
@@ -6563,7 +6563,7 @@ class HasMeetingsTests(TestCase):
q = PyQuery(r.content)
self.assertTrue(q('#id_group option[value="%d"]'%group.pk))
date = datetime.date.today() + datetime.timedelta(days=30+meeting_count)
- time = datetime.datetime.now().time().replace(microsecond=0,second=0)
+ time = timezone.now().time().replace(microsecond=0,second=0)
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@@ -6659,7 +6659,7 @@ class HasMeetingsTests(TestCase):
session = SessionFactory(
group__type_id = gf.type_id,
meeting__type_id='interim',
- meeting__date = datetime.datetime.today()+datetime.timedelta(days=30),
+ meeting__date = timezone.now()+datetime.timedelta(days=30),
status_id='sched',
)
sessions.append(session)
@@ -6675,7 +6675,7 @@ class HasMeetingsTests(TestCase):
sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
- meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
+ meeting_date = timezone.now() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
@@ -6696,7 +6696,7 @@ class HasMeetingsTests(TestCase):
sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
- meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
+ meeting_date = timezone.now() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py
index 0d1a56270..f6c1800f1 100644
--- a/ietf/meeting/utils.py
+++ b/ietf/meeting/utils.py
@@ -12,6 +12,7 @@ from urllib.error import HTTPError
from django.conf import settings
from django.contrib import messages
from django.template.loader import render_to_string
+from django.utils import timezone
from django.utils.encoding import smart_text
import debug # pyflakes:ignore
@@ -511,7 +512,7 @@ def swap_meeting_schedule_timeslot_assignments(schedule, source_timeslots, targe
if max_overlap > datetime.timedelta(minutes=5):
for a in lts_assignments:
a.timeslot = most_overlapping_rts
- a.modified = datetime.datetime.now()
+ a.modified = timezone.now()
a.save()
swapped = True
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index 0c054db97..e4fc9dc17 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -36,10 +36,10 @@ from django.db.models import F, Max, Q
from django.forms.models import modelform_factory, inlineformset_factory
from django.template import TemplateDoesNotExist
from django.template.loader import render_to_string
+from django.utils import timezone
from django.utils.encoding import force_str
from django.utils.functional import curry
from django.utils.text import slugify
-from django.utils.timezone import now
from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from django.views.generic import RedirectView
@@ -128,7 +128,7 @@ def materials(request, num=None):
cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date()
now = datetime.date.today()
- old = datetime.datetime.now() - datetime.timedelta(days=1)
+ old = timezone.now() - datetime.timedelta(days=1)
if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET:
pass
elif now > cor_cut_off_date:
@@ -442,7 +442,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
lock_time = settings.MEETING_SESSION_LOCK_TIME
def timeslot_locked(ts):
- meeting_now = now().astimezone(pytz.timezone(meeting.time_zone))
+ meeting_now = timezone.now().astimezone(pytz.timezone(meeting.time_zone))
if not settings.USE_TZ:
meeting_now = meeting_now.replace(tzinfo=None)
return schedule.is_official and (ts.time - meeting_now < lock_time)
@@ -785,7 +785,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
timeslot=old_timeslot,
)
- existing_assignments.update(timeslot=timeslot, modified=datetime.datetime.now())
+ existing_assignments.update(timeslot=timeslot, modified=timezone.now())
else:
SchedTimeSessAssignment.objects.create(
session=session,
@@ -1560,7 +1560,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
"updated": updated,
"filter_categories": filter_organizer.get_filter_categories(),
"non_area_keywords": filter_organizer.get_non_area_keywords(),
- "now": datetime.datetime.now().astimezone(pytz.UTC),
+ "now": timezone.now().astimezone(pytz.utc),
"timezone": meeting.time_zone,
"is_current_meeting": is_current_meeting,
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
@@ -2454,7 +2454,7 @@ def session_details(request, num, acronym):
'can_manage_materials' : can_manage,
'can_view_request': can_view_request,
'thisweek': datetime.date.today()-datetime.timedelta(days=7),
- 'now': datetime.datetime.now(),
+ 'now': timezone.now(),
'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
})
@@ -3542,7 +3542,7 @@ def interim_request_edit(request, number):
@cache_page(60*60)
def past(request):
'''List of past meetings'''
- today = datetime.datetime.today()
+ today = timezone.now()
meetings = data_for_meetings_overview(Meeting.objects.filter(date__lte=today).order_by('-date'))
@@ -3615,7 +3615,7 @@ def upcoming(request):
'menu_actions': actions,
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
- 'now': datetime.datetime.now(),
+ 'now': timezone.now(),
'use_codimd': True if datetime.date.today()>=settings.MEETING_USES_CODIMD_DATE else False,
})
diff --git a/ietf/message/management/commands/show_messages.py b/ietf/message/management/commands/show_messages.py
index b232da481..8741b2129 100644
--- a/ietf/message/management/commands/show_messages.py
+++ b/ietf/message/management/commands/show_messages.py
@@ -6,6 +6,7 @@ import email
import datetime
from django.core.management.base import BaseCommand
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -25,7 +26,7 @@ class Command(BaseCommand):
"""
def add_arguments(self, parser):
- default_start = datetime.datetime.now() - datetime.timedelta(days=14)
+ default_start = timezone.now() - datetime.timedelta(days=14)
parser.add_argument(
'-t', '--start', '--from', type=str, default=default_start.strftime('%Y-%m-%d %H:%M'),
help='Limit the list to messages saved after the given time (default %(default)s).',
diff --git a/ietf/message/migrations/0012_use_timezone_now_for_message_models.py b/ietf/message/migrations/0012_use_timezone_now_for_message_models.py
new file mode 100644
index 000000000..dbef893ea
--- /dev/null
+++ b/ietf/message/migrations/0012_use_timezone_now_for_message_models.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('message', '0011_auto_20201109_0439'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='message',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='sendqueue',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/ietf/message/models.py b/ietf/message/models.py
index 01162a4b6..fe2c8d325 100644
--- a/ietf/message/models.py
+++ b/ietf/message/models.py
@@ -2,10 +2,10 @@
# -*- coding: utf-8 -*-
-import datetime
import email.utils
from django.db import models
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -17,7 +17,7 @@ from ietf.utils.models import ForeignKey
from ietf.utils.mail import get_email_addresses_from_text
class Message(models.Model):
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
by = ForeignKey(Person)
subject = models.CharField(max_length=255)
@@ -62,7 +62,7 @@ class MessageAttachment(models.Model):
class SendQueue(models.Model):
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
by = ForeignKey(Person)
message = ForeignKey(Message)
diff --git a/ietf/message/tests.py b/ietf/message/tests.py
index 4ab790699..c1f22b3d0 100644
--- a/ietf/message/tests.py
+++ b/ietf/message/tests.py
@@ -5,6 +5,7 @@
import datetime
from django.urls import reverse as urlreverse
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -87,7 +88,7 @@ class SendScheduledAnnouncementsTests(TestCase):
q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"),
message=msg,
- send_at=datetime.datetime.now() + datetime.timedelta(hours=12)
+ send_at=timezone.now() + datetime.timedelta(hours=12)
)
mailbox_before = len(outbox)
@@ -113,7 +114,7 @@ class SendScheduledAnnouncementsTests(TestCase):
q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"),
message=msg,
- send_at=datetime.datetime.now() + datetime.timedelta(hours=12)
+ send_at=timezone.now() + datetime.timedelta(hours=12)
)
mailbox_before = len(outbox)
diff --git a/ietf/message/utils.py b/ietf/message/utils.py
index 65c018a39..2601eccab 100644
--- a/ietf/message/utils.py
+++ b/ietf/message/utils.py
@@ -2,8 +2,9 @@
# -*- coding: utf-8 -*-
-import re, datetime, email
+import re, email
+from django.utils import timezone
from django.utils.encoding import force_str
from ietf.utils.mail import send_mail_text, send_mail_mime
@@ -52,7 +53,7 @@ def send_scheduled_message_from_send_queue(queue_item):
send_mail_mime(None, message.to, message.frm, message.subject,
msg, cc=message.cc, bcc=message.bcc)
- queue_item.sent_at = datetime.datetime.now()
+ queue_item.sent_at = timezone.now()
queue_item.save()
queue_item.message.sent = queue_item.sent_at
diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py
index b8ffcf103..5c4e1ba2d 100644
--- a/ietf/nomcom/tests.py
+++ b/ietf/nomcom/tests.py
@@ -17,6 +17,7 @@ from django.conf import settings
from django.core.files import File
from django.contrib.auth.models import User
from django.urls import reverse
+from django.utils import timezone
from django.utils.encoding import force_str
import debug # pyflakes:ignore
@@ -1389,7 +1390,7 @@ class FeedbackLastSeenTests(TestCase):
f.nominees.add(self.nominee)
f = FeedbackFactory.create(author=self.author,nomcom=self.nc,type_id='comment')
f.topics.add(self.topic)
- now = datetime.datetime.now()
+ now = timezone.now()
self.hour_ago = now - datetime.timedelta(hours=1)
self.half_hour_ago = now - datetime.timedelta(minutes=30)
self.second_from_now = now + datetime.timedelta(seconds=1)
diff --git a/ietf/person/management/commands/purge_old_personal_api_key_events.py b/ietf/person/management/commands/purge_old_personal_api_key_events.py
index e60f784b5..47674e2d5 100644
--- a/ietf/person/management/commands/purge_old_personal_api_key_events.py
+++ b/ietf/person/management/commands/purge_old_personal_api_key_events.py
@@ -1,9 +1,10 @@
# Copyright The IETF Trust 2021, All Rights Reserved
# -*- coding: utf-8 -*-
-from datetime import datetime, timedelta
+from datetime import timedelta
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Max, Min
+from django.utils import timezone
from ietf.person.models import PersonApiKeyEvent
@@ -33,7 +34,7 @@ class Command(BaseCommand):
self.stdout.write('Finding events older than {}\n'.format(_format_count(keep_days)))
self.stdout.flush()
- now = datetime.now()
+ now = timezone.now()
old_events = PersonApiKeyEvent.objects.filter(
time__lt=now - timedelta(days=keep_days)
)
diff --git a/ietf/person/management/commands/tests.py b/ietf/person/management/commands/tests.py
index f30a80bf5..291a6ace5 100644
--- a/ietf/person/management/commands/tests.py
+++ b/ietf/person/management/commands/tests.py
@@ -5,6 +5,7 @@ import datetime
from io import StringIO
from django.core.management import call_command, CommandError
+from django.utils import timezone
from ietf.person.factories import PersonApiKeyEventFactory
from ietf.person.models import PersonApiKeyEvent, PersonEvent
@@ -51,7 +52,7 @@ class CommandTests(TestCase):
# Remember how many PersonEvents were present so we can verify they're cleaned up properly.
personevents_before = PersonEvent.objects.count()
- now = datetime.datetime.now()
+ now = timezone.now()
# The first of these events will be timestamped a fraction of a second more than keep_days
# days ago by the time we call the management command, so will just barely chosen for purge.
old_events = [
@@ -101,7 +102,7 @@ class CommandTests(TestCase):
def test_purge_old_personal_api_key_events_rejects_invalid_arguments(self):
"""The purge_old_personal_api_key_events command should reject invalid arguments"""
- event = PersonApiKeyEventFactory(time=datetime.datetime.now() - datetime.timedelta(days=30))
+ event = PersonApiKeyEventFactory(time=timezone.now() - datetime.timedelta(days=30))
with self.assertRaises(CommandError):
self._call_command('purge_old_personal_api_key_events')
diff --git a/ietf/person/migrations/0025_use_timezone_now_for_person_models.py b/ietf/person/migrations/0025_use_timezone_now_for_person_models.py
new file mode 100644
index 000000000..22dee042c
--- /dev/null
+++ b/ietf/person/migrations/0025_use_timezone_now_for_person_models.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('person', '0024_pronouns'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='historicalperson',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='person',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='personalapikey',
+ name='created',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='personevent',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text='When the event happened'),
+ ),
+ ]
diff --git a/ietf/person/models.py b/ietf/person/models.py
index 143b8dd08..7fff66d9a 100644
--- a/ietf/person/models.py
+++ b/ietf/person/models.py
@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
-import datetime
import email.utils
import email.header
import jsonfield
@@ -18,6 +17,7 @@ from django.core.validators import validate_email
from django.db import models
from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.encoding import smart_bytes
from django.utils.text import slugify
@@ -38,7 +38,7 @@ from ietf.utils.models import ForeignKey, OneToOneField
class Person(models.Model):
history = HistoricalRecords()
user = OneToOneField(User, blank=True, null=True, on_delete=models.SET_NULL)
- time = models.DateTimeField(default=datetime.datetime.now) # When this Person record entered the system
+ time = models.DateTimeField(default=timezone.now) # When this Person record entered the system
# The normal unicode form of the name. This must be
# set to the same value as the ascii-form if equal.
name = models.CharField("Full Name (Unicode)", max_length=255, db_index=True, help_text="Preferred long form of name.")
@@ -377,7 +377,7 @@ PERSON_API_KEY_ENDPOINTS = sorted(list(set([ (v, n) for (v, n, r) in PERSON_API_
class PersonalApiKey(models.Model):
person = ForeignKey(Person, related_name='apikeys')
endpoint = models.CharField(max_length=128, null=False, blank=False, choices=PERSON_API_KEY_ENDPOINTS)
- created = models.DateTimeField(default=datetime.datetime.now, null=False)
+ created = models.DateTimeField(default=timezone.now, null=False)
valid = models.BooleanField(default=True)
salt = models.BinaryField(default=salt, max_length=12, null=False, blank=False)
count = models.IntegerField(default=0, null=False, blank=False)
@@ -427,7 +427,7 @@ PERSON_EVENT_CHOICES = [
class PersonEvent(models.Model):
person = ForeignKey(Person)
- time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
+ time = models.DateTimeField(default=timezone.now, help_text="When the event happened")
type = models.CharField(max_length=50, choices=PERSON_EVENT_CHOICES)
desc = models.TextField()
diff --git a/ietf/person/tests.py b/ietf/person/tests.py
index 59228a795..16bcdf6da 100644
--- a/ietf/person/tests.py
+++ b/ietf/person/tests.py
@@ -13,6 +13,7 @@ from pyquery import PyQuery
from django.http import HttpRequest
from django.test import override_settings
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.encoding import iri_to_uri
import debug # pyflakes:ignore
@@ -211,7 +212,7 @@ class PersonUtilsTests(TestCase):
self.assertEqual(results,(p1,p3))
# both have User
- today = datetime.datetime.today()
+ today = timezone.now()
p2.user.last_login = today
p2.user.save()
p4.user.last_login = today - datetime.timedelta(days=30)
diff --git a/ietf/review/factories.py b/ietf/review/factories.py
index cf66f1ed8..d6780fad8 100644
--- a/ietf/review/factories.py
+++ b/ietf/review/factories.py
@@ -2,6 +2,8 @@
import factory
import datetime
+from django.utils import timezone
+
from ietf.review.models import ReviewTeamSettings, ReviewRequest, ReviewAssignment, ReviewerSettings
from ietf.name.models import ReviewTypeName, ReviewResultName
@@ -39,7 +41,7 @@ class ReviewRequestFactory(factory.django.DjangoModelFactory):
type_id = 'lc'
doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft')
team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='review')
- deadline = datetime.datetime.today()+datetime.timedelta(days=14)
+ deadline = timezone.now()+datetime.timedelta(days=14)
requested_by = factory.SubFactory('ietf.person.factories.PersonFactory')
class ReviewAssignmentFactory(factory.django.DjangoModelFactory):
@@ -49,7 +51,7 @@ class ReviewAssignmentFactory(factory.django.DjangoModelFactory):
review_request = factory.SubFactory('ietf.review.factories.ReviewRequestFactory')
state_id = 'assigned'
reviewer = factory.SubFactory('ietf.person.factories.EmailFactory')
- assigned_on = datetime.datetime.now()
+ assigned_on = timezone.now()
class ReviewerSettingsFactory(factory.django.DjangoModelFactory):
class Meta:
diff --git a/ietf/review/migrations/0029_use_timezone_now_for_review_models.py b/ietf/review/migrations/0029_use_timezone_now_for_review_models.py
new file mode 100644
index 000000000..a8a0558d4
--- /dev/null
+++ b/ietf/review/migrations/0029_use_timezone_now_for_review_models.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('review', '0028_auto_20220513_1456'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='historicalreviewrequest',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='reviewrequest',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='reviewwish',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/ietf/review/models.py b/ietf/review/models.py
index 96355417d..0ec367819 100644
--- a/ietf/review/models.py
+++ b/ietf/review/models.py
@@ -7,6 +7,7 @@ import datetime
from simple_history.models import HistoricalRecords
from django.db import models
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -95,7 +96,7 @@ class UnavailablePeriod(models.Model):
class ReviewWish(models.Model):
"""Reviewer wishes to review a document when it becomes available for review."""
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None))
person = ForeignKey(Person)
doc = ForeignKey(Document)
@@ -125,7 +126,7 @@ class ReviewRequest(models.Model):
# Fields filled in on the initial record creation - these
# constitute the request part.
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
type = ForeignKey(ReviewTypeName)
doc = ForeignKey(Document, related_name='reviewrequest_set')
team = ForeignKey(Group, limit_choices_to=~models.Q(reviewteamsettings=None))
diff --git a/ietf/review/utils.py b/ietf/review/utils.py
index 3fec1b84f..f665ba06b 100644
--- a/ietf/review/utils.py
+++ b/ietf/review/utils.py
@@ -12,6 +12,8 @@ from django.template.defaultfilters import pluralize
from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
from django.contrib.sites.models import Site
+from django.utils import timezone
+
from simple_history.utils import update_change_reason
import debug # pyflakes:ignore
@@ -119,7 +121,7 @@ def days_needed_to_fulfill_min_interval_for_reviewers(team):
min_intervals = dict(ReviewerSettings.objects.filter(team=team).values_list("person_id", "min_interval"))
- now = datetime.datetime.now()
+ now = timezone.now()
res = {}
for person_id, latest_assignment_time in latest_assignments.items():
@@ -495,7 +497,7 @@ def suggested_review_requests_for_team(team):
requests = {}
- now = datetime.datetime.now()
+ now = timezone.now()
reviewable_docs_qs = Document.objects.filter(type="draft").exclude(stream="ise")
diff --git a/ietf/secr/meetings/tests.py b/ietf/secr/meetings/tests.py
index c6576a0cc..2e90a23dc 100644
--- a/ietf/secr/meetings/tests.py
+++ b/ietf/secr/meetings/tests.py
@@ -14,6 +14,7 @@ import debug # pyflakes:ignore
from django.conf import settings
from django.urls import reverse
+from django.utils import timezone
from ietf.group.models import Group, GroupEvent
from ietf.meeting.factories import MeetingFactory
@@ -212,8 +213,8 @@ class SecrMeetingTestCase(TestCase):
self.assertEqual(q('#id_notification_list').html(),'ames, mars')
# test that only changes since last notification show up
- now = datetime.datetime.now()
- then = datetime.datetime.now()+datetime.timedelta(hours=1)
+ now = timezone.now()
+ then = timezone.now()+datetime.timedelta(hours=1)
person = Person.objects.get(name="(System)")
GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification',
by=person,desc='sent scheduled notification for %s' % meeting)
diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py
index 7d6a227dc..cf6252cc0 100644
--- a/ietf/secr/meetings/views.py
+++ b/ietf/secr/meetings/views.py
@@ -11,6 +11,7 @@ from django.db.models import IntegerField
from django.db.models.functions import Cast
from django.forms.models import inlineformset_factory
from django.shortcuts import render, get_object_or_404, redirect
+from django.utils import timezone
from django.utils.text import slugify
import debug # pyflakes:ignore
@@ -121,7 +122,7 @@ def send_notifications(meeting, groups, person):
Send session scheduled email notifications for each group in groups. Person is the
user who initiated this action, request.uesr.get_profile().
'''
- now = datetime.datetime.now()
+ now = timezone.now()
for group in groups:
sessions = group.session_set.filter(meeting=meeting)
addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0])
diff --git a/ietf/secr/proceedings/reports.py b/ietf/secr/proceedings/reports.py
index 115e4facd..49b632822 100644
--- a/ietf/secr/proceedings/reports.py
+++ b/ietf/secr/proceedings/reports.py
@@ -1,6 +1,7 @@
import datetime
from django.template.loader import render_to_string
+from django.utils import timezone
from ietf.meeting.models import Meeting
from ietf.doc.models import DocEvent, Document
@@ -9,7 +10,7 @@ from ietf.secr.proceedings.proc_utils import get_progress_stats
def report_id_activity(start,end):
# get previous meeting
- meeting = Meeting.objects.filter(date__lt=datetime.datetime.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('-')
eyear,emonth,eday = end.split('-')
sdate = datetime.datetime(int(syear),int(smonth),int(sday))
diff --git a/ietf/secr/proceedings/tests_reports.py b/ietf/secr/proceedings/tests_reports.py
index 6039cfced..034dbe5fa 100644
--- a/ietf/secr/proceedings/tests_reports.py
+++ b/ietf/secr/proceedings/tests_reports.py
@@ -1,6 +1,8 @@
import datetime
import debug # pyflakes:ignore
+from django.utils import timezone
+
from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory
from ietf.secr.proceedings.reports import report_id_activity, report_progress_report
from ietf.utils.test_utils import TestCase
@@ -10,7 +12,7 @@ class ReportsTestCase(TestCase):
def test_report_id_activity(self):
- today = datetime.datetime.today()
+ today = timezone.now()
yesterday = today - datetime.timedelta(days=1)
last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7)
@@ -24,7 +26,7 @@ class ReportsTestCase(TestCase):
self.assertTrue('IETF Activity since last IETF Meeting' in result)
def test_report_progress_report(self):
- today = datetime.datetime.today()
+ today = timezone.now()
last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7)
diff --git a/ietf/secr/telechat/tests.py b/ietf/secr/telechat/tests.py
index f664745d7..7a65cb40d 100644
--- a/ietf/secr/telechat/tests.py
+++ b/ietf/secr/telechat/tests.py
@@ -8,6 +8,7 @@ from pyquery import PyQuery
import debug # pyflakes:ignore
from django.urls import reverse
+from django.utils import timezone
from ietf.doc.factories import (WgDraftFactory, IndividualRfcFactory, CharterFactory,
IndividualDraftFactory, ConflictReviewFactory)
@@ -22,7 +23,7 @@ from ietf.secr.telechat.views import get_next_telechat_date
SECR_USER='secretary'
def augment_data():
- TelechatDate.objects.create(date=datetime.datetime.today())
+ TelechatDate.objects.create(date=timezone.now())
class SecrTelechatTestCase(TestCase):
def test_main(self):
@@ -138,7 +139,7 @@ class SecrTelechatTestCase(TestCase):
self.assertEqual(q("#telechat-positions-table").find("th:contains('No Record')").length,1)
def test_bash(self):
- today = datetime.datetime.today()
+ today = timezone.now()
TelechatDate.objects.create(date=today)
url = reverse('ietf.secr.telechat.views.bash',kwargs={'date':today.strftime('%Y-%m-%d')})
self.client.login(username="secretary", password="secretary+password")
diff --git a/ietf/stats/management/commands/fetch_meeting_attendance.py b/ietf/stats/management/commands/fetch_meeting_attendance.py
index 5078c7cee..7f936531d 100644
--- a/ietf/stats/management/commands/fetch_meeting_attendance.py
+++ b/ietf/stats/management/commands/fetch_meeting_attendance.py
@@ -1,10 +1,10 @@
# Copyright The IETF Trust 2017-2019, All Rights Reserved
# Copyright 2016 IETF Trust
-import datetime
import syslog
from django.core.management.base import BaseCommand, CommandError
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -32,7 +32,7 @@ class Command(BaseCommand):
elif options['all']:
meetings = Meeting.objects.filter(type="ietf").order_by("date")
elif options['latest']:
- meetings = Meeting.objects.filter(type="ietf", date__lte=datetime.datetime.today()).order_by("-date")[:options['latest']]
+ meetings = Meeting.objects.filter(type="ietf", date__lte=timezone.now()).order_by("-date")[:options['latest']]
else:
raise CommandError("Please use one of --meeting, --all or --latest")
diff --git a/ietf/stats/tests.py b/ietf/stats/tests.py
index d34d7b11b..13b81d934 100644
--- a/ietf/stats/tests.py
+++ b/ietf/stats/tests.py
@@ -13,6 +13,7 @@ from requests import Response
import debug # pyflakes:ignore
from django.urls import reverse as urlreverse
+from django.utils import timezone
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
import ietf.stats.views
@@ -63,7 +64,7 @@ class StatisticsTests(TestCase):
Document.objects.filter(pk=draft.pk).update(words=4000)
# move it back so it shows up in the yearly summaries
NewRevisionDocEvent.objects.filter(doc=draft, rev=draft.rev).update(
- time=datetime.datetime.now() - datetime.timedelta(days=500))
+ time=timezone.now() - datetime.timedelta(days=500))
referencing_draft = Document.objects.create(
name="draft-ietf-mars-referencing",
@@ -88,7 +89,7 @@ class StatisticsTests(TestCase):
doc=referencing_draft,
desc="New revision available",
rev=referencing_draft.rev,
- time=datetime.datetime.now() - datetime.timedelta(days=1000)
+ time=timezone.now() - datetime.timedelta(days=1000)
)
diff --git a/ietf/stats/views.py b/ietf/stats/views.py
index 593fc1947..97cf1178a 100644
--- a/ietf/stats/views.py
+++ b/ietf/stats/views.py
@@ -18,6 +18,7 @@ from django.db.models import Count, Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.text import slugify
@@ -196,7 +197,7 @@ def document_stats(request, stats_type=None):
if "y" in time_choice:
try:
y = int(time_choice.rstrip("y"))
- from_time = datetime.datetime.today() - dateutil.relativedelta.relativedelta(years=y)
+ from_time = timezone.now() - dateutil.relativedelta.relativedelta(years=y)
except ValueError:
pass
diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py
index 34f568d2e..857e6879c 100644
--- a/ietf/submit/forms.py
+++ b/ietf/submit/forms.py
@@ -21,6 +21,7 @@ from django import forms
from django.conf import settings
from django.utils.html import mark_safe, format_html # type:ignore
from django.urls import reverse as urlreverse
+from django.utils import timezone
from django.utils.encoding import force_str
import debug # pyflakes:ignore
@@ -79,7 +80,7 @@ class SubmissionBaseUploadForm(forms.Form):
self.base_formats = None # None will raise an exception in clean() if this isn't changed in a subclass
def set_cutoff_warnings(self):
- now = datetime.datetime.now(pytz.utc)
+ now = timezone.now().astimezone(pytz.utc)
meeting = Meeting.get_current_meeting()
if not meeting:
return
diff --git a/ietf/submit/migrations/0011_use_timezone_now_for_submit_models.py b/ietf/submit/migrations/0011_use_timezone_now_for_submit_models.py
new file mode 100644
index 000000000..69d40bf8a
--- /dev/null
+++ b/ietf/submit/migrations/0011_use_timezone_now_for_submit_models.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.28 on 2022-07-12 11:24
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('submit', '0010_create_cancel_stale_submissions_task'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='preapproval',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='submissioncheck',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AlterField(
+ model_name='submissionevent',
+ name='time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/ietf/submit/models.py b/ietf/submit/models.py
index 9f0e2fa33..629303296 100644
--- a/ietf/submit/models.py
+++ b/ietf/submit/models.py
@@ -7,6 +7,7 @@ import email
import jsonfield
from django.db import models
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -120,7 +121,7 @@ class Submission(models.Model):
class SubmissionCheck(models.Model):
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
submission = ForeignKey(Submission, related_name='checks')
checker = models.CharField(max_length=256, blank=True)
passed = models.BooleanField(null=True, default=False)
@@ -139,7 +140,7 @@ class SubmissionCheck(models.Model):
class SubmissionEvent(models.Model):
submission = ForeignKey(Submission)
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
by = ForeignKey(Person, null=True, blank=True)
desc = models.TextField()
@@ -157,7 +158,7 @@ class Preapproval(models.Model):
"""Pre-approved draft submission name."""
name = models.CharField(max_length=255, db_index=True)
by = ForeignKey(Person)
- time = models.DateTimeField(default=datetime.datetime.now)
+ time = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.name
diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py
index 70baff432..86815f59f 100644
--- a/ietf/submit/tests.py
+++ b/ietf/submit/tests.py
@@ -287,7 +287,7 @@ class SubmitTests(BaseSubmitTestCase):
# prepare draft to suggest replace
sug_replaced_draft = Document.objects.create(
name="draft-ietf-ames-sug-replaced",
- time=datetime.datetime.now(),
+ time=timezone.now(),
type_id="draft",
title="Draft to be suggested to be replaced",
stream_id="ietf",
@@ -298,7 +298,7 @@ class SubmitTests(BaseSubmitTestCase):
words=100,
intended_std_level_id="ps",
ad=draft.ad,
- expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
+ expires=timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
notify="aliens@example.mars",
note="",
)
@@ -357,7 +357,7 @@ class SubmitTests(BaseSubmitTestCase):
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, "%s-%s.txt" % (name, rev))))
self.assertEqual(draft.type_id, "draft")
self.assertEqual(draft.stream_id, "ietf")
- self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1))
+ self.assertTrue(draft.expires >= timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1))
self.assertEqual(draft.get_state("draft-stream-%s" % draft.stream_id).slug, "wg-doc")
authors = draft.documentauthor_set.all()
self.assertEqual(len(authors), 1)
@@ -2320,7 +2320,7 @@ Subject: test submission via email
Please submit my draft at http://test.com/mydraft.txt
Thank you
-""".format(datetime.datetime.now().ctime())
+""".format(timezone.now().ctime())
message = email.message_from_string(force_str(message_string))
submission, submission_email_event = (
add_submission_email(request=None,
@@ -2402,7 +2402,7 @@ Content-Disposition: attachment; filename="attach.txt"
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
--------------090908050800030909090207--
-""".format(frm, datetime.datetime.now().ctime())
+""".format(frm, timezone.now().ctime())
message = email.message_from_string(force_str(message_string))
submission, submission_email_event = (
@@ -2458,7 +2458,7 @@ Content-Disposition: attachment; filename="attachment.txt"
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
--------------090908050800030909090207--
-""".format(datetime.datetime.now().ctime())
+""".format(timezone.now().ctime())
# Back to secretariat
self.client.login(username="secretary", password="secretary+password")
@@ -2597,7 +2597,7 @@ Subject: Another message
About my submission
Thank you
-""".format(datetime.datetime.now().ctime())
+""".format(timezone.now().ctime())
r = self.client.post(add_email_url, {
"name": "{}-{}".format(submission.name, submission.rev),
@@ -2664,7 +2664,7 @@ Thank you
From: {}
Date: {}
Subject: test
-""".format(reply_to, to, datetime.datetime.now().ctime())
+""".format(reply_to, to, timezone.now().ctime())
result = process_response_email(message_string)
self.assertIsInstance(result, Message)
diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py
index f0a78c5cf..ea51ab18f 100644
--- a/ietf/submit/utils.py
+++ b/ietf/submit/utils.py
@@ -22,6 +22,7 @@ from django.http import HttpRequest # pyflakes:ignore
from django.utils.module_loading import import_string
from django.template.loader import render_to_string
from django.contrib.auth.models import AnonymousUser
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -338,7 +339,7 @@ def post_submission(request, submission, approved_doc_desc, approved_subm_desc):
if stream_slug:
draft.stream = StreamName.objects.get(slug=stream_slug)
- draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
+ draft.expires = timezone.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
log.log(f"{submission.name}: got draft details")
events = []
@@ -616,7 +617,7 @@ def ensure_person_email_info_exists(name, email, docname):
email.active = active
email.person = person
if email.time is None:
- email.time = datetime.datetime.now()
+ email.time = timezone.now()
email.origin = "author: %s" % docname
email.save()
diff --git a/ietf/sync/iana.py b/ietf/sync/iana.py
index 9d0f66435..cf072d4c7 100644
--- a/ietf/sync/iana.py
+++ b/ietf/sync/iana.py
@@ -10,6 +10,7 @@ import re
import requests
from django.conf import settings
+from django.utils import timezone
from django.utils.encoding import smart_bytes, force_str
from django.utils.http import urlquote
@@ -241,7 +242,7 @@ def parse_review_email(text):
doc_name = strip_version_extension(doc_name)
# date
- review_time = datetime.datetime.now()
+ review_time = timezone.now()
if "Date" in msg:
review_time = email_time_to_local_timezone(msg["Date"])
diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index c2264a0d3..6feb86308 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -11,6 +11,7 @@ from urllib.parse import urlencode
from xml.dom import pulldom, Node
from django.conf import settings
+from django.utils import timezone
from django.utils.encoding import smart_bytes, force_str, force_text
import debug # pyflakes:ignore
@@ -443,7 +444,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
# at the moment because the data only has month/year, so
# try to deduce it
d = datetime.datetime.combine(rfc_published_date, datetime.time())
- synthesized = datetime.datetime.now()
+ synthesized = timezone.now()
if abs(d - synthesized) > datetime.timedelta(days=60):
synthesized = d
else:
diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index 61bab795c..6b8186d78 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -10,6 +10,7 @@ import quopri
from django.conf import settings
from django.urls import reverse as urlreverse
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -34,11 +35,11 @@ class IANASyncTests(TestCase):
self.assertEqual(len(rfc_names), 1)
self.assertEqual(rfc_names[0], "rfc1234")
- iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1))
+ iana.update_rfc_log_from_protocol_page(rfc_names, timezone.now() - datetime.timedelta(days=1))
self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1)
# make sure it doesn't create duplicates
- iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1))
+ iana.update_rfc_log_from_protocol_page(rfc_names, timezone.now() - datetime.timedelta(days=1))
self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1)
def test_changes_sync(self):
diff --git a/ietf/sync/views.py b/ietf/sync/views.py
index d6fc505c2..1d22c424c 100644
--- a/ietf/sync/views.py
+++ b/ietf/sync/views.py
@@ -12,6 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render
+from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from ietf.doc.models import DeletedEvent, StateDocEvent, DocEvent
@@ -118,12 +119,12 @@ def rfceditor_undo(request):
events = []
events.extend(StateDocEvent.objects.filter(
state_type="draft-rfceditor",
- time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
+ time__gte=timezone.now() - datetime.timedelta(weeks=1)
).order_by("-time", "-id"))
events.extend(DocEvent.objects.filter(
type="sync_from_rfc_editor",
- time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
+ time__gte=timezone.now() - datetime.timedelta(weeks=1)
).order_by("-time", "-id"))
events.sort(key=lambda e: (e.time, e.id), reverse=True)
diff --git a/ietf/utils/decorators.py b/ietf/utils/decorators.py
index 694e989f8..634724880 100644
--- a/ietf/utils/decorators.py
+++ b/ietf/utils/decorators.py
@@ -10,6 +10,7 @@ from django.conf import settings
from django.contrib.auth import login
from django.http import HttpResponse
from django.shortcuts import render
+from django.utils import timezone
from django.utils.encoding import force_bytes
import debug # pyflakes:ignore
@@ -64,7 +65,7 @@ def require_api_key(f, request, *args, **kwargs):
person = key.person
last_login = person.user.last_login
if not person.user.is_staff:
- time_limit = (datetime.datetime.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS))
+ time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS))
if last_login == None or last_login < time_limit:
return err(400, "Too long since last regular login")
# Log in
@@ -74,7 +75,7 @@ def require_api_key(f, request, *args, **kwargs):
person.user.save()
# Update stats
key.count += 1
- key.latest = datetime.datetime.now()
+ key.latest = timezone.now()
key.save()
PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint))
# Execute decorated function
diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py
index 04c0ed916..3500d888e 100644
--- a/ietf/utils/mail.py
+++ b/ietf/utils/mail.py
@@ -3,7 +3,6 @@
import copy
-import datetime
#import logging
import re
import smtplib
@@ -27,6 +26,7 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import validate_email
from django.template.loader import render_to_string
from django.template import Context,RequestContext
+from django.utils import timezone
from django.utils.encoding import force_text, force_str, force_bytes
import debug # pyflakes:ignore
@@ -324,7 +324,7 @@ def show_that_mail_was_sent(request,leadline,msg,bcc):
if request and request.user:
from ietf.ietfauth.utils import has_role
if has_role(request.user,['Area Director','Secretariat','IANA','RFC Editor','ISE','IAD','IRTF Chair','WG Chair','RG Chair','WG Secretary','RG Secretary']):
- info = "%s at %s %s\n" % (leadline,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),settings.TIME_ZONE)
+ info = "%s at %s %s\n" % (leadline,timezone.now().strftime("%Y-%m-%d %H:%M:%S"),settings.TIME_ZONE)
info += "Subject: %s\n" % force_text(msg.get('Subject','[no subject]'))
info += "To: %s\n" % msg.get('To','[no to]')
if msg.get('Cc'):
@@ -378,7 +378,7 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
try:
send_smtp(msg, bcc)
if save:
- message.sent = datetime.datetime.now()
+ message.sent = timezone.now()
message.save()
if settings.SERVER_MODE != 'development':
show_that_mail_was_sent(request,'Email was sent',msg,bcc)
@@ -505,7 +505,7 @@ def send_mail_message(request, message, extra=None):
# msg = send_mail_text(request, message.to, message.frm, message.subject,
# message.body, cc=message.cc, bcc=message.bcc, extra=e, save=False)
- message.sent = datetime.datetime.now()
+ message.sent = timezone.now()
message.save()
return msg
diff --git a/ietf/utils/management/commands/check_draft_event_revision_integrity.py b/ietf/utils/management/commands/check_draft_event_revision_integrity.py
index a4c38bd1c..c8d2cbd21 100644
--- a/ietf/utils/management/commands/check_draft_event_revision_integrity.py
+++ b/ietf/utils/management/commands/check_draft_event_revision_integrity.py
@@ -13,6 +13,7 @@ import django
django.setup()
from django.core.management.base import BaseCommand #, CommandError
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -29,7 +30,7 @@ class Command(BaseCommand):
"""
def add_arguments(self, parser):
- default_start = datetime.datetime.now() - datetime.timedelta(days=60)
+ default_start = timezone.now() - datetime.timedelta(days=60)
parser.add_argument(
'-d', '--from', type=str, default=default_start.strftime('%Y-%m-%d'),
help='Limit the list to messages saved after the given date (default %(default)s).',
diff --git a/ietf/utils/management/commands/fix_ambiguous_timestamps.py b/ietf/utils/management/commands/fix_ambiguous_timestamps.py
index fc872f458..84a82bb30 100644
--- a/ietf/utils/management/commands/fix_ambiguous_timestamps.py
+++ b/ietf/utils/management/commands/fix_ambiguous_timestamps.py
@@ -10,6 +10,7 @@ from django.apps import apps
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db import models
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -53,7 +54,7 @@ class Command(BaseCommand):
def handle(self, *app_labels, **options):
self.verbosity = options['verbosity']
self.quiet = self.verbosity < 1
- stop = datetime.datetime.now()
+ stop = timezone.now()
start = stop - datetime.timedelta(days=14)
for name, appconf in apps.app_configs.items():
diff --git a/ietf/utils/management/commands/send_gdpr_consent_request.py b/ietf/utils/management/commands/send_gdpr_consent_request.py
index 2bb08eed1..152fc83fe 100644
--- a/ietf/utils/management/commands/send_gdpr_consent_request.py
+++ b/ietf/utils/management/commands/send_gdpr_consent_request.py
@@ -7,6 +7,7 @@ import time
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
+from django.utils import timezone
import debug # pyflakes:ignore
@@ -65,7 +66,7 @@ class Command(BaseCommand):
delay = 1.0/options['rate']
# --minimum_interval
minimum_interval = options['minimum_interval']
- latest_previous = datetime.datetime.now() - datetime.timedelta(days=minimum_interval)
+ latest_previous = timezone.now() - datetime.timedelta(days=minimum_interval)
# user
self.stdout.write('Querying the database for matching person records ...')
if 'user' in options and options['user']:
diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py
index 7a7a77af4..cd5a06e83 100644
--- a/ietf/utils/test_data.py
+++ b/ietf/utils/test_data.py
@@ -6,6 +6,7 @@ import datetime
from django.conf import settings
from django.contrib.auth.models import User
+from django.utils import timezone
from django.utils.encoding import smart_text
import debug # pyflakes:ignore
@@ -271,14 +272,14 @@ def make_test_data():
# old draft
old_draft = Document.objects.create(
name="draft-foo-mars-test",
- time=datetime.datetime.now() - datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
+ time=timezone.now() - datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
type_id="draft",
title="Optimizing Martian Network Topologies",
stream_id="ietf",
abstract="Techniques for achieving near-optimal Martian networks.",
rev="00",
pages=2,
- expires=datetime.datetime.now(),
+ expires=timezone.now(),
)
old_draft.set_state(State.objects.get(used=True, type="draft", slug="expired"))
old_alias = DocAlias.objects.create(name=old_draft.name)
@@ -287,7 +288,7 @@ def make_test_data():
# draft
draft = Document.objects.create(
name="draft-ietf-mars-test",
- time=datetime.datetime.now(),
+ time=timezone.now(),
type_id="draft",
title="Optimizing Martian Network Topologies",
stream_id="ietf",
@@ -298,7 +299,7 @@ def make_test_data():
intended_std_level_id="ps",
shepherd=email,
ad=ad,
- expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
+ expires=timezone.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
notify="aliens@example.mars",
note="",
)
@@ -458,7 +459,7 @@ def make_review_data(doc):
doc=doc,
team=team1,
type_id="early",
- deadline=datetime.datetime.now() + datetime.timedelta(days=20),
+ deadline=timezone.now() + datetime.timedelta(days=20),
state_id="accepted",
requested_by=reviewer,
reviewer=email,
diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py
index 129b2e842..ae6db8bd6 100644
--- a/ietf/utils/test_runner.py
+++ b/ietf/utils/test_runner.py
@@ -43,7 +43,6 @@ import json
import pytz
import importlib
import socket
-import datetime
import gzip
import unittest
import pathlib
@@ -76,6 +75,7 @@ from django.core.management import call_command
from django.urls import URLResolver # type: ignore
from django.template.backends.django import DjangoTemplates
from django.template.backends.django import Template # type: ignore[attr-defined]
+from django.utils import timezone
# from django.utils.safestring import mark_safe
import debug # pyflakes:ignore
@@ -570,7 +570,7 @@ class CoverageTest(unittest.TestCase):
checker.stop()
# Save to the .coverage file
checker.save()
- # Apply the configured and requested omit and include data
+ # Apply the configured and requested omit and include data
checker.config.from_args(ignore_errors=None, omit=settings.TEST_CODE_COVERAGE_EXCLUDE_FILES,
include=include, file=None)
for pattern in settings.TEST_CODE_COVERAGE_EXCLUDE_LINES:
@@ -739,7 +739,7 @@ class IetfTestRunner(DiscoverRunner):
print(" Datatracker %s test suite, %s:" % (ietf.__version__, time.strftime("%d %B %Y %H:%M:%S %Z")))
print(" Python %s." % sys.version.replace('\n', ' '))
print(" Django %s, settings '%s'" % (django.get_version(), settings.SETTINGS_MODULE))
-
+
settings.TEMPLATES[0]['BACKEND'] = 'ietf.utils.test_runner.ValidatingTemplates'
if self.check_coverage:
if self.coverage_file.endswith('.gz'):
@@ -749,19 +749,19 @@ class IetfTestRunner(DiscoverRunner):
with io.open(self.coverage_file, encoding='utf-8') as file:
self.coverage_master = json.load(file)
self.coverage_data = {
- "time": datetime.datetime.now(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
+ "time": timezone.now().astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"template": {
- "coverage": 0.0,
+ "coverage": 0.0,
"covered": {},
"format": 1, # default format, coverage data in 'covered' are just fractions
},
"url": {
- "coverage": 0.0,
+ "coverage": 0.0,
"covered": {},
"format": 4,
},
"code": {
- "coverage": 0.0,
+ "coverage": 0.0,
"covered": {},
"format": 1,
},
@@ -808,8 +808,8 @@ class IetfTestRunner(DiscoverRunner):
for offset in range(10):
try:
# remember the value so ietf.utils.mail.send_smtp() will use the same
- ietf.utils.mail.SMTP_ADDR['port'] = base + offset
- self.smtpd_driver = SMTPTestServerDriver((ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port']),None)
+ ietf.utils.mail.SMTP_ADDR['port'] = base + offset
+ self.smtpd_driver = SMTPTestServerDriver((ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port']),None)
self.smtpd_driver.start()
print((" Running an SMTP test server on %(ip4)s:%(port)s to catch outgoing email." % ietf.utils.mail.SMTP_ADDR))
break
diff --git a/ietf/utils/tests_meetecho.py b/ietf/utils/tests_meetecho.py
index d40e013f8..db3d36f40 100644
--- a/ietf/utils/tests_meetecho.py
+++ b/ietf/utils/tests_meetecho.py
@@ -1,15 +1,16 @@
# Copyright The IETF Trust 2021, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
+import pytz
import requests
import requests_mock
-from pytz import timezone, utc
from unittest.mock import patch
from urllib.parse import urljoin
from django.conf import settings
from django.test import override_settings
+from django.utils import timezone
from ietf.utils.tests import TestCase
from .meetecho import Conference, ConferenceManager, MeetechoAPI, MeetechoAPIError
@@ -92,7 +93,7 @@ class APITests(TestCase):
api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET)
api_response = api.schedule_meeting(
wg_token='my-token',
- start_time=utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
+ start_time=pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
duration=datetime.timedelta(minutes=130),
description='interim-2021-wgname-01',
extrainfo='message for staff',
@@ -120,11 +121,11 @@ class APITests(TestCase):
)
# same time in different time zones
for start_time in [
- utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
- timezone('america/halifax').localize(datetime.datetime(2021, 9, 14, 7, 0, 0)),
- timezone('europe/kiev').localize(datetime.datetime(2021, 9, 14, 13, 0, 0)),
- timezone('pacific/easter').localize(datetime.datetime(2021, 9, 14, 5, 0, 0)),
- timezone('africa/porto-novo').localize(datetime.datetime(2021, 9, 14, 11, 0, 0)),
+ pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
+ pytz.timezone('america/halifax').localize(datetime.datetime(2021, 9, 14, 7, 0, 0)),
+ pytz.timezone('europe/kiev').localize(datetime.datetime(2021, 9, 14, 13, 0, 0)),
+ pytz.timezone('pacific/easter').localize(datetime.datetime(2021, 9, 14, 5, 0, 0)),
+ pytz.timezone('africa/porto-novo').localize(datetime.datetime(2021, 9, 14, 11, 0, 0)),
]:
self.assertEqual(
api_response,
@@ -191,7 +192,7 @@ class APITests(TestCase):
'3d55bce0-535e-4ba8-bb8e-734911cf3c32': {
'room': {
'id': 18,
- 'start_time': utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2021, 9, 14, 10, 0, 0)),
'duration': datetime.timedelta(minutes=130),
'description': 'interim-2021-wgname-01',
},
@@ -201,7 +202,7 @@ class APITests(TestCase):
'e68e96d4-d38f-475b-9073-ecab46ca96a5': {
'room': {
'id': 23,
- 'start_time': utc.localize(datetime.datetime(2021, 9, 15, 14, 30, 0)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2021, 9, 15, 14, 30, 0)),
'duration': datetime.timedelta(minutes=30),
'description': 'interim-2021-wgname-02',
},
@@ -249,7 +250,7 @@ class APITests(TestCase):
def test_time_serialization(self):
"""Time de/serialization should be consistent"""
- time = datetime.datetime.now(utc).replace(microsecond=0) # cut off to 0 microseconds
+ time = timezone.now().astimezone(pytz.utc).replace(microsecond=0) # cut off to 0 microseconds
api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET)
self.assertEqual(api._deserialize_time(api._serialize_time(time)), time)
@@ -263,7 +264,7 @@ class ConferenceManagerTests(TestCase):
'session-1-uuid': {
'room': {
'id': 1,
- 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)),
'duration': datetime.timedelta(minutes=45),
'description': 'some-description',
},
@@ -273,7 +274,7 @@ class ConferenceManagerTests(TestCase):
'session-2-uuid': {
'room': {
'id': 2,
- 'start_time': utc.localize(datetime.datetime(2022,2,5,4,5,6)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2022,2,5,4,5,6)),
'duration': datetime.timedelta(minutes=90),
'description': 'another-description',
},
@@ -290,7 +291,7 @@ class ConferenceManagerTests(TestCase):
id=1,
public_id='session-1-uuid',
description='some-description',
- start_time=utc.localize(datetime.datetime(2022, 2, 4, 1, 2, 3)),
+ start_time=pytz.utc.localize(datetime.datetime(2022, 2, 4, 1, 2, 3)),
duration=datetime.timedelta(minutes=45),
url='https://example.com/some/url',
deletion_token='delete-me',
@@ -300,7 +301,7 @@ class ConferenceManagerTests(TestCase):
id=2,
public_id='session-2-uuid',
description='another-description',
- start_time=utc.localize(datetime.datetime(2022, 2, 5, 4, 5, 6)),
+ start_time=pytz.utc.localize(datetime.datetime(2022, 2, 5, 4, 5, 6)),
duration=datetime.timedelta(minutes=90),
url='https://example.com/another/url',
deletion_token='delete-me-too',
@@ -316,7 +317,7 @@ class ConferenceManagerTests(TestCase):
'session-1-uuid': {
'room': {
'id': 1,
- 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)),
'duration': datetime.timedelta(minutes=45),
'description': 'some-description',
},
@@ -335,7 +336,7 @@ class ConferenceManagerTests(TestCase):
id=1,
public_id='session-1-uuid',
description='some-description',
- start_time=utc.localize(datetime.datetime(2022,2,4,1,2,3)),
+ start_time=pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)),
duration=datetime.timedelta(minutes=45),
url='https://example.com/some/url',
deletion_token='delete-me',
@@ -351,7 +352,7 @@ class ConferenceManagerTests(TestCase):
'session-1-uuid': {
'room': {
'id': 1,
- 'start_time': utc.localize(datetime.datetime(2022,2,4,1,2,3)),
+ 'start_time': pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)),
'duration': datetime.timedelta(minutes=45),
'description': 'some-description',
},
@@ -369,7 +370,7 @@ class ConferenceManagerTests(TestCase):
id=1,
public_id='session-1-uuid',
description='some-description',
- start_time=utc.localize(datetime.datetime(2022,2,4,1,2,3)),
+ start_time=pytz.utc.localize(datetime.datetime(2022,2,4,1,2,3)),
duration=datetime.timedelta(minutes=45),
url='https://example.com/some/url',
deletion_token='delete-me',
From 42203d7a9c528bab7a5da7cccf7c4220431d4281 Mon Sep 17 00:00:00 2001
From: Jennifer Richards
Date: Fri, 26 Aug 2022 13:03:19 -0300
Subject: [PATCH 02/19] chore: migrate timestamps for use with USE_TZ=True
(#4370)
* chore: add migration to change timestamps to UTC
* chore: fill in Meeting.time_zone where it is blank
Nearly all interim meetings on or before 2016-07-01 have blank
time_zone values. This migration fills these in with PST8PDT.
* chore: disallow blank Meeting.time_zone value
* refactor: no need to handle blank time_zone case in TZ migration
* refactor: remove now-unnecessary checks that meeting has time_zone
* chore: renumber migrations
* chore: update timestamp conversion migration
The django-celery-beat package introduces tables with timestamp
columns. These columns are stored in CELERY_TIMEZONE. Because we run with
this set to UTC, the migration ignores these columns.
* chore: fix pytz-related change in migration
* chore: be explicit that Meeting.vtimezone can return None
* refactor: remove unnecessary save()
---
.../0057_fill_in_empty_meeting_time_zone.py | 47 ++++
.../0058_meeting_time_zone_not_blank.py | 18 ++
ietf/meeting/models.py | 49 ++--
ietf/meeting/tests_models.py | 18 ++
ietf/meeting/views.py | 5 +-
.../0002_convert_timestamps_to_utc.py | 256 ++++++++++++++++++
6 files changed, 361 insertions(+), 32 deletions(-)
create mode 100644 ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py
create mode 100644 ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py
create mode 100644 ietf/utils/migrations/0002_convert_timestamps_to_utc.py
diff --git a/ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py b/ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py
new file mode 100644
index 000000000..f009b08f3
--- /dev/null
+++ b/ietf/meeting/migrations/0057_fill_in_empty_meeting_time_zone.py
@@ -0,0 +1,47 @@
+# Generated by Django 2.2.28 on 2022-08-08 11:37
+
+import datetime
+
+from django.db import migrations
+
+
+# date of last meeting with an empty time_zone before this migration
+LAST_EMPTY_TZ = datetime.date(2022, 7, 1)
+
+
+def forward(apps, schema_editor):
+ Meeting = apps.get_model('meeting', 'Meeting')
+
+ # Check that we will be able to identify the migrated meetings later
+ old_meetings_in_pst8pdt = Meeting.objects.filter(type_id='interim', time_zone='PST8PDT', date__lte=LAST_EMPTY_TZ)
+ assert old_meetings_in_pst8pdt.count() == 0, 'not expecting interim meetings in PST8PDT time_zone'
+
+ meetings_with_empty_tz = Meeting.objects.filter(time_zone='')
+ # check our expected conditions
+ for mtg in meetings_with_empty_tz:
+ assert mtg.type_id == 'interim', 'was not expecting non-interim meetings to be affected'
+ assert mtg.date <= LAST_EMPTY_TZ, 'affected meeting outside expected date range'
+ mtg.time_zone = 'PST8PDT'
+
+ # commit the changes
+ Meeting.objects.bulk_update(meetings_with_empty_tz, ['time_zone'])
+
+
+def reverse(apps, schema_editor):
+ Meeting = apps.get_model('meeting', 'Meeting')
+ meetings_to_restore = Meeting.objects.filter(time_zone='PST8PDT', date__lte=LAST_EMPTY_TZ)
+ for mtg in meetings_to_restore:
+ mtg.time_zone = ''
+ # commit the changes
+ Meeting.objects.bulk_update(meetings_to_restore, ['time_zone'])
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('meeting', '0056_use_timezone_now_for_meeting_models'),
+ ]
+
+ operations = [
+ migrations.RunPython(forward, reverse),
+ ]
diff --git a/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py
new file mode 100644
index 000000000..0adec77a2
--- /dev/null
+++ b/ietf/meeting/migrations/0058_meeting_time_zone_not_blank.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2022-08-25 12:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('meeting', '0057_fill_in_empty_meeting_time_zone'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='meeting',
+ name='time_zone',
+ field=models.CharField(choices=[('', '---------'), ('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('UTC', 'UTC')], default='UTC', max_length=255),
+ ),
+ ]
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index d7294f70a..df5876e27 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -86,7 +86,7 @@ class Meeting(models.Model):
# We can't derive time-zone from country, as there are some that have
# more than one timezone, and the pytz module doesn't provide timezone
# lookup information for all relevant city/country combinations.
- time_zone = models.CharField(blank=True, max_length=255, choices=timezones)
+ time_zone = models.CharField(max_length=255, choices=timezones, default='UTC')
idsubmit_cutoff_day_offset_00 = models.IntegerField(blank=True,
default=settings.IDSUBMIT_DEFAULT_CUTOFF_DAY_OFFSET_00,
help_text = "The number of days before the meeting start date when the submission of -00 drafts will be closed.")
@@ -355,19 +355,18 @@ class Meeting(models.Model):
# timeslot = ts)
def vtimezone(self):
- if self.time_zone:
- try:
- tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
- if os.path.exists(tzfn):
- with io.open(tzfn) as tzf:
- icstext = tzf.read()
- vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip()
- if vtimezone:
- vtimezone += "\n"
- return vtimezone
- except IOError:
- pass
- return ''
+ try:
+ tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
+ if os.path.exists(tzfn):
+ with io.open(tzfn) as tzf:
+ icstext = tzf.read()
+ vtimezone = re.search("(?sm)(\nBEGIN:VTIMEZONE.*\nEND:VTIMEZONE\n)", icstext).group(1).strip()
+ if vtimezone:
+ vtimezone += "\n"
+ return vtimezone
+ except IOError:
+ pass
+ return None
def set_official_schedule(self, schedule):
if self.schedule != schedule:
@@ -606,24 +605,15 @@ class TimeSlot(models.Model):
def tz(self):
if not hasattr(self, '_cached_tz'):
- if self.meeting.time_zone:
- self._cached_tz = pytz.timezone(self.meeting.time_zone)
- else:
- self._cached_tz = None
+ self._cached_tz = pytz.timezone(self.meeting.time_zone)
return self._cached_tz
def tzname(self):
- if self.tz():
- return self.tz().tzname(self.time)
- else:
- return ""
+ return self.tz().tzname(self.time)
def utc_start_time(self):
- if self.tz():
- local_start_time = self.tz().localize(self.time)
- return local_start_time.astimezone(pytz.utc)
- else:
- return None
+ local_start_time = self.tz().localize(self.time)
+ return local_start_time.astimezone(pytz.utc)
def utc_end_time(self):
utc_start = self.utc_start_time()
@@ -631,10 +621,7 @@ class TimeSlot(models.Model):
return None if utc_start is None else utc_start + self.duration
def local_start_time(self):
- if self.tz():
- return self.tz().localize(self.time)
- else:
- return None
+ return self.tz().localize(self.time)
def local_end_time(self):
local_start = self.local_start_time()
diff --git a/ietf/meeting/tests_models.py b/ietf/meeting/tests_models.py
index dea31dc04..4a2f42c16 100644
--- a/ietf/meeting/tests_models.py
+++ b/ietf/meeting/tests_models.py
@@ -3,6 +3,8 @@
"""Tests of models in the Meeting application"""
import datetime
+from mock import patch
+
from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.stats.factories import MeetingRegistrationFactory
from ietf.utils.test_utils import TestCase
@@ -52,6 +54,22 @@ class MeetingTests(TestCase):
self.assertEqual(attendance.online, 0)
self.assertEqual(attendance.onsite, 5)
+ def test_vtimezone(self):
+ # normal time zone that should have a zoneinfo file
+ meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
+ vtz = meeting.vtimezone()
+ self.assertIsNotNone(vtz)
+ self.assertGreater(len(vtz), 0)
+ # time zone that does not have a zoneinfo file should return None
+ meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone')
+ vtz = meeting.vtimezone()
+ self.assertIsNone(vtz)
+ # ioerror trying to read zoneinfo should return None
+ meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
+ with patch('ietf.meeting.models.io.open', side_effect=IOError):
+ vtz = meeting.vtimezone()
+ self.assertIsNone(vtz)
+
class SessionTests(TestCase):
def test_chat_archive_url_with_jabber(self):
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index e4fc9dc17..c8eac6f9f 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -3662,9 +3662,12 @@ def upcoming_ical(request):
ietfs = [m for m in meetings if m.type_id == 'ietf']
preprocess_meeting_important_dates(ietfs)
+ meeting_vtz = {meeting.vtimezone() for meeting in meetings}
+ meeting_vtz.discard(None)
+
# icalendar response file should have '\r\n' line endings per RFC5545
response = render_to_string('meeting/upcoming.ics', {
- 'vtimezones': ''.join(sorted(list({meeting.vtimezone() for meeting in meetings if meeting.vtimezone()}))),
+ 'vtimezones': ''.join(sorted(meeting_vtz)),
'assignments': assignments,
'ietfs': ietfs,
}, request=request)
diff --git a/ietf/utils/migrations/0002_convert_timestamps_to_utc.py b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py
new file mode 100644
index 000000000..6cc022538
--- /dev/null
+++ b/ietf/utils/migrations/0002_convert_timestamps_to_utc.py
@@ -0,0 +1,256 @@
+# Generated by Django 2.2.28 on 2022-06-21 11:44
+
+from django.conf import settings
+from django.db import migrations, connection
+
+# to generate the expected list:
+#
+# from django.db import connection
+# from pprint import pp
+# cursor = connection.cursor()
+# cursor.execute("""
+# SELECT table_name, column_name
+# FROM information_schema.columns
+# WHERE table_schema='ietf_utf8'
+# AND column_type LIKE 'datetime%'
+# AND NOT table_name LIKE 'django_celery_beat_%'
+# ORDER BY table_name, column_name;
+# """)
+# pp(cursor.fetchall())
+#
+expected_datetime_columns = (
+ ('auth_user', 'date_joined'),
+ ('auth_user', 'last_login'),
+ ('community_documentchangedates', 'new_version_date'),
+ ('community_documentchangedates', 'normal_change_date'),
+ ('community_documentchangedates', 'significant_change_date'),
+ ('django_admin_log', 'action_time'),
+ ('django_migrations', 'applied'),
+ ('django_session', 'expire_date'),
+ ('doc_ballotpositiondocevent', 'comment_time'),
+ ('doc_ballotpositiondocevent', 'discuss_time'),
+ ('doc_deletedevent', 'time'),
+ ('doc_docevent', 'time'),
+ ('doc_dochistory', 'expires'),
+ ('doc_dochistory', 'time'),
+ ('doc_docreminder', 'due'),
+ ('doc_document', 'expires'),
+ ('doc_document', 'time'),
+ ('doc_documentactionholder', 'time_added'),
+ ('doc_initialreviewdocevent', 'expires'),
+ ('doc_irsgballotdocevent', 'duedate'),
+ ('doc_lastcalldocevent', 'expires'),
+ ('group_group', 'time'),
+ ('group_groupevent', 'time'),
+ ('group_grouphistory', 'time'),
+ ('group_groupmilestone', 'time'),
+ ('group_groupmilestonehistory', 'time'),
+ ('ipr_iprdisclosurebase', 'time'),
+ ('ipr_iprevent', 'response_due'),
+ ('ipr_iprevent', 'time'),
+ ('liaisons_liaisonstatementevent', 'time'),
+ ('mailinglists_subscribed', 'time'),
+ ('mailinglists_whitelisted', 'time'),
+ ('meeting_floorplan', 'modified'),
+ ('meeting_room', 'modified'),
+ ('meeting_schedtimesessassignment', 'modified'),
+ ('meeting_schedulingevent', 'time'),
+ ('meeting_session', 'modified'),
+ ('meeting_session', 'scheduled'),
+ ('meeting_slidesubmission', 'time'),
+ ('meeting_timeslot', 'modified'),
+ ('meeting_timeslot', 'time'),
+ ('message_message', 'sent'),
+ ('message_message', 'time'),
+ ('message_sendqueue', 'send_at'),
+ ('message_sendqueue', 'sent_at'),
+ ('message_sendqueue', 'time'),
+ ('nomcom_feedback', 'time'),
+ ('nomcom_feedbacklastseen', 'time'),
+ ('nomcom_nomination', 'time'),
+ ('nomcom_nomineeposition', 'time'),
+ ('nomcom_topicfeedbacklastseen', 'time'),
+ ('oidc_provider_code', 'expires_at'),
+ ('oidc_provider_token', 'expires_at'),
+ ('oidc_provider_userconsent', 'date_given'),
+ ('oidc_provider_userconsent', 'expires_at'),
+ ('person_email', 'time'),
+ ('person_historicalemail', 'history_date'),
+ ('person_historicalemail', 'time'),
+ ('person_historicalperson', 'history_date'),
+ ('person_historicalperson', 'time'),
+ ('person_person', 'time'),
+ ('person_personalapikey', 'created'),
+ ('person_personalapikey', 'latest'),
+ ('person_personevent', 'time'),
+ ('request_profiler_profilingrecord', 'end_ts'),
+ ('request_profiler_profilingrecord', 'start_ts'),
+ ('review_historicalreviewassignment', 'assigned_on'),
+ ('review_historicalreviewassignment', 'completed_on'),
+ ('review_historicalreviewassignment', 'history_date'),
+ ('review_historicalreviewersettings', 'history_date'),
+ ('review_historicalreviewrequest', 'history_date'),
+ ('review_historicalreviewrequest', 'time'),
+ ('review_historicalunavailableperiod', 'history_date'),
+ ('review_reviewassignment', 'assigned_on'),
+ ('review_reviewassignment', 'completed_on'),
+ ('review_reviewrequest', 'time'),
+ ('review_reviewwish', 'time'),
+ ('south_migrationhistory', 'applied'),
+ ('submit_preapproval', 'time'),
+ ('submit_submissioncheck', 'time'),
+ ('submit_submissionevent', 'time'),
+ ('tastypie_apikey', 'created'),
+ ('utils_dumpinfo', 'date'),
+ ('utils_versioninfo', 'time'),
+)
+
+
+def forward(apps, schema_editor):
+ # Check that we can safely ignore celery beat columns - it defaults to UTC if CELERY_TIMEZONE is not set.
+ celery_timezone = getattr(settings, 'CELERY_TIMEZONE', None)
+ assert celery_timezone in ('UTC', None), 'update migration, celery is not using UTC'
+ # If the CELERY_ENABLE_UTC flag is set, abort because someone is using a strange configuration.
+ assert not hasattr(settings, 'CELERY_ENABLE_UTC'), 'update migration, settings.CELERY_ENABLE_UTC was not expected'
+
+ with connection.cursor() as cursor:
+ # Check that we have timezones.
+ # If these assertions fail, the DB does not know all the necessary time zones.
+ # To load timezones,
+ # $ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
+ # (on a dev system, first connect to the db image with `docker compose exec db bash`)
+ cursor.execute("SELECT CONVERT_TZ('2022-06-22T17:43:00', 'PST8PDT', 'UTC');")
+ assert not any(None in row for row in cursor.fetchall()), 'database does not recognize PST8PDT'
+ cursor.execute(
+ "SELECT CONVERT_TZ('2022-06-22T17:43:00', time_zone, 'UTC') FROM meeting_meeting WHERE time_zone != '';"
+ )
+ assert not any(None in row for row in cursor.fetchall()), 'database does not recognize a meeting time zone'
+
+ # Check that we have all and only the expected datetime columns to work with.
+ # If this fails, figure out what changed and decide how to proceed safely.
+ cursor.execute("""
+ SELECT table_name, column_name
+ FROM information_schema.columns
+ WHERE table_schema='ietf_utf8'
+ AND column_type LIKE 'datetime%'
+ AND NOT table_name LIKE 'django_celery_beat_%'
+ ORDER BY table_name, column_name;
+ """)
+ assert cursor.fetchall() == expected_datetime_columns, 'unexpected or missing datetime columns in db'
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('utils', '0001_initial'),
+ ('meeting', '0058_meeting_time_zone_not_blank'),
+ ]
+
+ # To generate the queries:
+ #
+ # pst8pdt_columns = [e for e in expected_datetime_columns if e != ('meeting_timeslot', 'time')]
+ # queries = []
+ # for table, column in pst8pdt_columns:
+ # queries.append(f"UPDATE {table} SET {column} = CONVERT_TZ({column}, 'PST8PDT', 'UTC');")
+ #
+ # queries.append("""
+ # UPDATE meeting_timeslot
+ # JOIN meeting_meeting
+ # ON meeting_meeting.id = meeting_id
+ # SET time = CONVERT_TZ(time, time_zone, 'UTC');
+ # """)
+ #
+ # print("\n".join(queries))
+ #
+ operations = [
+ migrations.RunPython(forward),
+ migrations.RunSQL("""
+UPDATE auth_user SET date_joined = CONVERT_TZ(date_joined, 'PST8PDT', 'UTC');
+UPDATE auth_user SET last_login = CONVERT_TZ(last_login, 'PST8PDT', 'UTC');
+UPDATE community_documentchangedates SET new_version_date = CONVERT_TZ(new_version_date, 'PST8PDT', 'UTC');
+UPDATE community_documentchangedates SET normal_change_date = CONVERT_TZ(normal_change_date, 'PST8PDT', 'UTC');
+UPDATE community_documentchangedates SET significant_change_date = CONVERT_TZ(significant_change_date, 'PST8PDT', 'UTC');
+UPDATE django_admin_log SET action_time = CONVERT_TZ(action_time, 'PST8PDT', 'UTC');
+UPDATE django_migrations SET applied = CONVERT_TZ(applied, 'PST8PDT', 'UTC');
+UPDATE django_session SET expire_date = CONVERT_TZ(expire_date, 'PST8PDT', 'UTC');
+UPDATE doc_ballotpositiondocevent SET comment_time = CONVERT_TZ(comment_time, 'PST8PDT', 'UTC');
+UPDATE doc_ballotpositiondocevent SET discuss_time = CONVERT_TZ(discuss_time, 'PST8PDT', 'UTC');
+UPDATE doc_deletedevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE doc_docevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE doc_dochistory SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC');
+UPDATE doc_dochistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE doc_docreminder SET due = CONVERT_TZ(due, 'PST8PDT', 'UTC');
+UPDATE doc_document SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC');
+UPDATE doc_document SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE doc_documentactionholder SET time_added = CONVERT_TZ(time_added, 'PST8PDT', 'UTC');
+UPDATE doc_initialreviewdocevent SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC');
+UPDATE doc_irsgballotdocevent SET duedate = CONVERT_TZ(duedate, 'PST8PDT', 'UTC');
+UPDATE doc_lastcalldocevent SET expires = CONVERT_TZ(expires, 'PST8PDT', 'UTC');
+UPDATE group_group SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE group_groupevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE group_grouphistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE group_groupmilestone SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE group_groupmilestonehistory SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE ipr_iprdisclosurebase SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE ipr_iprevent SET response_due = CONVERT_TZ(response_due, 'PST8PDT', 'UTC');
+UPDATE ipr_iprevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE liaisons_liaisonstatementevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE mailinglists_subscribed SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE mailinglists_whitelisted SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE meeting_floorplan SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC');
+UPDATE meeting_room SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC');
+UPDATE meeting_schedtimesessassignment SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC');
+UPDATE meeting_schedulingevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE meeting_session SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC');
+UPDATE meeting_session SET scheduled = CONVERT_TZ(scheduled, 'PST8PDT', 'UTC');
+UPDATE meeting_slidesubmission SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE meeting_timeslot SET modified = CONVERT_TZ(modified, 'PST8PDT', 'UTC');
+UPDATE message_message SET sent = CONVERT_TZ(sent, 'PST8PDT', 'UTC');
+UPDATE message_message SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE message_sendqueue SET send_at = CONVERT_TZ(send_at, 'PST8PDT', 'UTC');
+UPDATE message_sendqueue SET sent_at = CONVERT_TZ(sent_at, 'PST8PDT', 'UTC');
+UPDATE message_sendqueue SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE nomcom_feedback SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE nomcom_feedbacklastseen SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE nomcom_nomination SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE nomcom_nomineeposition SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE nomcom_topicfeedbacklastseen SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE oidc_provider_code SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC');
+UPDATE oidc_provider_token SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC');
+UPDATE oidc_provider_userconsent SET date_given = CONVERT_TZ(date_given, 'PST8PDT', 'UTC');
+UPDATE oidc_provider_userconsent SET expires_at = CONVERT_TZ(expires_at, 'PST8PDT', 'UTC');
+UPDATE person_email SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE person_historicalemail SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE person_historicalemail SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE person_historicalperson SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE person_historicalperson SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE person_person SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE person_personalapikey SET created = CONVERT_TZ(created, 'PST8PDT', 'UTC');
+UPDATE person_personalapikey SET latest = CONVERT_TZ(latest, 'PST8PDT', 'UTC');
+UPDATE person_personevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE request_profiler_profilingrecord SET end_ts = CONVERT_TZ(end_ts, 'PST8PDT', 'UTC');
+UPDATE request_profiler_profilingrecord SET start_ts = CONVERT_TZ(start_ts, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewassignment SET assigned_on = CONVERT_TZ(assigned_on, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewassignment SET completed_on = CONVERT_TZ(completed_on, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewassignment SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewersettings SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewrequest SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE review_historicalreviewrequest SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE review_historicalunavailableperiod SET history_date = CONVERT_TZ(history_date, 'PST8PDT', 'UTC');
+UPDATE review_reviewassignment SET assigned_on = CONVERT_TZ(assigned_on, 'PST8PDT', 'UTC');
+UPDATE review_reviewassignment SET completed_on = CONVERT_TZ(completed_on, 'PST8PDT', 'UTC');
+UPDATE review_reviewrequest SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE review_reviewwish SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE south_migrationhistory SET applied = CONVERT_TZ(applied, 'PST8PDT', 'UTC');
+UPDATE submit_preapproval SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE submit_submissioncheck SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE submit_submissionevent SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+UPDATE tastypie_apikey SET created = CONVERT_TZ(created, 'PST8PDT', 'UTC');
+UPDATE utils_dumpinfo SET date = CONVERT_TZ(date, 'PST8PDT', 'UTC');
+UPDATE utils_versioninfo SET time = CONVERT_TZ(time, 'PST8PDT', 'UTC');
+
+UPDATE meeting_timeslot
+ JOIN meeting_meeting
+ ON meeting_meeting.id = meeting_id
+ SET time = CONVERT_TZ(time, time_zone, 'UTC');
+"""),
+ ]
From 8b52d27b02cf4e7e34efbaa5e0d640b27cc40f68 Mon Sep 17 00:00:00 2001
From: Jennifer Richards
Date: Fri, 26 Aug 2022 16:53:19 -0300
Subject: [PATCH 03/19] refactor: refactor timestamp handling so tests in
meeting app pass (#4371)
* refactor: replace datetime.now with timezone.now
* refactor: migrate model fields to use timezone.now as default
* refactor: replace datetime.today with timezone.now
datetime.datetime.today() is equivalent to datetime.datetime.now(); both
return a naive datetime with the current local time.
* refactor: rephrase datetime.now(tz) as timezone.now().astimezone(tz)
This is effectively the same, but is less likely to encourage accidental
use of naive datetimes.
* refactor: revert datetime.today() change to old migrations
* refactor: change a missed datetime.now to timezone.now
* chore: renumber timezone_now migration
* chore: add migration to change timestamps to UTC
* refactor: move tz instantiation/caching from TimeSlot to Meeting
* fix: assume utc if meeting.time_zone is blank
* chore: make datetime.combine() calls tz aware in the meeting app
* ci: correctly use meeting.tz in TimeSlotFactory
* chore: compute TimeSlot utc / local times assuming tz-aware times
* chore: use tzaware math for agenda editor timeslot layout
* chore: fill in Meeting.time_zone where it is blank
Nearly all interim meetings on or before 2016-07-01 have blank
time_zone values. This migration fills these in with PST8PDT.
* chore: disallow blank Meeting.time_zone value
* refactor: no need to handle blank time_zone case in TZ migration
* refactor: remove now-unnecessary checks that meeting has time_zone
* chore: fix timezone handling in agenda.ics and Meeting.updated()
* chore: fix tz handling in interim_request_details, exercise in tests
* chore: fix timezone handling for test_interim_send_announcement
* chore: fix timezone handling in agenda_json()
* chore: fix timezone handling in old agenda
* chore: fix timezone handling for EditTimeslotsTests
* refactor: refactor a few fixes for more consistent timezone handling
* chore: add timezone info to timestamps in fixtures
* chore: remove naive datetime warnings found in meetings.tests_views
* chore: fix a few more test failures in meetings.tests_views
All tests in meetings.tests_views now passing
* chore: remove unused import
* chore: fix timezone handling in test_schedule_generator.py
* chore: fix timezone handling affecting meeting.tests_js
* chore: fix timeslot test bug when local date != UTC date
* test: fix a few failing tests, all meetings tests now pass
(for me, anyway)
* chore: renumber migrations
* chore: update timestamp conversion migration
The django-celery-beat package introduces tables with timestamp
columns. These columns are stored in CELERY_TIMEZONE. Because we run with
this set to UTC, the migration ignores these columns.
* chore: fix pytz-related change in migration
* chore: remove duplicate migrations
* chore: remove CELERY_BEAT_TZ_AWARE setting now that USE_TZ is True
* test: avoid failure in test with bogus timezone
---
ietf/group/models.py | 11 +-
ietf/meeting/factories.py | 4 +-
.../fixtures/proceedings_templates.json | 2 +-
ietf/meeting/helpers.py | 21 +-
.../commands/create_dummy_meeting.py | 24 +-
ietf/meeting/models.py | 43 ++--
ietf/meeting/test_data.py | 43 ++--
ietf/meeting/tests_js.py | 48 ++--
ietf/meeting/tests_models.py | 6 +-
ietf/meeting/tests_schedule_generator.py | 14 +-
ietf/meeting/tests_views.py | 200 ++++++++++------
ietf/meeting/utils.py | 20 +-
ietf/meeting/views.py | 226 ++++++++++--------
ietf/name/fixtures/names.json | 8 +-
ietf/secr/proceedings/proc_utils.py | 20 +-
ietf/settings.py | 4 +-
ietf/templates/meeting/agenda.html | 19 +-
ietf/templates/meeting/agenda.ics | 8 +-
.../meeting/interim_announcement.txt | 7 +-
.../meeting/interim_request_details.html | 8 +-
.../templates/meeting/timeslot_start_end.html | 6 +-
ietf/templates/meeting/upcoming.html | 12 +-
ietf/utils/timezone.py | 34 ++-
23 files changed, 480 insertions(+), 308 deletions(-)
diff --git a/ietf/group/models.py b/ietf/group/models.py
index fd665e6b3..8183f35a0 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
-import datetime
import email.utils
import jsonfield
import os
@@ -181,11 +180,15 @@ class Group(GroupInfo):
return self.role_set.none()
def status_for_meeting(self,meeting):
- end_date = meeting.end_date()+datetime.timedelta(days=1)
previous_meeting = meeting.previous_meeting()
- status_events = self.groupevent_set.filter(type='status_update',time__lte=end_date).order_by('-time')
+ status_events = self.groupevent_set.filter(
+ type='status_update',
+ time__lt=meeting.end_datetime(),
+ ).order_by('-time')
if previous_meeting:
- status_events = status_events.filter(time__gte=previous_meeting.end_date()+datetime.timedelta(days=1))
+ status_events = status_events.filter(
+ time__gte=previous_meeting.end_datetime()
+ )
return status_events.first()
def get_description(self):
diff --git a/ietf/meeting/factories.py b/ietf/meeting/factories.py
index e59b559f4..cf3c87e7c 100644
--- a/ietf/meeting/factories.py
+++ b/ietf/meeting/factories.py
@@ -187,7 +187,9 @@ class TimeSlotFactory(factory.django.DjangoModelFactory):
@factory.lazy_attribute
def time(self):
- return datetime.datetime.combine(self.meeting.date,datetime.time(11,0))
+ return self.meeting.tz().localize(
+ datetime.datetime.combine(self.meeting.date, datetime.time(11, 0))
+ )
@factory.lazy_attribute
def duration(self):
diff --git a/ietf/meeting/fixtures/proceedings_templates.json b/ietf/meeting/fixtures/proceedings_templates.json
index 1594debff..97d38f566 100644
--- a/ietf/meeting/fixtures/proceedings_templates.json
+++ b/ietf/meeting/fixtures/proceedings_templates.json
@@ -32,7 +32,7 @@
"comments": "",
"list_subscribe": "",
"state": "active",
- "time": "2012-02-26T00:21:36",
+ "time": "2012-02-26T00:21:36Z",
"unused_tags": [],
"list_archive": "",
"type": "ietf",
diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py
index 5f463b269..99be2dd89 100644
--- a/ietf/meeting/helpers.py
+++ b/ietf/meeting/helpers.py
@@ -118,7 +118,10 @@ def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefe
# assignments = list(assignments_queryset) # make sure we're set in stone
assignments = assignments_queryset
- meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
+ # meeting_time is meeting-local midnight at the start of the meeting date
+ meeting_time = meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time())
+ )
# replace groups with historic counterparts
groups = [ ]
@@ -1149,11 +1152,15 @@ def sessions_post_cancel(request, sessions):
def update_interim_session_assignment(form):
- """Helper function to create / update timeslot assigned to interim session"""
- time = datetime.datetime.combine(
- form.cleaned_data['date'],
- form.cleaned_data['time'])
+ """Helper function to create / update timeslot assigned to interim session
+
+ form is an InterimSessionModelForm
+ """
session = form.instance
+ meeting = session.meeting
+ time = meeting.tz().localize(
+ datetime.datetime.combine(form.cleaned_data['date'], form.cleaned_data['time'])
+ )
if session.official_timeslotassignment():
slot = session.official_timeslotassignment().timeslot
slot.time = time
@@ -1161,14 +1168,14 @@ def update_interim_session_assignment(form):
slot.save()
else:
slot = TimeSlot.objects.create(
- meeting=session.meeting,
+ meeting=meeting,
type_id='regular',
duration=session.requested_duration,
time=time)
SchedTimeSessAssignment.objects.create(
timeslot=slot,
session=session,
- schedule=session.meeting.schedule)
+ schedule=meeting.schedule)
def populate_important_dates(meeting):
assert ImportantDate.objects.filter(meeting=meeting).exists() is False
diff --git a/ietf/meeting/management/commands/create_dummy_meeting.py b/ietf/meeting/management/commands/create_dummy_meeting.py
index 79b1e5db6..2a4b1dad7 100644
--- a/ietf/meeting/management/commands/create_dummy_meeting.py
+++ b/ietf/meeting/management/commands/create_dummy_meeting.py
@@ -48,7 +48,7 @@ import socket
import datetime
import pytz
-from django.core.management.base import BaseCommand
+from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import Q
@@ -75,10 +75,12 @@ class Command(BaseCommand):
def _meeting_datetime(self, day, *time_args):
"""Generate a datetime on a meeting day"""
- return datetime.datetime.combine(
- self.start_date,
- datetime.time(*time_args)
- ) + datetime.timedelta(days=day)
+ return self.meeting_tz.localize(
+ datetime.datetime.combine(
+ self.start_date,
+ datetime.time(*time_args)
+ ) + datetime.timedelta(days=day)
+ )
def handle(self, *args, **options):
if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]:
@@ -87,10 +89,7 @@ class Command(BaseCommand):
opt_delete = options.get('delete', False)
opt_use_old_conflicts = options.get('old_conflicts', False)
self.start_date = options['start_date']
- meeting_tz = options['tz']
- if not opt_delete and (meeting_tz not in pytz.common_timezones):
- self.stderr.write("Warning: {} is not a recognized time zone.".format(meeting_tz))
-
+ meeting_tzname = options['tz']
if opt_delete:
if Meeting.objects.filter(number='999').exists():
Meeting.objects.filter(number='999').delete()
@@ -98,6 +97,11 @@ class Command(BaseCommand):
else:
self.stderr.write("Dummy meeting IETF 999 does not exist; nothing to do.\n")
else:
+ try:
+ self.meeting_tz = pytz.timezone(meeting_tzname)
+ except pytz.UnknownTimeZoneError:
+ raise CommandError("{} is not a recognized time zone.".format(meeting_tzname))
+
if Meeting.objects.filter(number='999').exists():
self.stderr.write("Dummy meeting IETF 999 already exists; nothing to do.\n")
else:
@@ -111,7 +115,7 @@ class Command(BaseCommand):
type_id='IETF',
date=self._meeting_datetime(0).date(),
days=7,
- time_zone=meeting_tz,
+ time_zone=meeting_tzname,
)
# Set enabled constraints
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index df5876e27..d3fd191fb 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -138,6 +138,11 @@ class Meeting(models.Model):
def end_date(self):
return self.get_meeting_date(self.days-1)
+ def end_datetime(self):
+ """Datetime of the first instant _after_ the meeting's last day"""
+ return self.tz().localize(
+ datetime.datetime.combine(self.get_meeting_date(self.days), datetime.time())
+ )
def get_00_cutoff(self):
start_date = datetime.datetime(year=self.date.year, month=self.date.month, day=self.date.day, tzinfo=pytz.utc)
importantdate = self.importantdate_set.filter(name_id='idcutoff').first()
@@ -322,7 +327,7 @@ class Meeting(models.Model):
for ts in self.timeslot_set.all():
if ts.location_id is None:
continue
- ymd = ts.time.date()
+ ymd = ts.local_start_time().date()
if ymd not in time_slices:
time_slices[ymd] = []
slots[ymd] = []
@@ -330,15 +335,15 @@ class Meeting(models.Model):
if ymd in time_slices:
# only keep unique entries
- if [ts.time, ts.time + ts.duration, ts.duration.seconds] not in time_slices[ymd]:
- time_slices[ymd].append([ts.time, ts.time + ts.duration, ts.duration.seconds])
+ if [ts.local_start_time(), ts.local_end_time(), ts.duration.seconds] not in time_slices[ymd]:
+ time_slices[ymd].append([ts.local_start_time(), ts.local_end_time(), ts.duration.seconds])
slots[ymd].append(ts)
days.sort()
for ymd in time_slices:
# Make sure these sort the same way
time_slices[ymd].sort()
- slots[ymd].sort(key=lambda x: (x.time, x.duration))
+ slots[ymd].sort(key=lambda x: (x.local_start_time(), x.duration))
return days,time_slices,slots
# this functions makes a list of timeslices and rooms, and
@@ -354,6 +359,11 @@ class Meeting(models.Model):
# SchedTimeSessAssignment.objects.create(schedule = sched,
# timeslot = ts)
+ def tz(self):
+ if not hasattr(self, '_cached_tz'):
+ self._cached_tz = pytz.timezone(self.time_zone)
+ return self._cached_tz
+
def vtimezone(self):
try:
tzfn = os.path.join(settings.TZDATA_ICS_PATH, self.time_zone + ".ics")
@@ -374,16 +384,14 @@ class Meeting(models.Model):
self.save()
def updated(self):
- min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
+ # should be Meeting.modified, but we don't have that
+ min_time = pytz.utc.localize(datetime.datetime(1970, 1, 1, 0, 0, 0))
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
assignments_updated = min_time
if self.schedule:
assignments_updated = SchedTimeSessAssignment.objects.filter(schedule__in=[self.schedule, self.schedule.base if self.schedule else None]).aggregate(Max('modified'))["modified__max"] or min_time
- ts = max(timeslots_updated, sessions_updated, assignments_updated)
- tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
- ts = tz.localize(ts)
- return ts
+ return max(timeslots_updated, sessions_updated, assignments_updated)
@memoize
def previous_meeting(self):
@@ -604,29 +612,22 @@ class TimeSlot(models.Model):
return self._cached_html_location
def tz(self):
- if not hasattr(self, '_cached_tz'):
- self._cached_tz = pytz.timezone(self.meeting.time_zone)
- return self._cached_tz
+ return self.meeting.tz()
def tzname(self):
return self.tz().tzname(self.time)
def utc_start_time(self):
- local_start_time = self.tz().localize(self.time)
- return local_start_time.astimezone(pytz.utc)
+ return self.time.astimezone(pytz.utc) # USE_TZ is True, so time is aware
def utc_end_time(self):
- utc_start = self.utc_start_time()
- # Add duration after converting start time, otherwise errors creep in around DST change
- return None if utc_start is None else utc_start + self.duration
+ return self.time.astimezone(pytz.utc) + self.duration # USE_TZ is True, so time is aware
def local_start_time(self):
- return self.tz().localize(self.time)
+ return self.time.astimezone(self.tz())
def local_end_time(self):
- local_start = self.local_start_time()
- # Add duration after converting start time, otherwise errors creep in around DST change
- return None if local_start is None else local_start + self.duration
+ return (self.time.astimezone(pytz.utc) + self.duration).astimezone(self.tz())
@property
def js_identifier(self):
diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py
index e5fdd71c5..9fd49f1c8 100644
--- a/ietf/meeting/test_data.py
+++ b/ietf/meeting/test_data.py
@@ -21,10 +21,12 @@ from ietf.person.factories import PersonFactory
from ietf.person.models import Person
from ietf.utils.test_data import make_test_data
-def make_interim_meeting(group,date,status='sched'):
+def make_interim_meeting(group,date,status='sched',tz='UTC'):
system_person = Person.objects.get(name="(System)")
- time = datetime.datetime.combine(date, datetime.time(9))
- meeting = create_interim_meeting(group=group,date=date)
+ meeting = create_interim_meeting(group=group,date=date,timezone=tz)
+ time = meeting.tz().localize(
+ datetime.datetime.combine(date, datetime.time(9))
+ )
session = SessionFactory(meeting=meeting, group=group,
attendees=10,
requested_duration=datetime.timedelta(minutes=20),
@@ -102,24 +104,37 @@ def make_meeting_test_data(meeting=None, create_interims=False):
# slots
session_date = meeting.date + datetime.timedelta(days=1)
+ tz = meeting.tz()
slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
- time=datetime.datetime.combine(session_date, datetime.time(9, 30)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(9, 30))
+ ))
slot2 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
- time=datetime.datetime.combine(session_date, datetime.time(10, 50)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(10, 50))
+ ))
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", location=breakfast_room,
duration=datetime.timedelta(minutes=90),
- time=datetime.datetime.combine(session_date, datetime.time(7,0)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(7,0))
+ ))
reg_slot = TimeSlot.objects.create(meeting=meeting, type_id="reg", location=reg_room,
duration=datetime.timedelta(minutes=480),
- time=datetime.datetime.combine(session_date, datetime.time(9,0)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(9,0))
+ ))
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
duration=datetime.timedelta(minutes=90),
- time=datetime.datetime.combine(session_date, datetime.time(7,0)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(7,0))
+ ))
plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
duration=datetime.timedelta(minutes=60),
- time=datetime.datetime.combine(session_date, datetime.time(11,0)))
+ time=tz.localize(
+ datetime.datetime.combine(session_date, datetime.time(11,0))
+ ))
# mars WG
mars = Group.objects.get(acronym='mars')
mars_session = SessionFactory(meeting=meeting, group=mars,
@@ -213,7 +228,7 @@ def make_meeting_test_data(meeting=None, create_interims=False):
return meeting
-def make_interim_test_data():
+def make_interim_test_data(meeting_tz='UTC'):
date = datetime.date.today() + datetime.timedelta(days=365)
date2 = datetime.date.today() + datetime.timedelta(days=1000)
PersonFactory(user__username='plain')
@@ -225,10 +240,10 @@ def make_interim_test_data():
RoleFactory(group=mars,person__user__username='marschairman',name_id='chair')
RoleFactory(group=ames,person__user__username='ameschairman',name_id='chair')
- make_interim_meeting(group=mars,date=date,status='sched')
- make_interim_meeting(group=mars,date=date2,status='apprw')
- make_interim_meeting(group=ames,date=date,status='canceled')
- make_interim_meeting(group=ames,date=date2,status='apprw')
+ make_interim_meeting(group=mars,date=date,status='sched',tz=meeting_tz)
+ make_interim_meeting(group=mars,date=date2,status='apprw',tz=meeting_tz)
+ make_interim_meeting(group=ames,date=date,status='canceled',tz=meeting_tz)
+ make_interim_meeting(group=ames,date=date2,status='apprw',tz=meeting_tz)
return
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
index f62e41388..6fdc31c50 100644
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -36,6 +36,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.utils.test_utils import assert_ical_response_is_valid
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
presence_of_element_child_by_css_selector )
+from ietf.utils.timezone import datetime_today
if selenium_enabled():
from selenium.webdriver.common.action_chains import ActionChains
@@ -1434,13 +1435,16 @@ class AgendaTests(IetfSeleniumTestCase):
# for others (break, reg, other):
# row--------
meeting_number = components[1]
- start_time = datetime.datetime(
- year=int(components[2]),
- month=int(components[3]),
- day=int(components[4]),
- hour=int(components[6][0:2]),
- minute=int(components[6][2:4]),
+ start_time = pytz.utc.localize(
+ datetime.datetime(
+ year=int(components[2]),
+ month=int(components[3]),
+ day=int(components[4]),
+ hour=int(components[6][0:2]),
+ minute=int(components[6][2:4]),
+ )
)
+
# If labeled as plenary, it's plenary...
if components[7] == '1plenary':
session_type = 'plenary'
@@ -1904,10 +1908,9 @@ class WeekviewTests(IetfSeleniumTestCase):
# Session during a single day in meeting local time but multi-day UTC
# Compute a time that overlaps midnight, UTC, but won't when shifted to a local time zone
- start_time_utc = pytz.timezone('UTC').localize(
+ start_time_utc = pytz.utc.localize(
datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
)
- start_time_local = start_time_utc.astimezone(pytz.timezone(self.meeting.time_zone))
daytime_session = SessionFactory(
meeting=self.meeting,
@@ -1916,7 +1919,7 @@ class WeekviewTests(IetfSeleniumTestCase):
)
daytime_timeslot = TimeSlotFactory(
meeting=self.meeting,
- time=start_time_local.replace(tzinfo=None), # drop timezone for Django
+ time=start_time_utc,
duration=duration,
)
daytime_session.timeslotassignments.create(timeslot=daytime_timeslot, schedule=self.meeting.schedule)
@@ -1929,11 +1932,12 @@ class WeekviewTests(IetfSeleniumTestCase):
)
overnight_timeslot = TimeSlotFactory(
meeting=self.meeting,
- time=datetime.datetime.combine(self.meeting.date, datetime.time(23,0)),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(self.meeting.date, datetime.time(23,0))
+ ),
duration=duration,
)
overnight_session.timeslotassignments.create(timeslot=overnight_timeslot, schedule=self.meeting.schedule)
-
# Check assumptions about events overlapping midnight
self.assertEqual(daytime_timeslot.local_start_time().day,
daytime_timeslot.local_end_time().day,
@@ -2191,7 +2195,7 @@ class InterimTests(IetfSeleniumTestCase):
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
schedule__in=expected_schedules,
session__in=expected_interim_sessions,
- timeslot__time__gte=datetime.date.today(),
+ timeslot__time__gte=datetime_today(),
))
# The UID formats should match those in the upcoming.ics template
expected_uids = [
@@ -2688,13 +2692,17 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
delete: [TimeSlot] = TimeSlotFactory.create_batch(
2,
meeting=self.meeting,
- time=datetime.datetime.combine(delete_day, delete_time),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(delete_day, delete_time)
+ ),
duration=duration)
keep: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
- time=datetime.datetime.combine(day, time),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(day, time)
+ ),
duration=duration
)
for (day, time) in (
@@ -2711,7 +2719,9 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
'[data-col-id="{}T{}-{}"]'.format(
delete_day.isoformat(),
delete_time.strftime('%H:%M'),
- (datetime.datetime.combine(delete_day, delete_time) + duration).strftime(
+ self.meeting.tz().localize(
+ datetime.datetime.combine(delete_day, delete_time) + duration
+ ).strftime(
'%H:%M'
))
)
@@ -2733,14 +2743,18 @@ class EditTimeslotsTests(IetfSeleniumTestCase):
delete: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
- time=datetime.datetime.combine(delete_day, time),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(delete_day, time)
+ ),
) for time in times
]
keep: [TimeSlot] = [
TimeSlotFactory(
meeting=self.meeting,
- time=datetime.datetime.combine(day, time),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(day, time)
+ ),
) for day in other_days for time in times
]
diff --git a/ietf/meeting/tests_models.py b/ietf/meeting/tests_models.py
index 4a2f42c16..8dec586ec 100644
--- a/ietf/meeting/tests_models.py
+++ b/ietf/meeting/tests_models.py
@@ -56,16 +56,16 @@ class MeetingTests(TestCase):
def test_vtimezone(self):
# normal time zone that should have a zoneinfo file
- meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
+ meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
vtz = meeting.vtimezone()
self.assertIsNotNone(vtz)
self.assertGreater(len(vtz), 0)
# time zone that does not have a zoneinfo file should return None
- meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone')
+ meeting = MeetingFactory(type_id='ietf', time_zone='Fake/Time_Zone', populate_schedule=False)
vtz = meeting.vtimezone()
self.assertIsNone(vtz)
# ioerror trying to read zoneinfo should return None
- meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles')
+ meeting = MeetingFactory(type_id='ietf', time_zone='America/Los_Angeles', populate_schedule=False)
with patch('ietf.meeting.models.io.open', side_effect=IOError):
vtz = meeting.vtimezone()
self.assertIsNone(vtz)
diff --git a/ietf/meeting/tests_schedule_generator.py b/ietf/meeting/tests_schedule_generator.py
index d414805d3..a88280208 100644
--- a/ietf/meeting/tests_schedule_generator.py
+++ b/ietf/meeting/tests_schedule_generator.py
@@ -1,6 +1,7 @@
# Copyright The IETF Trust 2020, All Rights Reserved
import calendar
import datetime
+import pytz
from io import StringIO
from django.core.management.base import CommandError
@@ -36,9 +37,11 @@ class ScheduleGeneratorTest(TestCase):
t = TimeSlotFactory(
meeting=self.meeting,
location=room,
- time=datetime.datetime.combine(
- self.meeting.date + datetime.timedelta(days=day),
- datetime.time(hour, 0),
+ time=self.meeting.tz().localize(
+ datetime.datetime.combine(
+ self.meeting.date + datetime.timedelta(days=day),
+ datetime.time(hour, 0),
+ )
),
duration=datetime.timedelta(minutes=60),
)
@@ -306,8 +309,11 @@ class ScheduleGeneratorTest(TestCase):
add_to_schedule=False
)
# use a timeslot not on Sunday
+ meeting_date = pytz.utc.localize(
+ datetime.datetime.combine(self.meeting.get_meeting_date(1), datetime.time())
+ )
ts = self.meeting.timeslot_set.filter(
- time__gt=self.meeting.date + datetime.timedelta(days=1),
+ time__gt=meeting_date,
location__capacity__lt=base_reg_session.attendees,
).order_by(
'time'
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index 2b6b20d1c..4d53ab856 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -52,6 +52,7 @@ from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
from ietf.utils.text import xslugify
+from ietf.utils.timezone import date_today, time_now
from ietf.person.factories import PersonFactory
from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory
@@ -155,7 +156,7 @@ class MeetingTests(BaseMeetingTestCase):
#
self.write_materials_files(meeting, session)
#
- future_year = datetime.date.today().year+1
+ future_year = date_today().year+1
future_num = (future_year-1984)*3 # valid for the mid-year meeting
future_meeting = Meeting.objects.create(date=datetime.date(future_year, 7, 22), number=future_num, type_id='ietf',
city="Panama City", country="PA", time_zone='America/Panama')
@@ -228,7 +229,10 @@ class MeetingTests(BaseMeetingTestCase):
'Time zone indicator should be in nav sidebar')
# plain
- time_interval = r"%s-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0"))
+ time_interval = r"{}-{}".format(
+ slot.time.astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
+ slot.end_time().astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"),
+ )
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
self.assertEqual(r.status_code, 200)
@@ -430,7 +434,9 @@ class MeetingTests(BaseMeetingTestCase):
session_date = meeting.date + datetime.timedelta(days=1)
slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=60),
- time=datetime.datetime.combine(session_date, datetime.time(13, 30)))
+ time=meeting.tz().localize(
+ datetime.datetime.combine(session_date, datetime.time(13, 30))
+ ))
SchedTimeSessAssignment.objects.create(timeslot=slot3, session=venus_session, schedule=meeting.schedule)
url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number))
r = self.client.get(url)
@@ -801,7 +807,12 @@ class MeetingTests(BaseMeetingTestCase):
a1 = s1.official_timeslotassignment()
t1 = a1.timeslot
# Create an extra session
- t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
+ t2 = TimeSlotFactory.create(
+ meeting=meeting,
+ time=meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(11, 30))
+ )
+ )
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
#
@@ -811,16 +822,16 @@ class MeetingTests(BaseMeetingTestCase):
r,
expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=2)
- self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
- self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
+ self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
+ self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
#
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
r = self.client.get(url)
assert_ical_response_is_valid(self, r,
expected_event_summaries=['mars - Martian Special Interest Group'],
expected_event_count=1)
- self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
- self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
+ self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
+ self.assertNotContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
def test_meeting_agenda_has_static_ical_links(self):
"""Links to the agenda_ical view must appear on the agenda page
@@ -960,7 +971,7 @@ class MeetingTests(BaseMeetingTestCase):
url = urlreverse('ietf.meeting.views.current_materials')
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
- MeetingFactory(type_id='ietf', date=datetime.date.today())
+ MeetingFactory(type_id='ietf', date=date_today())
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
@@ -1105,7 +1116,9 @@ class EditMeetingScheduleTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=room,
- time=datetime.datetime.combine(meeting.date, time),
+ time=meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, time)
+ ),
duration=datetime.timedelta(minutes=duration),
)
@@ -1189,7 +1202,11 @@ class EditMeetingScheduleTests(TestCase):
]
# Set up different sets of timeslots
- t0 = datetime.datetime.combine(meeting.date, datetime.time(11, 0))
+ # Work with t0 in UTC for arithmetic. This does not change the results but is cleaner if someone looks
+ # at intermediate results which may be misleading until passed through tz.normalize().
+ t0 = meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(11, 0))
+ ).astimezone(pytz.utc)
dur = datetime.timedelta(hours=2)
for room in room_groups[0]:
TimeSlotFactory(meeting=meeting, location=room, duration=dur, time=t0)
@@ -1284,7 +1301,7 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past
- right_now = self._right_now_in(meeting.time_zone)
+ right_now = self._right_now_in(meeting.tz())
for room in room_groups[0]:
ts = room.timeslot_set.last()
ts.time = right_now - datetime.timedelta(minutes=5)
@@ -1466,12 +1483,18 @@ class EditMeetingScheduleTests(TestCase):
self.client.login(username=username, password=username + '+password')
# Swap group 0's first and last sessions, first in the past
- right_now = self._right_now_in(meeting.time_zone)
- yesterday = (right_now - datetime.timedelta(days=1)).date()
- day_before = (right_now - datetime.timedelta(days=2)).date()
+ right_now = self._right_now_in(meeting.tz())
+ yesterday = right_now.date() - datetime.timedelta(days=1)
+ day_before = right_now.date() - datetime.timedelta(days=2)
for room in room_groups[0]:
ts = room.timeslot_set.last()
- ts.time = datetime.datetime.combine(yesterday, ts.time.time())
+ # Calculation keeps local clock time, shifted to a different day.
+ ts.time = meeting.tz().localize(
+ datetime.datetime.combine(
+ yesterday,
+ ts.time.astimezone(meeting.tz()).time()
+ ),
+ )
ts.save()
# timeslot_set is ordered by -time, so check that we know which is past/future
self.assertTrue(room_groups[0][0].timeslot_set.last().time < right_now)
@@ -1505,7 +1528,12 @@ class EditMeetingScheduleTests(TestCase):
# now with both in the past
for room in room_groups[0]:
ts = room.timeslot_set.first()
- ts.time = datetime.datetime.combine(day_before, ts.time.time())
+ ts.time = meeting.tz().localize(
+ datetime.datetime.combine(
+ day_before,
+ ts.time.astimezone(meeting.tz()).time(),
+ )
+ )
ts.save()
past_slots = room_groups[0][0].timeslot_set.filter(time__lt=right_now)
self.assertEqual(len(past_slots), 2, 'Need two timeslots in the past!')
@@ -1528,8 +1556,8 @@ class EditMeetingScheduleTests(TestCase):
self.fail('Response was not valid JSON: {}'.format(err))
@staticmethod
- def _right_now_in(tzname):
- right_now = timezone.now().astimezone(pytz.timezone(tzname))
+ def _right_now_in(tzinfo):
+ right_now = timezone.now().astimezone(tzinfo)
if not settings.USE_TZ:
right_now = right_now.replace(tzinfo=None)
return right_now
@@ -1541,7 +1569,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
- right_now = self._right_now_in(meeting.time_zone)
+ right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@@ -1601,7 +1629,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
- right_now = self._right_now_in(meeting.time_zone)
+ right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@@ -1736,7 +1764,7 @@ class EditMeetingScheduleTests(TestCase):
date=(timezone.now() - datetime.timedelta(days=1)).date(),
days=3,
)
- right_now = self._right_now_in(meeting.time_zone)
+ right_now = self._right_now_in(meeting.tz())
schedules = dict(
official=meeting.schedule,
@@ -1857,7 +1885,7 @@ class EditTimeslotsTests(TestCase):
return MeetingFactory(
type_id='ietf',
number=number,
- date=timezone.now() + datetime.timedelta(days=10),
+ date=date_today() + datetime.timedelta(days=10),
populate_schedule=False,
)
@@ -1889,7 +1917,8 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_bare_meeting(number=number)
RoomFactory.create_batch(8, meeting=meeting)
self.create_initial_schedule(meeting)
- return meeting
+ # retrieve meeting from DB so it goes through Django's processing
+ return Meeting.objects.get(pk=meeting.pk)
def test_view_permissions(self):
"""Only the secretary should be able to edit timeslots"""
@@ -2058,7 +2087,7 @@ class EditTimeslotsTests(TestCase):
meeting = self.create_meeting()
# add some timeslots
times = [datetime.time(hour=h) for h in (11, 14)]
- days = [meeting.get_meeting_date(ii).date() for ii in range(meeting.days)]
+ days = [meeting.get_meeting_date(ii) for ii in range(meeting.days)]
timeslots = []
duration = datetime.timedelta(minutes=90)
@@ -2068,7 +2097,7 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=room,
- time=datetime.datetime.combine(day, t),
+ time=meeting.tz().localize(datetime.datetime.combine(day, t)),
duration=duration,
)
for t in times
@@ -2149,17 +2178,21 @@ class EditTimeslotsTests(TestCase):
TimeSlotFactory(
meeting=meeting,
location=meeting.room_set.first(),
- time=datetime.datetime.combine(
- meeting.get_meeting_date(day).date(),
- datetime.time(hour=11)
+ time=meeting.tz().localize(
+ datetime.datetime.combine(
+ meeting.get_meeting_date(day),
+ datetime.time(hour=11),
+ )
),
)
TimeSlotFactory(
meeting=meeting,
location=meeting.room_set.first(),
- time=datetime.datetime.combine(
- meeting.get_meeting_date(day).date(),
- datetime.time(hour=14)
+ time=meeting.tz().localize(
+ datetime.datetime.combine(
+ meeting.get_meeting_date(day),
+ datetime.time(hour=14),
+ )
),
)
@@ -2258,10 +2291,8 @@ class EditTimeslotsTests(TestCase):
name_before = 'Name Classic (tm)'
type_before = 'regular'
- time_before = datetime.datetime.combine(
- meeting.date,
- datetime.time(hour=10),
- )
+ time_utc = pytz.utc.localize(datetime.datetime.combine(meeting.date, datetime.time(hour=10)))
+ time_before = time_utc.astimezone(meeting.tz())
duration_before = datetime.timedelta(minutes=60)
show_location_before = True
location_before = meeting.room_set.first()
@@ -2278,7 +2309,7 @@ class EditTimeslotsTests(TestCase):
self.login()
name_after = 'New Name (tm)'
type_after = 'plenary'
- time_after = time_before + datetime.timedelta(days=1, hours=2)
+ time_after = (time_utc + datetime.timedelta(days=1, hours=2)).astimezone(meeting.tz())
duration_after = duration_before * 2
show_location_after = False
location_after = meeting.room_set.last()
@@ -2458,8 +2489,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
- self.assertEqual(str(ts.time.date().toordinal()), post_data['days'])
- self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
+ self.assertEqual(str(ts.local_start_time().date().toordinal()), post_data['days'])
+ self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations'])
@@ -2468,7 +2499,7 @@ class EditTimeslotsTests(TestCase):
"""Creating a single timeslot outside the official meeting days should work"""
meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
- other_date = meeting.get_meeting_date(-7).date()
+ other_date = meeting.get_meeting_date(-7)
post_data = dict(
name='some name',
type='regular',
@@ -2491,8 +2522,8 @@ class EditTimeslotsTests(TestCase):
ts = meeting.timeslot_set.exclude(pk__in=timeslots_before).first() # only 1
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
- self.assertEqual(ts.time.date(), other_date)
- self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
+ self.assertEqual(ts.local_start_time().date(), other_date)
+ self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
self.assertEqual(str(ts.location.pk), post_data['locations'])
@@ -2706,8 +2737,8 @@ class EditTimeslotsTests(TestCase):
"""Creating multiple timeslots should work"""
meeting = self.create_meeting()
timeslots_before = set(ts.pk for ts in meeting.timeslot_set.all())
- days = [meeting.get_meeting_date(n).date() for n in range(meeting.days)]
- other_date = meeting.get_meeting_date(-1).date() # date before start of meeting
+ days = [meeting.get_meeting_date(n) for n in range(meeting.days)]
+ other_date = meeting.get_meeting_date(-1) # date before start of meeting
self.assertNotIn(other_date, days)
locations = meeting.room_set.all()
post_data = dict(
@@ -2737,10 +2768,10 @@ class EditTimeslotsTests(TestCase):
for ts in meeting.timeslot_set.exclude(pk__in=timeslots_before):
self.assertEqual(ts.name, post_data['name'])
self.assertEqual(ts.type_id, post_data['type'])
- self.assertEqual(ts.time.strftime('%H:%M'), post_data['time'])
+ self.assertEqual(ts.local_start_time().strftime('%H:%M'), post_data['time'])
self.assertEqual(str(ts.duration), '{}:00'.format(post_data['duration'])) # add seconds
self.assertEqual(ts.show_location, post_data['show_location'])
- self.assertIn(ts.time.date(), days)
+ self.assertIn(ts.local_start_time().date(), days)
self.assertIn(ts.location, locations)
self.assertIn((ts.time.date(), ts.location), day_locs,
'Duplicated day / location found')
@@ -3288,7 +3319,9 @@ class EditTests(TestCase):
room = Room.objects.get(meeting=meeting, session_types='regular')
base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
duration=datetime.timedelta(minutes=50),
- time=datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30)))
+ time=meeting.tz().localize(
+ datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30))
+ ))
timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time'))
@@ -3510,7 +3543,12 @@ class EditTests(TestCase):
self.assertIn("#scroll=1234", r['Location'])
test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing")
- self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(8, 30)))
+ self.assertEqual(
+ test_timeslot.time,
+ meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(8, 30))
+ ),
+ )
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1, minutes=30))
self.assertEqual(test_timeslot.location_id, break_room.pk)
self.assertEqual(test_timeslot.show_location, True)
@@ -3552,7 +3590,12 @@ class EditTests(TestCase):
})
self.assertNoFormPostErrors(r)
test_timeslot.refresh_from_db()
- self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(9, 30)))
+ self.assertEqual(
+ test_timeslot.time,
+ meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(9, 30))
+ ),
+ )
self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1))
self.assertEqual(test_timeslot.location_id, breakfast_room.pk)
self.assertEqual(test_timeslot.show_location, False)
@@ -4329,12 +4372,10 @@ class InterimTests(TestCase):
self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True, base_session=True)
def do_interim_send_announcement_test(self, base_session=False, extra_session=False, canceled_session=False):
- make_interim_test_data()
+ make_interim_test_data(meeting_tz='America/Los_Angeles')
session = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first()
meeting = session.meeting
- meeting.time_zone = 'America/Los_Angeles'
- meeting.save()
if base_session:
base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False)
@@ -4679,9 +4720,9 @@ class InterimTests(TestCase):
def do_interim_request_single_virtual(self, emails_expected):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
- date = datetime.date.today() + datetime.timedelta(days=30)
- time = timezone.now().time().replace(microsecond=0,second=0)
- dt = datetime.datetime.combine(date, time)
+ date = date_today() + datetime.timedelta(days=30)
+ time = time_now().replace(microsecond=0,second=0)
+ dt = pytz.utc.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3)
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
@@ -4750,13 +4791,14 @@ class InterimTests(TestCase):
def test_interim_request_single_in_person(self):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
- date = datetime.date.today() + datetime.timedelta(days=30)
- time = timezone.now().time().replace(microsecond=0,second=0)
- dt = datetime.datetime.combine(date, time)
+ date = date_today() + datetime.timedelta(days=30)
+ time = time_now().replace(microsecond=0,second=0)
+ time_zone = 'America/Los_Angeles'
+ tz = pytz.timezone(time_zone)
+ dt = tz.localize(datetime.datetime.combine(date, time))
duration = datetime.timedelta(hours=3)
city = 'San Francisco'
country = 'US'
- time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@@ -4797,16 +4839,17 @@ class InterimTests(TestCase):
def test_interim_request_multi_day(self):
make_meeting_test_data()
- date = datetime.date.today() + datetime.timedelta(days=30)
+ date = date_today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=1)
- time = timezone.now().time().replace(microsecond=0,second=0)
- dt = datetime.datetime.combine(date, time)
- dt2 = datetime.datetime.combine(date2, time)
+ time = time_now().replace(microsecond=0,second=0)
+ time_zone = 'America/Los_Angeles'
+ tz = pytz.timezone(time_zone)
+ dt = tz.localize(datetime.datetime.combine(date, time))
+ dt2 = tz.localize(datetime.datetime.combine(date2, time))
duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars')
city = 'San Francisco'
country = 'US'
- time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@@ -4923,7 +4966,7 @@ class InterimTests(TestCase):
def test_interim_request_series(self):
make_meeting_test_data()
meeting_count_before = Meeting.objects.filter(type='interim').count()
- date = datetime.date.today() + datetime.timedelta(days=30)
+ date = date_today() + datetime.timedelta(days=30)
if (date.month, date.day) == (12, 31):
# Avoid date and date2 in separate years
# (otherwise the test will fail if run on December 1st)
@@ -4933,14 +4976,15 @@ class InterimTests(TestCase):
if date.year != date2.year:
date += datetime.timedelta(days=1)
date2 += datetime.timedelta(days=1)
- time = timezone.now().time().replace(microsecond=0,second=0)
- dt = datetime.datetime.combine(date, time)
- dt2 = datetime.datetime.combine(date2, time)
+ time = time_now().replace(microsecond=0,second=0)
+ time_zone = 'America/Los_Angeles'
+ tz = pytz.timezone(time_zone)
+ dt = tz.localize(datetime.datetime.combine(date, time))
+ dt2 = tz.localize(datetime.datetime.combine(date2, time))
duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars')
city = ''
country = ''
- time_zone = 'America/Los_Angeles'
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
@@ -5082,14 +5126,14 @@ class InterimTests(TestCase):
self.assertFalse(can_manage_group(user=user,group=group))
def test_interim_request_details(self):
- make_interim_test_data()
+ make_interim_test_data(meeting_tz='America/Chicago')
meeting = Session.objects.with_current_status().filter(
meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
- start_time = meeting.session_set.first().official_timeslotassignment().timeslot.time.strftime('%H:%M')
+ start_time = meeting.session_set.first().official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M')
utc_start_time = meeting.session_set.first().official_timeslotassignment().timeslot.utc_start_time().strftime('%H:%M')
self.assertIn(start_time, unicontent(r))
self.assertIn(utc_start_time, unicontent(r))
@@ -5630,7 +5674,11 @@ class InterimTests(TestCase):
a1 = s1.official_timeslotassignment()
t1 = a1.timeslot
# Create an extra session
- t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
+ t2 = TimeSlotFactory.create(
+ meeting=meeting,
+ time=meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(11, 30))
+ ))
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
#
@@ -5640,8 +5688,8 @@ class InterimTests(TestCase):
self.assertContains(r, 'BEGIN:VEVENT')
self.assertEqual(r.content.count(b'UID'), 2)
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
- self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
- self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
+ self.assertContains(r, t1.local_start_time().strftime('%Y%m%dT%H%M%S'))
+ self.assertContains(r, t2.local_start_time().strftime('%Y%m%dT%H%M%S'))
self.assertContains(r, 'END:VEVENT')
#
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
@@ -7422,7 +7470,7 @@ class ProceedingsTests(BaseMeetingTestCase):
def test_proceedings_no_agenda(self):
# Meeting number must be larger than the last special-cased proceedings (currently 96)
- meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=datetime.date.today(), number='100')
+ meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=date_today(), number='100')
url = urlreverse('ietf.meeting.views.proceedings')
r = self.client.get(url)
self.assertRedirects(r, urlreverse('ietf.meeting.views.materials'))
@@ -7531,7 +7579,7 @@ class ProceedingsTests(BaseMeetingTestCase):
"""Generate a meeting for proceedings material test"""
# meeting number 123 avoids various legacy cases that affect these tests
# (as of Aug 2021, anything above 96 is probably ok)
- return MeetingFactory(type_id='ietf', number='123', date=datetime.date.today())
+ return MeetingFactory(type_id='ietf', number='123', date=date_today())
def _secretary_only_permission_test(self, url, include_post=True):
self.client.logout()
diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py
index f6c1800f1..25366e7bd 100644
--- a/ietf/meeting/utils.py
+++ b/ietf/meeting/utils.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import datetime
import itertools
+import pytz
import requests
import subprocess
@@ -34,13 +35,17 @@ def session_time_for_sorting(session, use_meeting_date):
if official_timeslot:
return official_timeslot.time
elif use_meeting_date and session.meeting.date:
- return datetime.datetime.combine(session.meeting.date, datetime.time.min)
+ return session.meeting.tz().localize(
+ datetime.datetime.combine(session.meeting.date, datetime.time.min)
+ )
else:
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
if first_event:
return first_event.time
else:
- return datetime.datetime.min
+ # n.b. cannot interpret this in timezones west of UTC. That is not expected to be necessary,
+ # but could probably safely add a day to the minimum datetime to make that possible.
+ return pytz.utc.localize(datetime.datetime.min)
def session_requested_by(session):
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
@@ -159,7 +164,12 @@ def create_proceedings_templates(meeting):
def finalize(meeting):
end_date = meeting.end_date()
- end_time = datetime.datetime.combine(end_date, datetime.datetime.min.time())+datetime.timedelta(days=1)
+ end_time = meeting.tz().localize(
+ datetime.datetime.combine(
+ end_date,
+ datetime.time.min,
+ )
+ ).astimezone(pytz.utc) + datetime.timedelta(days=1)
for session in meeting.session_set.all():
for sp in session.sessionpresentation_set.filter(document__type='draft',rev=None):
rev_before_end = [e for e in sp.document.docevent_set.filter(newrevisiondocevent__isnull=False).order_by('-time') if e.time <= end_time ]
@@ -323,7 +333,9 @@ def preprocess_constraints_for_meeting_schedule_editor(meeting, sessions):
# synthesize AD constraints - we can treat them as a special kind of 'bethere'
responsible_ad_for_group = {}
session_groups = set(s.group for s in sessions if s.group and s.group.parent and s.group.parent.type_id == 'area')
- meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
+ meeting_time = meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
+ )
# dig up historic AD names
for group_id, history_time, pk in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'pk').order_by('rolehistory__group__time'):
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index c8eac6f9f..1d38346c7 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -96,6 +96,7 @@ from ietf.utils.pipe import pipe
from ietf.utils.pdf import pdf_pages
from ietf.utils.response import permission_denied
from ietf.utils.text import xslugify
+from ietf.utils.timezone import datetime_today, date_today
from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
@@ -127,7 +128,7 @@ def materials(request, num=None):
begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date()
- now = datetime.date.today()
+ now = date_today()
old = timezone.now() - datetime.timedelta(days=1)
if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET:
pass
@@ -142,7 +143,7 @@ def materials(request, num=None):
'cor_cut_off_date': cor_cut_off_date
})
- past_cutoff_date = datetime.date.today() > meeting.get_submission_correction_date()
+ past_cutoff_date = date_today() > meeting.get_submission_correction_date()
schedule = get_schedule(meeting, None)
@@ -192,7 +193,7 @@ def materials(request, num=None):
})
def current_materials(request):
- today = datetime.date.today()
+ today = date_today()
meetings = Meeting.objects.exclude(number__startswith='interim-').filter(date__lte=today).order_by('-date')
if meetings:
return redirect(materials, meetings[0].number)
@@ -281,60 +282,63 @@ def materials_editable_groups(request, num=None):
def edit_timeslots(request, num=None):
meeting = get_meeting(num)
+ timezone.activate(meeting.tz())
+ try:
+ if request.method == 'POST':
+ # handle AJAX requests
+ action = request.POST.get('action')
+ if action == 'delete':
+ # delete a timeslot
+ # Parameters:
+ # slot_id: comma-separated list of TimeSlot PKs to delete
+ slot_id = request.POST.get('slot_id')
+ if slot_id is None:
+ return HttpResponseBadRequest('missing slot_id')
+ slot_ids = [id.strip() for id in slot_id.split(',')]
+ try:
+ timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
+ except ValueError:
+ return HttpResponseBadRequest('invalid slot_id specification')
+ missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
+ if len(missing_ids) != 0:
+ return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
+ meeting.number,
+ ', '.join(sorted(missing_ids))
+ ))
+ timeslots.delete()
+ return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
+ else:
+ return HttpResponseBadRequest('unknown action')
- if request.method == 'POST':
- # handle AJAX requests
- action = request.POST.get('action')
- if action == 'delete':
- # delete a timeslot
- # Parameters:
- # slot_id: comma-separated list of TimeSlot PKs to delete
- slot_id = request.POST.get('slot_id')
- if slot_id is None:
- return HttpResponseBadRequest('missing slot_id')
- slot_ids = [id.strip() for id in slot_id.split(',')]
- try:
- timeslots = meeting.timeslot_set.filter(pk__in=slot_ids)
- except ValueError:
- return HttpResponseBadRequest('invalid slot_id specification')
- missing_ids = set(slot_ids).difference(str(ts.pk) for ts in timeslots)
- if len(missing_ids) != 0:
- return HttpResponseNotFound('TimeSlot ids not found in meeting {}: {}'.format(
- meeting.number,
- ', '.join(sorted(missing_ids))
- ))
- timeslots.delete()
- return HttpResponse(content='; '.join('Deleted TimeSlot {}'.format(id) for id in slot_ids))
- else:
- return HttpResponseBadRequest('unknown action')
+ # Labels here differ from those in the build_timeslices() method. The labels here are
+ # relative to the table: time_slices are the row headings (ie, days), date_slices are
+ # the column headings (i.e., time intervals), and slots are the per-day list of timeslots
+ # (with only one timeslot per unique time/duration)
+ time_slices, date_slices, slots = meeting.build_timeslices()
- # Labels here differ from those in the build_timeslices() method. The labels here are
- # relative to the table: time_slices are the row headings (ie, days), date_slices are
- # the column headings (i.e., time intervals), and slots are the per-day list of timeslots
- # (with only one timeslot per unique time/duration)
- time_slices, date_slices, slots = meeting.build_timeslices()
+ ts_list = deque()
+ rooms = meeting.room_set.order_by("capacity","name","id")
+ for room in rooms:
+ for day in time_slices:
+ for slice in date_slices[day]:
+ ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
- ts_list = deque()
- rooms = meeting.room_set.order_by("capacity","name","id")
- for room in rooms:
- for day in time_slices:
- for slice in date_slices[day]:
- ts_list.append(room.timeslot_set.filter(time=slice[0],duration=datetime.timedelta(seconds=slice[2])))
+ # Grab these in one query each to identify sessions that are in use and should be handled with care
+ ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
+ ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
- # Grab these in one query each to identify sessions that are in use and should be handled with care
- ts_with_official_assignments = meeting.timeslot_set.filter(sessionassignments__schedule=meeting.schedule)
- ts_with_any_assignments = meeting.timeslot_set.filter(sessionassignments__isnull=False)
-
- return render(request, "meeting/timeslot_edit.html",
- {"rooms":rooms,
- "time_slices":time_slices,
- "slot_slices": slots,
- "date_slices":date_slices,
- "meeting":meeting,
- "ts_list":ts_list,
- "ts_with_official_assignments": ts_with_official_assignments,
- "ts_with_any_assignments": ts_with_any_assignments,
- })
+ return render(request, "meeting/timeslot_edit.html",
+ {"rooms":rooms,
+ "time_slices":time_slices,
+ "slot_slices": slots,
+ "date_slices":date_slices,
+ "meeting":meeting,
+ "ts_list":ts_list,
+ "ts_with_official_assignments": ts_with_official_assignments,
+ "ts_with_any_assignments": ts_with_any_assignments,
+ })
+ finally:
+ timezone.deactivate()
class NewScheduleForm(forms.ModelForm):
class Meta:
@@ -1151,7 +1155,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
meeting=meeting,
type=c['type'],
name=c['name'],
- time=datetime.datetime.combine(c['day'], c['time']),
+ time=meeting.tz().localize(datetime.datetime.combine(c['day'], c['time'])),
duration=c['duration'],
location=c['location'],
show_location=c['show_location'],
@@ -1200,7 +1204,7 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
timeslot.type = c['type']
timeslot.name = c['name']
- timeslot.time = datetime.datetime.combine(c['day'], c['time'])
+ timeslot.time = meeting.tz().localize(datetime.datetime.combine(c['day'], c['time']))
timeslot.duration = c['duration']
timeslot.location = c['location']
timeslot.show_location = c['show_location']
@@ -1287,8 +1291,9 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
for t in timeslot_qs:
timeslots_by_day_and_room[(t.time.date(), t.location_id)].append(t)
- min_time = min([t.time.time() for t in timeslot_qs] + [datetime.time(8)])
- max_time = max([t.end_time().time() for t in timeslot_qs] + [datetime.time(22)])
+ # Calculate full time range for display in meeting-local time, always showing at least 8am to 10pm
+ min_time = min([t.local_start_time().time() for t in timeslot_qs] + [datetime.time(8)])
+ max_time = max([t.local_end_time().time() for t in timeslot_qs] + [datetime.time(22)])
min_max_delta = datetime.datetime.combine(meeting.date, max_time) - datetime.datetime.combine(meeting.date, min_time)
day_grid = []
@@ -1310,7 +1315,14 @@ def edit_meeting_timeslots_and_misc_sessions(request, num=None, owner=None, name
if s:
t.assigned_sessions.append(s)
- t.left_offset = 100.0 * (t.time - datetime.datetime.combine(t.time.date(), min_time)) / min_max_delta
+ local_start_dt = t.local_start_time()
+ local_min_dt = local_start_dt.replace(
+ hour=min_time.hour,
+ minute=min_time.minute,
+ second=min_time.second,
+ microsecond=min_time.microsecond,
+ )
+ t.left_offset = 100.0 * (local_start_dt - local_min_dt) / min_max_delta
t.layout_width = min(100.0 * t.duration / min_max_delta, 100 - t.left_offset)
ts.append(t)
@@ -1553,19 +1565,29 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
- rendered_page = render(request, "meeting/"+base+ext, {
- "personalize": False,
- "schedule": schedule,
- "filtered_assignments": filtered_assignments,
- "updated": updated,
- "filter_categories": filter_organizer.get_filter_categories(),
- "non_area_keywords": filter_organizer.get_non_area_keywords(),
- "now": timezone.now().astimezone(pytz.utc),
- "timezone": meeting.time_zone,
- "is_current_meeting": is_current_meeting,
- "use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
- "cache_time": 150 if is_current_meeting else 3600,
- }, content_type=mimetype[ext])
+ display_timezone = 'UTC' if utc else meeting.time_zone
+ timezone.activate(display_timezone)
+ try:
+ rendered_page = render(
+ request,
+ "meeting/" + base + ext,
+ {
+ "personalize": False,
+ "schedule": schedule,
+ "filtered_assignments": filtered_assignments,
+ "updated": updated,
+ "filter_categories": filter_organizer.get_filter_categories(),
+ "non_area_keywords": filter_organizer.get_non_area_keywords(),
+ "now": timezone.now().astimezone(pytz.utc),
+ "display_timezone": display_timezone,
+ "is_current_meeting": is_current_meeting,
+ "use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
+ "cache_time": 150 if is_current_meeting else 3600,
+ },
+ content_type=mimetype[ext],
+ )
+ finally:
+ timezone.deactivate()
return rendered_page
@@ -1917,7 +1939,7 @@ def agenda_personalize(request, num):
'filtered_assignments': filtered_assignments,
'filter_categories': filter_organizer.get_filter_categories(),
'non_area_labels': filter_organizer.get_non_area_keywords(),
- 'timezone': meeting.time_zone,
+ 'display_timezone': meeting.time_zone,
'is_current_meeting': is_current_meeting,
'cache_time': 150 if is_current_meeting else 3600,
}
@@ -2305,16 +2327,14 @@ def agenda_json(request, num=None):
meetinfo.sort(key=lambda x: x['modified'],reverse=True)
last_modified = meetinfo and meetinfo[0]['modified']
- tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
-
for obj in meetinfo:
- obj['modified'] = tz.localize(obj['modified']).astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
+ obj['modified'] = obj['modified'].astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
data = {"%s"%num: meetinfo}
response = HttpResponse(json.dumps(data, indent=2, sort_keys=True), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET)
if last_modified:
- last_modified = tz.localize(last_modified).astimezone(pytz.utc)
+ last_modified = last_modified.astimezone(pytz.utc)
response['Last-Modified'] = format_date_time(timegm(last_modified.timetuple()))
return response
@@ -2386,7 +2406,9 @@ def session_details(request, num, acronym):
# Find the time of the meeting, so that we can look back historically
# for what the group was called at the time.
- meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
+ meeting_time = meeting.tz().localize(
+ datetime.datetime.combine(meeting.date, datetime.time())
+ )
groups = list(set([ s.group for s in sessions ]))
group_replacements = find_history_replacements_active_at(groups, meeting_time)
@@ -2453,7 +2475,7 @@ def session_details(request, num, acronym):
'is_materials_manager' : session.group.has_role(request.user, session.group.features.matman_roles),
'can_manage_materials' : can_manage,
'can_view_request': can_view_request,
- 'thisweek': datetime.date.today()-datetime.timedelta(days=7),
+ 'thisweek': datetime_today()-datetime.timedelta(days=7),
'now': timezone.now(),
'use_codimd': True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
})
@@ -3552,7 +3574,7 @@ def past(request):
def upcoming(request):
'''List of upcoming meetings'''
- today = datetime.date.today()
+ today = datetime_today()
# Get ietf meetings starting 7 days ago, and interim meetings starting today
ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
@@ -3616,7 +3638,7 @@ def upcoming(request):
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
'now': timezone.now(),
- 'use_codimd': True if datetime.date.today()>=settings.MEETING_USES_CODIMD_DATE else False,
+ 'use_codimd': (date_today() >= settings.MEETING_USES_CODIMD_DATE),
})
@@ -3630,7 +3652,7 @@ def upcoming_ical(request):
except ValueError as e:
return HttpResponseBadRequest(str(e))
- today = datetime.date.today()
+ today = datetime_today()
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).prefetch_related('schedule').order_by('date'))
@@ -3680,7 +3702,7 @@ def upcoming_ical(request):
def upcoming_json(request):
'''Return Upcoming meetings in json format'''
- today = datetime.date.today()
+ today = datetime_today()
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
meetings = data_for_meetings_overview(Meeting.objects.filter(date__gte=today-datetime.timedelta(days=7)).order_by('date'))
@@ -3729,7 +3751,7 @@ def proceedings(request, num=None):
begin_date = meeting.get_submission_start_date()
cut_off_date = meeting.get_submission_cut_off_date()
cor_cut_off_date = meeting.get_submission_correction_date()
- now = datetime.date.today()
+ now = date_today()
schedule = get_schedule(meeting, None)
sessions = add_event_info_to_session_qs(
@@ -4026,7 +4048,7 @@ def important_dates(request, num=None, output_format=None):
base_num = int(meeting.number)
user = request.user
- today = datetime.date.today()
+ today = datetime_today()
meetings = []
if meeting.show_important_dates or meeting.date < today:
meetings.append(meeting)
@@ -4078,23 +4100,27 @@ def edit_timeslot(request, num, slot_id):
meeting = get_object_or_404(Meeting, number=num)
if timeslot.meeting != meeting:
raise Http404()
- if request.method == 'POST':
- form = TimeSlotEditForm(instance=timeslot, data=request.POST)
- if form.is_valid():
- form.save()
- return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
- else:
- form = TimeSlotEditForm(instance=timeslot)
+ timezone.activate(meeting.tz()) # specifies current_timezone used for rendering and form handling
+ try:
+ if request.method == 'POST':
+ form = TimeSlotEditForm(instance=timeslot, data=request.POST)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(reverse('ietf.meeting.views.edit_timeslots', kwargs={'num': num}))
+ else:
+ form = TimeSlotEditForm(instance=timeslot)
- sessions = timeslot.sessions.filter(
- timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
+ sessions = timeslot.sessions.filter(
+ timeslotassignments__schedule__in=[meeting.schedule, meeting.schedule.base if meeting.schedule else None])
- return render(
- request,
- 'meeting/edit_timeslot.html',
- {'timeslot': timeslot, 'form': form, 'sessions': sessions},
- status=400 if form.errors else 200,
- )
+ return render(
+ request,
+ 'meeting/edit_timeslot.html',
+ {'timeslot': timeslot, 'form': form, 'sessions': sessions},
+ status=400 if form.errors else 200,
+ )
+ finally:
+ timezone.deactivate()
@role_required('Secretariat')
@@ -4105,7 +4131,7 @@ def create_timeslot(request, num):
if form.is_valid():
bulk_create_timeslots(
meeting,
- [datetime.datetime.combine(day, form.cleaned_data['time'])
+ [meeting.tz().localize(datetime.datetime.combine(day, form.cleaned_data['time']))
for day in form.cleaned_data.get('days', [])],
form.cleaned_data['locations'],
dict(
diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json
index 41f7fec62..604b11cb2 100644
--- a/ietf/name/fixtures/names.json
+++ b/ietf/name/fixtures/names.json
@@ -15998,7 +15998,7 @@
"fields": {
"command": "xym",
"switch": "--version",
- "time": "2022-07-13T00:09:29.108",
+ "time": "2022-07-13T00:09:29.108Z",
"used": true,
"version": "xym 0.5"
},
@@ -16009,7 +16009,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
- "time": "2022-07-13T00:09:29.475",
+ "time": "2022-07-13T00:09:29.475Z",
"used": true,
"version": "pyang 2.5.3"
},
@@ -16020,7 +16020,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
- "time": "2022-07-13T00:09:29.497",
+ "time": "2022-07-13T00:09:29.497Z",
"used": true,
"version": "yanglint SO 1.9.2"
},
@@ -16031,7 +16031,7 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
- "time": "2022-07-13T00:09:30.513",
+ "time": "2022-07-13T00:09:30.513Z",
"used": true,
"version": "xml2rfc 3.13.0"
},
diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py
index 505bcdcc7..f1fcae132 100644
--- a/ietf/secr/proceedings/proc_utils.py
+++ b/ietf/secr/proceedings/proc_utils.py
@@ -9,6 +9,7 @@ This module contains all the functions for generating static proceedings pages
'''
import datetime
import os
+import pytz
import re
import subprocess
from urllib.parse import urlencode
@@ -201,17 +202,22 @@ def send_audio_import_warning(unmatched_files):
# End Recording Functions
# -------------------------------------------------
-def get_progress_stats(sdate,edate):
+def get_progress_stats(sdate, edate):
'''
This function takes a date range and produces a dictionary of statistics / objects for
use in a progress report. Generally the end date will be the date of the last meeting
and the start date will be the date of the meeting before that.
+
+ Data between midnight UTC on the specified dates are included in the stats.
'''
+ sdatetime = pytz.utc.localize(datetime.datetime.combine(sdate, datetime.time()))
+ edatetime = pytz.utc.localize(datetime.datetime.combine(edate, datetime.time()))
+
data = {}
data['sdate'] = sdate
data['edate'] = edate
- events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lt=edate)
+ events = DocEvent.objects.filter(doc__type='draft', time__gte=sdatetime, time__lt=edatetime)
data['actions_count'] = events.filter(type='iesg_approved').count()
data['last_calls_count'] = events.filter(type='sent_last_call').count()
@@ -226,7 +232,7 @@ def get_progress_stats(sdate,edate):
data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ]))
# Calculate Final Four Weeks stats (ffw)
- ffwdate = edate - datetime.timedelta(days=28)
+ ffwdate = edatetime - datetime.timedelta(days=28)
ffw_new_count = events.filter(time__gte=ffwdate,newrevisiondocevent__rev='00').count()
try:
ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%')
@@ -257,14 +263,14 @@ def get_progress_stats(sdate,edate):
data['new_groups'] = Group.objects.filter(
type='wg',
groupevent__changestategroupevent__state='active',
- groupevent__time__gte=sdate,
- groupevent__time__lt=edate)
+ groupevent__time__gte=sdatetime,
+ groupevent__time__lt=edatetime)
data['concluded_groups'] = Group.objects.filter(
type='wg',
groupevent__changestategroupevent__state='conclude',
- groupevent__time__gte=sdate,
- groupevent__time__lt=edate)
+ groupevent__time__gte=sdatetime,
+ groupevent__time__lt=edatetime)
return data
diff --git a/ietf/settings.py b/ietf/settings.py
index c41d4ae0f..0beb06a84 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -114,7 +114,7 @@ SITE_ID = 1
# to load the internationalization machinery.
USE_I18N = False
-USE_TZ = False
+USE_TZ = True
if SERVER_MODE == 'production':
MEDIA_ROOT = '/a/www/www6s/lib/dt/media/'
@@ -1187,8 +1187,6 @@ CELERY_TIMEZONE = 'UTC'
CELERY_BROKER_URL = 'amqp://mq/'
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
CELERY_BEAT_SYNC_EVERY = 1 # update DB after every event
-assert not USE_TZ, 'Drop DJANGO_CELERY_BEAT_TZ_AWARE setting once USE_TZ is True!'
-DJANGO_CELERY_BEAT_TZ_AWARE = False
# Meetecho API setup: Uncomment this and provide real credentials to enable
# Meetecho conference creation for interim session requests
diff --git a/ietf/templates/meeting/agenda.html b/ietf/templates/meeting/agenda.html
index 1d037010a..959b6feaf 100644
--- a/ietf/templates/meeting/agenda.html
+++ b/ietf/templates/meeting/agenda.html
@@ -4,7 +4,7 @@
{% load static %}
{% load ietf_filters %}
{% load textfilters %}
-{% load htmlfilters agenda_custom_tags %}
+{% load htmlfilters agenda_custom_tags tz %}
{% block title %}
IETF {{ schedule.meeting.number }} Meeting Agenda
{% if "-utc" in request.path %}(UTC){% endif %}
@@ -36,10 +36,10 @@
Jump to current session
{% endif %}
- {% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=timezone minimal=True only %}
+ {% include 'meeting/tz-display.html' with id_suffix="-rh" meeting_timezone=display_timezone minimal=True only %}
- Showing {{ timezone|split:"_"|join:" "|split:"/"|join:" / " }} time
+ Showing {{ display_timezone|split:"_"|join:" "|split:"/"|join:" / " }} time
{% endif %}
- {% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=timezone only %}
+ {% include 'meeting/tz-display.html' with id_suffix="" meeting_timezone=display_timezone only %}
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Filter this agenda view..." always_show=personalize %}
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting personalize=personalize only %}
{% if item.timeslot.show_location and item.timeslot.location %}
{% location_anchor item.timeslot %}
@@ -327,11 +328,11 @@
{% if item.session.rescheduled_to %}
TO
-
{% if "-utc" in request.path %}
- {{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
+ {{ item.session.rescheduled_to.time|utc|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|utc|date:"G:i" }}
{% else %}
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}
{% endif %}
@@ -487,7 +488,7 @@
$(document).ready(function() {
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
- meeting_timezone = '{{ timezone }}';
+ meeting_timezone = '{{ display_timezone }}';
// First, initialize_moments(). This must be done before calling any of the update methods.
// It does not need timezone info, so safe to call before initializing ietf_timezone.
diff --git a/ietf/templates/meeting/agenda.ics b/ietf/templates/meeting/agenda.ics
index 4f207afae..18ec8def8 100644
--- a/ietf/templates/meeting/agenda.ics
+++ b/ietf/templates/meeting/agenda.ics
@@ -1,4 +1,4 @@
-{% load humanize %}{% autoescape off %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
+{% load humanize tz %}{% autoescape off %}{% timezone schedule.meeting.tz %}{% load ietf_filters textfilters %}{% load cache %}{% cache 1800 ietf_meeting_agenda_ics schedule.meeting.number request.path request.GET %}BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
PRODID:-//IETF//datatracker.ietf.org ical agenda//EN
@@ -8,8 +8,8 @@ SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{% if n
{% if item.timeslot.show_location %}LOCATION:{{item.timeslot.get_location}}
{% endif %}STATUS:{{item.session.ical_status}}
CLASS:PUBLIC
-DTSTART{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
-DTEND{% if schedule.meeting.time_zone %};TZID={{schedule.meeting.time_zone|ics_esc}}{%endif%}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
+DTSTART;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
+DTEND;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
DTSTAMP:{{ item.timeslot.modified|date:"Ymd" }}T{{ item.timeslot.modified|date:"His" }}Z{% if item.session.agenda %}
URL:{{item.session.agenda.get_versionless_href}}{% endif %}
DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
@@ -25,4 +25,4 @@ DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
\n{# link agenda for ietf meetings #}
See in schedule: {% absurl 'ietf.meeting.views.agenda' num=schedule.meeting.number %}#row-{{ item.slug }}\n{% endif %}
END:VEVENT
-{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endautoescape %}
+{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endtimezone %}{% endautoescape %}
diff --git a/ietf/templates/meeting/interim_announcement.txt b/ietf/templates/meeting/interim_announcement.txt
index 074394099..f9d5394c3 100644
--- a/ietf/templates/meeting/interim_announcement.txt
+++ b/ietf/templates/meeting/interim_announcement.txt
@@ -1,11 +1,11 @@
-{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
+{% load ietf_filters tz %}{% timezone meeting.tz %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
{% endif %}The {{ group.name }} ({{ group.acronym }}) {% if group.type.slug == 'wg' and group.state.slug == 'bof' %}BOF{% else %}{{group.type.name}}{% endif %} will hold
-{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.utc_start_time | date:"H:i" }} to {{ assignments.first.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}.
+{% if assignments.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ assignments.first.timeslot.time | date:"H:i" }} to {{ assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone}}{% if meeting.time_zone != 'UTC' %} ({{ assignments.first.timeslot.time | utc | date:"H:i" }} to {{ assignments.first.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}.
{% else %}a multi-day {% if not meeting.city %}virtual {% endif %}interim meeting.
{% for assignment in assignments %}Session {{ forloop.counter }}:
-{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.utc_start_time | date:"H:i" }} to {{ assignment.timeslot.utc_end_time | date:"H:i" }} UTC){% endif %}
+{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}{% if meeting.time_zone != 'UTC' %}({{ assignment.timeslot.time | utc | date:"H:i" }} to {{ assignment.timeslot.end_time | utc | date:"H:i" }} UTC){% endif %}
{% endfor %}{% endif %}
{% if meeting.city %}Meeting Location:
{{ meeting.city }}, {{ meeting.country }}
@@ -17,3 +17,4 @@ Information about remote participation:
{{ meeting.session_set.first.remote_instructions }}
{{ meeting.session_set.first.agenda_note }}
+{% endtimezone %}
\ No newline at end of file
diff --git a/ietf/templates/meeting/interim_request_details.html b/ietf/templates/meeting/interim_request_details.html
index 0cfec501b..d0476895f 100644
--- a/ietf/templates/meeting/interim_request_details.html
+++ b/ietf/templates/meeting/interim_request_details.html
@@ -1,12 +1,12 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
-{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters %}
+{% load static django_bootstrap5 widget_tweaks ietf_filters person_filters textfilters tz %}
{% block title %}Interim Request Details{% endblock %}
{% block pagehead %}
{% endblock %}
-{% block content %}
+{% block content %}{% timezone meeting.tz %}
{% origin %}