Skip to content

Commit 37cb46b

Browse files
Add extension to generate a table of future meeting dates and times (#186)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent b080c57 commit 37cb46b

4 files changed

Lines changed: 134 additions & 9 deletions

File tree

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 2
33
build:
44
os: ubuntu-22.04
55
tools:
6-
python: "3.11"
6+
python: "3.14"
77

88
python:
99
install:

docs/community/monthly-meeting.rst

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@ Documentation Community Monthly Meetings
55

66
Monthly meetings are held on the first Tuesday of the month
77
on the `Discord server <https://discord.gg/sMWqvzXvde>`_ and last one hour.
8-
Check the agenda or Discourse announcement to verify the start time.
8+
Upcoming meeting dates and times:
99

10-
+-----------------+--------------+--------------+
11-
| Period | Even months | Odd months |
12-
+=================+==============+==============+
13-
| April-October | 19:00 UTC | 16:00 UTC |
14-
+-----------------+--------------+--------------+
15-
| November-March | 20:00 UTC | 17:00 UTC |
16-
+-----------------+--------------+--------------+
10+
.. meeting-dates::
11+
12+
`Download iCalendar file </docs-community-meetings.ics>`_
1713

1814
The agenda and later the meeting minutes are written in the `HackMD <https://hackmd.io/@encukou/pydocswg1>`_.
1915
To edit notes, click the “pencil” or “split view” button on the `HackMD Document <https://hackmd.io/@encukou/pydocswg1>`_.
@@ -27,6 +23,16 @@ By participating in meetings, you are agreeing to abide by and uphold the
2723
`PSF Code of Conduct <https://policies.python.org/python.org/code-of-conduct/>`_.
2824
Please take a second to read through it!
2925

26+
Meetings follow the pattern:
27+
28+
+-----------------+--------------+--------------+
29+
| Period | Even months | Odd months |
30+
+=================+==============+==============+
31+
| April-October | 19:00 UTC | 16:00 UTC |
32+
+-----------------+--------------+--------------+
33+
| November-March | 20:00 UTC | 17:00 UTC |
34+
+-----------------+--------------+--------------+
35+
3036

3137
Minutes template
3238
----------------

docs/conf.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
1212

1313
# A list of strings that are module names of Sphinx extensions
14+
import os
15+
import sys
16+
17+
sys.path.append(os.path.abspath("tools/"))
18+
1419
extensions = [
1520
"sphinx_copybutton",
1621
"sphinx.ext.intersphinx",
1722
"myst_parser",
23+
"meeting_dates",
1824
]
1925

2026
myst_enable_extensions = ["linkify"]
@@ -68,6 +74,10 @@
6874
r"https://plausible.io/docs.python.org",
6975
r"https://plausible.io/packaging.python.org",
7076
r"https://us.pycon.org/2024/registration/category/4",
77+
# Have redirects:
78+
r"https://arewemeetingyet.com/.*",
79+
# Generated at build time:
80+
r"/docs-community-meetings.ics",
7181
]
7282

7383
# A list of document names to exclude from linkcheck

docs/tools/meeting_dates.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Sphinx extension to generate a list of upcoming meeting dates."""
2+
3+
import datetime as dt
4+
import os
5+
6+
from docutils import nodes
7+
from sphinx.util.docutils import SphinxDirective
8+
9+
10+
def utc_hour(date):
11+
if 4 <= date.month <= 10:
12+
# Daylight saving time in Europe and the US
13+
return 19 if date.month % 2 == 0 else 16
14+
else:
15+
# Winter time in Europe and the US
16+
return 20 if date.month % 2 == 0 else 17
17+
18+
19+
def first_tuesday(year, month):
20+
first = dt.date(year, month, 1)
21+
days_ahead = (1 - first.weekday()) % 7
22+
return first + dt.timedelta(days=days_ahead)
23+
24+
25+
def upcoming_meetings(today, count):
26+
meetings = []
27+
year, month = today.year, today.month
28+
while len(meetings) < count:
29+
meeting_date = first_tuesday(year, month)
30+
if meeting_date >= today:
31+
meetings.append((meeting_date, utc_hour(meeting_date)))
32+
month += 1
33+
if month > 12:
34+
month = 1
35+
year += 1
36+
return meetings
37+
38+
39+
def past_meetings(today, count):
40+
meetings = []
41+
year, month = today.year, today.month
42+
while len(meetings) < count:
43+
meeting_date = first_tuesday(year, month)
44+
if meeting_date < today:
45+
meetings.append((meeting_date, utc_hour(meeting_date)))
46+
month -= 1
47+
if month < 1:
48+
month = 12
49+
year -= 1
50+
meetings.reverse()
51+
return meetings
52+
53+
54+
class MeetingDatesDirective(SphinxDirective):
55+
has_content = False
56+
57+
def run(self):
58+
bullets = nodes.bullet_list()
59+
for date, hour in upcoming_meetings(dt.date.today(), 6):
60+
item = nodes.list_item()
61+
text = f"{date.strftime('%B %d, %Y')} - {hour:02d}:00 UTC"
62+
url = f"https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting"
63+
64+
paragraph = nodes.paragraph()
65+
ref = nodes.reference("", text, refuri=url)
66+
paragraph += ref
67+
item += paragraph
68+
bullets += item
69+
70+
return [bullets]
71+
72+
73+
def generate_ics(app, exception):
74+
if exception:
75+
return
76+
77+
lines = [
78+
"BEGIN:VCALENDAR",
79+
"VERSION:2.0",
80+
"PRODID:-//Python Docs WG//Meeting dates//EN",
81+
"X-WR-CALDESC:Python Docs WG meetings from https://docs-community.readthedocs.io/",
82+
"X-WR-CALNAME:Python Docs WG meetings",
83+
]
84+
today = dt.date.today()
85+
meetings = past_meetings(today, 12) + upcoming_meetings(today, 12)
86+
for date, hour in meetings:
87+
start = dt.datetime(date.year, date.month, date.day, hour, 0, 0)
88+
end = start + dt.timedelta(hours=1)
89+
lines += [
90+
"BEGIN:VEVENT",
91+
f"UID:{start.strftime('%Y%m%dT%H%M%SZ')}@python-docs-community",
92+
f"DTSTAMP:{dt.datetime.now(dt.timezone.utc).strftime('%Y%m%dT%H%M%SZ')}",
93+
f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}",
94+
f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}",
95+
"SUMMARY:Python Docs WG",
96+
f"URL:https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Python Docs WG meeting",
97+
"END:VEVENT",
98+
]
99+
lines += ["END:VCALENDAR"]
100+
ics = "\r\n".join(lines) + "\r\n"
101+
102+
with open(os.path.join(app.outdir, "docs-community-meetings.ics"), "w") as f:
103+
f.write(ics)
104+
105+
106+
def setup(app):
107+
app.add_directive("meeting-dates", MeetingDatesDirective)
108+
app.connect("build-finished", generate_ics)
109+
return {"version": "1.0", "parallel_read_safe": True}

0 commit comments

Comments
 (0)