From 924a758a71aec440e1e23b920f98480159b1e729 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 25 Mar 2026 22:25:47 +0000 Subject: [PATCH 1/9] Add extension to generate a table of future meeting dates and times --- docs/community/monthly-meeting.rst | 21 ++++--- docs/conf.py | 6 ++ docs/tools/meeting_dates.py | 97 ++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 docs/tools/meeting_dates.py diff --git a/docs/community/monthly-meeting.rst b/docs/community/monthly-meeting.rst index 256db4f..3c56067 100644 --- a/docs/community/monthly-meeting.rst +++ b/docs/community/monthly-meeting.rst @@ -7,13 +7,11 @@ Monthly meetings are held on the first Tuesday of the month on the `Discord server `_ and last one hour. Check the agenda or Discourse announcement to verify the start time. -+-----------------+--------------+--------------+ -| Period | Even months | Odd months | -+=================+==============+==============+ -| April-October | 19:00 UTC | 16:00 UTC | -+-----------------+--------------+--------------+ -| November-March | 20:00 UTC | 17:00 UTC | -+-----------------+--------------+--------------+ +Upcoming meetings: + +.. meeting-dates:: + +`Download calendar (ICS) `_ The agenda and later the meeting minutes are written in the `HackMD `_. To edit notes, click the “pencil” or “split view” button on the `HackMD Document `_. @@ -27,6 +25,15 @@ By participating in meetings, you are agreeing to abide by and uphold the `PSF Code of Conduct `_. Please take a second to read through it! +Meetings follow the pattern: + ++-----------------+--------------+--------------+ +| Period | Even months | Odd months | ++=================+==============+==============+ +| April-October | 19:00 UTC | 16:00 UTC | ++-----------------+--------------+--------------+ +| November-March | 20:00 UTC | 17:00 UTC | ++-----------------+--------------+--------------+ Minutes template ---------------- diff --git a/docs/conf.py b/docs/conf.py index 10a738a..8536330 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,10 +11,16 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # A list of strings that are module names of Sphinx extensions +import os +import sys + +sys.path.append(os.path.abspath("tools/")) + extensions = [ "sphinx_copybutton", "sphinx.ext.intersphinx", "myst_parser", + "meeting_dates", ] myst_enable_extensions = ["linkify"] diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py new file mode 100644 index 0000000..5cb9e10 --- /dev/null +++ b/docs/tools/meeting_dates.py @@ -0,0 +1,97 @@ +"""Sphinx extension to generate a list of upcoming meeting dates.""" + +import datetime as dt +import os + +from docutils import nodes +from sphinx.util.docutils import SphinxDirective + + +def utc_hour(date): + if 4 <= date.month <= 10: + # Daylight saving time in Europe and the US + return 19 if date.month % 2 == 0 else 16 + else: + # Winter time in Europe and the US + return 20 if date.month % 2 == 0 else 17 + + +def first_tuesday(year, month): + first = dt.date(year, month, 1) + days_ahead = (1 - first.weekday()) % 7 + return first + dt.timedelta(days=days_ahead) + + +def upcoming_meetings(today): + meetings = [] + year, month = today.year, today.month + while len(meetings) < 6: # Max of six meeting entries + meeting_date = first_tuesday(year, month) + if meeting_date >= today: + meetings.append((meeting_date, utc_hour(meeting_date))) + month += 1 + if month > 12: + month = 1 + year += 1 + return meetings + + +class MeetingDatesDirective(SphinxDirective): + has_content = False + + def run(self): + today = dt.date.today() + meetings = upcoming_meetings(today) + self.env.meeting_dates = meetings + + bullets = nodes.bullet_list() + for date, hour in meetings: + item = nodes.list_item() + text = f"{date.strftime('%B %d, %Y')} - {hour:02d}:00 UTC" + url = f"https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting" + + paragraph = nodes.paragraph() + ref = nodes.reference("", text, refuri=url) + paragraph += ref + item += paragraph + bullets += item + + return [bullets] + + +def generate_ics(app, exception): + if exception: + return + + meetings = app.env.meeting_dates + + lines = [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + "PRODID:-//Python Docs Community//Meeting Dates//EN", + ] + for date, hour in meetings: + start = dt.datetime(date.year, date.month, date.day, hour, 0, 0) + end = start + dt.timedelta(hours=1) + lines += [ + "BEGIN:VEVENT", + f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}", + f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}", + "SUMMARY:Docs Community Meeting", + f"URL:https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting", + "END:VEVENT", + ] + lines += ["END:VCALENDAR"] + ics = ( + "\r\n".join(lines) + "\r\n" + ) # Required by spec for some reason: https://datatracker.ietf.org/doc/html/rfc5545#section-3.1 + + path = os.path.join(app.outdir, "docs-community-meetings.ics") + with open(path, "w") as f: + f.write(ics) + + +def setup(app): + app.add_directive("meeting-dates", MeetingDatesDirective) + app.connect("build-finished", generate_ics) + return {"version": "1.0", "parallel_read_safe": True} From fc1731c8fbc025bb92c557cf81741f9302b53f3f Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 25 Mar 2026 22:29:50 +0000 Subject: [PATCH 2/9] refatcior a 'lil --- docs/tools/meeting_dates.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index 5cb9e10..d857505 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -41,11 +41,10 @@ class MeetingDatesDirective(SphinxDirective): def run(self): today = dt.date.today() - meetings = upcoming_meetings(today) - self.env.meeting_dates = meetings + self.env.meeting_dates = upcoming_meetings(today) bullets = nodes.bullet_list() - for date, hour in meetings: + for date, hour in self.env.meeting_dates: item = nodes.list_item() text = f"{date.strftime('%B %d, %Y')} - {hour:02d}:00 UTC" url = f"https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting" @@ -63,14 +62,12 @@ def generate_ics(app, exception): if exception: return - meetings = app.env.meeting_dates - lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Python Docs Community//Meeting Dates//EN", ] - for date, hour in meetings: + for date, hour in app.env.meeting_dates: start = dt.datetime(date.year, date.month, date.day, hour, 0, 0) end = start + dt.timedelta(hours=1) lines += [ @@ -86,8 +83,7 @@ def generate_ics(app, exception): "\r\n".join(lines) + "\r\n" ) # Required by spec for some reason: https://datatracker.ietf.org/doc/html/rfc5545#section-3.1 - path = os.path.join(app.outdir, "docs-community-meetings.ics") - with open(path, "w") as f: + with open(os.path.join(app.outdir, "docs-community-meetings.ics"), "w") as f: f.write(ics) From 14e256fe4d929ea05005f0d496899d2dbe56a396 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 25 Mar 2026 22:31:59 +0000 Subject: [PATCH 3/9] Fix when cached build exists --- docs/tools/meeting_dates.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index d857505..ecaa023 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -40,11 +40,8 @@ class MeetingDatesDirective(SphinxDirective): has_content = False def run(self): - today = dt.date.today() - self.env.meeting_dates = upcoming_meetings(today) - bullets = nodes.bullet_list() - for date, hour in self.env.meeting_dates: + for date, hour in upcoming_meetings(dt.date.today()): item = nodes.list_item() text = f"{date.strftime('%B %d, %Y')} - {hour:02d}:00 UTC" url = f"https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting" @@ -67,7 +64,7 @@ def generate_ics(app, exception): "VERSION:2.0", "PRODID:-//Python Docs Community//Meeting Dates//EN", ] - for date, hour in app.env.meeting_dates: + for date, hour in upcoming_meetings(dt.date.today()): start = dt.datetime(date.year, date.month, date.day, hour, 0, 0) end = start + dt.timedelta(hours=1) lines += [ From 5176a477ce41558e2d32168a9c36c2f7561bed7e Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 25 Mar 2026 22:37:49 +0000 Subject: [PATCH 4/9] Linkcheck. --- docs/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 8536330..c05ce88 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,6 +74,9 @@ r"https://plausible.io/docs.python.org", r"https://plausible.io/packaging.python.org", r"https://us.pycon.org/2024/registration/category/4", + r"https://arewemeetingyet.com/.*", + # Generated at build time + r"/docs-community-meetings.ics", ] # A list of document names to exclude from linkcheck From f3ce972d37a8f1c0614c05390cafa54fb346d9ff Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 26 Mar 2026 16:12:48 +0000 Subject: [PATCH 5/9] Hugo's review (bar typing...) --- docs/community/monthly-meeting.rst | 2 +- docs/conf.py | 3 ++- docs/tools/meeting_dates.py | 27 +++++++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/community/monthly-meeting.rst b/docs/community/monthly-meeting.rst index 3c56067..6376dd7 100644 --- a/docs/community/monthly-meeting.rst +++ b/docs/community/monthly-meeting.rst @@ -11,7 +11,7 @@ Upcoming meetings: .. meeting-dates:: -`Download calendar (ICS) `_ +`Download iCalendar file `_ The agenda and later the meeting minutes are written in the `HackMD `_. To edit notes, click the “pencil” or “split view” button on the `HackMD Document `_. diff --git a/docs/conf.py b/docs/conf.py index c05ce88..5ba170c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,8 +74,9 @@ r"https://plausible.io/docs.python.org", r"https://plausible.io/packaging.python.org", r"https://us.pycon.org/2024/registration/category/4", + # Have redirects: r"https://arewemeetingyet.com/.*", - # Generated at build time + # Generated at build time: r"/docs-community-meetings.ics", ] diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index ecaa023..5dfd6a7 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -22,10 +22,10 @@ def first_tuesday(year, month): return first + dt.timedelta(days=days_ahead) -def upcoming_meetings(today): +def upcoming_meetings(today, count): meetings = [] year, month = today.year, today.month - while len(meetings) < 6: # Max of six meeting entries + while len(meetings) < count: meeting_date = first_tuesday(year, month) if meeting_date >= today: meetings.append((meeting_date, utc_hour(meeting_date))) @@ -36,12 +36,27 @@ def upcoming_meetings(today): return meetings +def past_meetings(today, count): + meetings = [] + year, month = today.year, today.month + while len(meetings) < count: + meeting_date = first_tuesday(year, month) + if meeting_date < today: + meetings.append((meeting_date, utc_hour(meeting_date))) + month -= 1 + if month < 1: + month = 12 + year -= 1 + meetings.reverse() + return meetings + + class MeetingDatesDirective(SphinxDirective): has_content = False def run(self): bullets = nodes.bullet_list() - for date, hour in upcoming_meetings(dt.date.today()): + for date, hour in upcoming_meetings(dt.date.today(), 6): item = nodes.list_item() text = f"{date.strftime('%B %d, %Y')} - {hour:02d}:00 UTC" url = f"https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting" @@ -64,11 +79,15 @@ def generate_ics(app, exception): "VERSION:2.0", "PRODID:-//Python Docs Community//Meeting Dates//EN", ] - for date, hour in upcoming_meetings(dt.date.today()): + today = dt.date.today() + meetings = past_meetings(today, 12) + upcoming_meetings(today, 12) + for date, hour in meetings: start = dt.datetime(date.year, date.month, date.day, hour, 0, 0) end = start + dt.timedelta(hours=1) lines += [ "BEGIN:VEVENT", + f"UID:{start.strftime('%Y%m%dT%H%M%SZ')}@python-docs-community", + f"DTSTAMP:{dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")}", f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}", f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}", "SUMMARY:Docs Community Meeting", From 5bbe98835da5fffc37c7b028c7df604538e16071 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 26 Mar 2026 16:19:03 +0000 Subject: [PATCH 6/9] RTD builds with 3.11.12...? --- .readthedocs.yaml | 2 +- docs/tools/meeting_dates.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 0d7e650..f4cf7b3 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.14" python: install: diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index 5dfd6a7..ed4f649 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -87,7 +87,7 @@ def generate_ics(app, exception): lines += [ "BEGIN:VEVENT", f"UID:{start.strftime('%Y%m%dT%H%M%SZ')}@python-docs-community", - f"DTSTAMP:{dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")}", + f"DTSTAMP:{dt.datetime.now(dt.timezone.utc).strftime('%Y%m%dT%H%M%SZ')}", f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}", f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}", "SUMMARY:Docs Community Meeting", From 7633f95e159a6b41282e438e525d819f3e35595f Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 26 Mar 2026 22:07:10 +0000 Subject: [PATCH 7/9] Hugo's review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/tools/meeting_dates.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index ed4f649..90c2c44 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -77,7 +77,9 @@ def generate_ics(app, exception): lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", - "PRODID:-//Python Docs Community//Meeting Dates//EN", + "PRODID:-//Python Docs Community//Meeting dates//EN", + "X-WR-CALDESC:Python docs community meeting dates from https://docs-community.readthedocs.io/", + "X-WR-CALNAME:Python docs community meeting dates", ] today = dt.date.today() meetings = past_meetings(today, 12) + upcoming_meetings(today, 12) @@ -95,9 +97,7 @@ def generate_ics(app, exception): "END:VEVENT", ] lines += ["END:VCALENDAR"] - ics = ( - "\r\n".join(lines) + "\r\n" - ) # Required by spec for some reason: https://datatracker.ietf.org/doc/html/rfc5545#section-3.1 + ics = "\r\n".join(lines) + "\r\n" with open(os.path.join(app.outdir, "docs-community-meetings.ics"), "w") as f: f.write(ics) From 01cb26bb11ffcb6f9cc59c7c5fca141702b20de0 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 26 Mar 2026 22:08:39 +0000 Subject: [PATCH 8/9] Doc update --- docs/community/monthly-meeting.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/community/monthly-meeting.rst b/docs/community/monthly-meeting.rst index 6376dd7..92ca6e9 100644 --- a/docs/community/monthly-meeting.rst +++ b/docs/community/monthly-meeting.rst @@ -5,9 +5,7 @@ Documentation Community Monthly Meetings Monthly meetings are held on the first Tuesday of the month on the `Discord server `_ and last one hour. -Check the agenda or Discourse announcement to verify the start time. - -Upcoming meetings: +Upcoming meeting dates and times: .. meeting-dates:: @@ -35,6 +33,7 @@ Meetings follow the pattern: | November-March | 20:00 UTC | 17:00 UTC | +-----------------+--------------+--------------+ + Minutes template ---------------- From 11d219b71d15f8c04de7612e485351f25ab3ea2a Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:43:19 +0000 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/tools/meeting_dates.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py index 90c2c44..b0be525 100644 --- a/docs/tools/meeting_dates.py +++ b/docs/tools/meeting_dates.py @@ -77,9 +77,9 @@ def generate_ics(app, exception): lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", - "PRODID:-//Python Docs Community//Meeting dates//EN", - "X-WR-CALDESC:Python docs community meeting dates from https://docs-community.readthedocs.io/", - "X-WR-CALNAME:Python docs community meeting dates", + "PRODID:-//Python Docs WG//Meeting dates//EN", + "X-WR-CALDESC:Python Docs WG meetings from https://docs-community.readthedocs.io/", + "X-WR-CALNAME:Python Docs WG meetings", ] today = dt.date.today() meetings = past_meetings(today, 12) + upcoming_meetings(today, 12) @@ -92,8 +92,8 @@ def generate_ics(app, exception): f"DTSTAMP:{dt.datetime.now(dt.timezone.utc).strftime('%Y%m%dT%H%M%SZ')}", f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}", f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}", - "SUMMARY:Docs Community Meeting", - f"URL:https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Docs Community Meeting", + "SUMMARY:Python Docs WG", + f"URL:https://arewemeetingyet.com/UTC/{date.isoformat()}/{hour}:00/Python Docs WG meeting", "END:VEVENT", ] lines += ["END:VCALENDAR"]