Skip to content

Commit 4084d7d

Browse files
fix: record and interpret RFC pub dates in correct timezone (ietf-tools#4421)
* fix: use PST8PDT for published_rfc event timestamps * fix: find RFCs by PST8PDT year in RfcFeed * refactor: add const RPC_TZINFO to represent RFC publication timezone * chore: remove (rather than fix) unused template tags * fix: always return RPC_TZINFO-local date from Document.pub_date() * refactor: convert 'published' flag to a Boolean to reflect its usage * fix: display doc publication dates in correct time zones * fix: fix various small issues breaking tests
1 parent d0383c7 commit 4084d7d

13 files changed

Lines changed: 68 additions & 58 deletions

File tree

ietf/doc/feeds.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ietf.doc.models import Document, State, LastCallDocEvent, DocEvent
1717
from ietf.doc.utils import augment_events_with_revision
1818
from ietf.doc.templatetags.ietf_filters import format_textarea
19+
from ietf.utils.timezone import RPC_TZINFO
1920

2021

2122
def strip_control_characters(s):
@@ -134,7 +135,14 @@ def get_object(self,request,year=None):
134135

135136
def items(self):
136137
if self.year:
137-
rfc_events = DocEvent.objects.filter(type='published_rfc',time__year=self.year).order_by('-time')
138+
# Find published RFCs based on their official publication year
139+
start_of_year = datetime.datetime(int(self.year), 1, 1, tzinfo=RPC_TZINFO)
140+
start_of_next_year = datetime.datetime(int(self.year) + 1, 1, 1, tzinfo=RPC_TZINFO)
141+
rfc_events = DocEvent.objects.filter(
142+
type='published_rfc',
143+
time__gte=start_of_year,
144+
time__lt=start_of_next_year,
145+
).order_by('-time')
138146
else:
139147
cutoff = timezone.now() - datetime.timedelta(days=8)
140148
rfc_events = DocEvent.objects.filter(type='published_rfc',time__gte=cutoff).order_by('-time')

ietf/doc/models.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from ietf.utils.validators import validate_no_control_chars
3737
from ietf.utils.mail import formataddr
3838
from ietf.utils.models import ForeignKey
39+
from ietf.utils.timezone import RPC_TZINFO
3940
if TYPE_CHECKING:
4041
# importing other than for type checking causes errors due to cyclic imports
4142
from ietf.meeting.models import ProceedingsMaterial, Session
@@ -925,13 +926,18 @@ def submission(self):
925926
return s
926927

927928
def pub_date(self):
928-
"""This is the rfc publication date (datetime) for RFCs,
929-
and the new-revision datetime for other documents."""
929+
"""Get the publication date for this document
930+
931+
This is the rfc publication date for RFCs, and the new-revision date for other documents.
932+
"""
930933
if self.get_state_slug() == "rfc":
934+
# As of Sept 2022, in ietf.sync.rfceditor.update_docs_from_rfc_index() `published_rfc` events are
935+
# created with a timestamp whose date *in the PST8PDT timezone* is the official publication date
936+
# assigned by the RFC editor.
931937
event = self.latest_event(type='published_rfc')
932938
else:
933939
event = self.latest_event(type='new_revision')
934-
return event.time
940+
return event.time.astimezone(RPC_TZINFO).date() if event else None
935941

936942
def is_dochistory(self):
937943
return False

ietf/doc/tests.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
5959
from ietf.utils.test_utils import TestCase
6060
from ietf.utils.text import normalize_text
61-
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO
61+
from ietf.utils.timezone import datetime_today, DEADLINE_TZINFO, RPC_TZINFO
6262

6363

6464
class SearchTests(TestCase):
@@ -1431,7 +1431,7 @@ def _run_test(username=None, expect_buttons=False):
14311431

14321432
def test_draft_group_link(self):
14331433
"""Link to group 'about' page should have correct format"""
1434-
event_datetime = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles'))
1434+
event_datetime = datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO)
14351435

14361436
for group_type_id in ['wg', 'rg', 'ag']:
14371437
group = GroupFactory(type_id=group_type_id)
@@ -1890,13 +1890,13 @@ def test_document_bibtex(self):
18901890
#other_aliases = ['rfc6020',],
18911891
states = [('draft','rfc'),('draft-iesg','pub')],
18921892
std_level_id = 'ps',
1893-
time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles')),
1893+
time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
18941894
)
18951895
num = rfc.rfc_number()
18961896
DocEventFactory.create(
18971897
doc=rfc,
18981898
type='published_rfc',
1899-
time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo('America/Los_Angeles')),
1899+
time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO),
19001900
)
19011901
#
19021902
url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name))
@@ -1915,13 +1915,13 @@ def test_document_bibtex(self):
19151915
stream_id = 'ise',
19161916
states = [('draft','rfc'),('draft-iesg','pub')],
19171917
std_level_id = 'inf',
1918-
time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo('America/Los_Angeles')),
1918+
time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo(settings.TIME_ZONE)),
19191919
)
19201920
num = april1.rfc_number()
19211921
DocEventFactory.create(
19221922
doc=april1,
19231923
type='published_rfc',
1924-
time=datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo('America/Los_Angeles')),
1924+
time=datetime.datetime(1990, 4, 1, tzinfo=RPC_TZINFO),
19251925
)
19261926
#
19271927
url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name))
@@ -2057,8 +2057,7 @@ def tearDown(self):
20572057
super().tearDown()
20582058

20592059
def testManagementCommand(self):
2060-
tz = ZoneInfo('America/Los_Angeles')
2061-
a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(tz)
2060+
a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(RPC_TZINFO)
20622061
a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0)
20632062
ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
20642063
shepherd = PersonFactory()
@@ -2075,8 +2074,8 @@ def testManagementCommand(self):
20752074
doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad)
20762075
doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago)
20772076
DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago)
2078-
doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10, tzinfo=tz))
2079-
DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=tz))
2077+
doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10, tzinfo=ZoneInfo(settings.TIME_ZONE)))
2078+
DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO))
20802079
doc5 = IndividualDraftFactory(authors=[author6])
20812080

20822081
args = [ ]

ietf/doc/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ def get_replaces_tree(doc):
968968
history[url]['pages'] = d.history_set.filter(rev=e.newrevisiondocevent.rev).first().pages
969969

970970
if doc.type_id == "draft":
971+
# e.time.date() agrees with RPC publication date when shown in the RPC_TZINFO time zone
971972
e = doc.latest_event(type='published_rfc')
972973
else:
973974
e = doc.latest_event(type='iesg_approved')

ietf/doc/utils_search.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import datetime
66
import debug # pyflakes:ignore
77

8+
from zoneinfo import ZoneInfo
9+
10+
from django.conf import settings
11+
812
from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, TelechatDocEvent, BallotDocEvent
913
from ietf.doc.expire import expirable_drafts
1014
from ietf.doc.utils import augment_docs_and_user_with_user_info
@@ -204,7 +208,7 @@ def num(i):
204208
if sort_key == "title":
205209
res.append(d.title)
206210
elif sort_key == "date":
207-
res.append(str(d.latest_revision_date))
211+
res.append(str(d.latest_revision_date.astimezone(ZoneInfo(settings.TIME_ZONE))))
208212
elif sort_key == "status":
209213
if rfc_num != None:
210214
res.append(num(rfc_num))

ietf/doc/views_doc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ def document_bibtex(request, name, rev=None):
967967

968968
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
969969
replaced_by = [d.name for d in doc.related_that("replaces")]
970-
published = doc.latest_event(type="published_rfc")
970+
published = doc.latest_event(type="published_rfc") is not None
971971
rfc = latest_revision.doc if latest_revision and latest_revision.doc.get_state_slug() == "rfc" else None
972972

973973
if rev != None and rev != doc.rev:

ietf/secr/proceedings/templatetags/ams_filters.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,6 @@ def display_duration(value):
3838
x=int(value)
3939
return "%d Hours %d Minutes %d Seconds"%(x//3600,(x%3600)//60,x%60)
4040

41-
@register.filter
42-
def get_published_date(doc):
43-
'''
44-
Returns the published date for a RFC Document
45-
'''
46-
event = doc.latest_event(type='published_rfc')
47-
if event:
48-
return event.time
49-
event = doc.latest_event(type='new_revision')
50-
if event:
51-
return event.time
52-
else:
53-
return None
54-
5541
@register.filter
5642
def is_ppt(value):
5743
'''

ietf/secr/sreq/templatetags/ams_filters.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,6 @@ def display_duration(value):
4242
else:
4343
return "%d Hours %d Minutes %d Seconds"%(value//3600,(value%3600)//60,value%60)
4444

45-
@register.filter
46-
def get_published_date(doc):
47-
'''
48-
Returns the published date for a RFC Document
49-
'''
50-
event = doc.latest_event(type='published_rfc')
51-
if event:
52-
return event.time
53-
event = doc.latest_event(type='new_revision')
54-
if event:
55-
return event.time
56-
else:
57-
return None
58-
5945
@register.filter
6046
def is_ppt(value):
6147
'''

ietf/stats/views.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from ietf.stats.utils import get_aliased_affiliations, get_aliased_countries, compute_hirsch_index
4141
from ietf.ietfauth.utils import has_role
4242
from ietf.utils.response import permission_denied
43-
from ietf.utils.timezone import date_today, DEADLINE_TZINFO
43+
from ietf.utils.timezone import date_today, DEADLINE_TZINFO, RPC_TZINFO
4444

4545

4646
def stats_index(request):
@@ -625,8 +625,9 @@ def generate_canonical_names(values):
625625
type__in=["published_rfc", "new_revision"],
626626
).values_list("doc", "time").order_by("doc")
627627

628-
for doc, time in docevent_qs.iterator():
629-
doc_years[doc].add(time.year)
628+
for doc_id, time in docevent_qs.iterator():
629+
# RPC_TZINFO is used to match the timezone handling in Document.pub_date()
630+
doc_years[doc_id].add(time.astimezone(RPC_TZINFO).year)
630631

631632
person_qs = Person.objects.filter(person_filters)
632633

ietf/sync/rfceditor.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from ietf.person.models import Person
2626
from ietf.utils.log import log
2727
from ietf.utils.mail import send_mail_text
28-
from ietf.utils.timezone import datetime_from_date
28+
from ietf.utils.timezone import datetime_from_date, RPC_TZINFO
2929

3030
#QUEUE_URL = "https://www.rfc-editor.org/queue2.xml"
3131
#INDEX_URL = "https://www.rfc-editor.org/rfc/rfc-index.xml"
@@ -333,9 +333,12 @@ def extract_doc_list(parentNode, tagName):
333333

334334

335335
def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=None):
336-
"""Given parsed data from the RFC Editor index, update the documents
337-
in the database. Yields a list of change descriptions for each
338-
document, if any."""
336+
"""Given parsed data from the RFC Editor index, update the documents in the database
337+
338+
Yields a list of change descriptions for each document, if any.
339+
340+
The skip_older_than_date is a bare date, not a datetime.
341+
"""
339342

340343
errata = {}
341344
for item in errata_data:
@@ -373,7 +376,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
373376

374377
for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in index_data:
375378

376-
if skip_older_than_date and datetime_from_date(rfc_published_date) < datetime_from_date(skip_older_than_date):
379+
if skip_older_than_date and rfc_published_date < skip_older_than_date:
377380
# speed up the process by skipping old entries
378381
continue
379382

@@ -444,8 +447,16 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
444447
# unfortunately, rfc_published_date doesn't include the correct day
445448
# at the moment because the data only has month/year, so
446449
# try to deduce it
447-
d = datetime_from_date(rfc_published_date)
448-
synthesized = timezone.now()
450+
#
451+
# Note: This is in done PST8PDT to preserve compatibility with events created when
452+
# USE_TZ was False. The published_rfc event was created with a timestamp whose
453+
# server-local datetime (PST8PDT) matched the publication date from the RFC index.
454+
# When switching to USE_TZ=True, the timestamps were migrated so they still
455+
# matched the publication date in PST8PDT. When interpreting the event timestamp
456+
# as a publication date, you must treat it in the PST8PDT time zone. The
457+
# RPC_TZINFO constant in ietf.utils.timezone is defined for this purpose.
458+
d = datetime_from_date(rfc_published_date, RPC_TZINFO)
459+
synthesized = timezone.now().astimezone(RPC_TZINFO)
449460
if abs(d - synthesized) > datetime.timedelta(days=60):
450461
synthesized = d
451462
else:

0 commit comments

Comments
 (0)