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/community/monthly-meeting.rst b/docs/community/monthly-meeting.rst index 256db4f..6376dd7 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 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 `_. @@ -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..5ba170c 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"] @@ -68,6 +74,10 @@ 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: + r"/docs-community-meetings.ics", ] # A list of document names to exclude from linkcheck diff --git a/docs/tools/meeting_dates.py b/docs/tools/meeting_dates.py new file mode 100644 index 0000000..ed4f649 --- /dev/null +++ b/docs/tools/meeting_dates.py @@ -0,0 +1,109 @@ +"""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, 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 > 12: + month = 1 + year += 1 + 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(), 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" + + 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 + + lines = [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + "PRODID:-//Python Docs Community//Meeting Dates//EN", + ] + 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", + 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 + + with open(os.path.join(app.outdir, "docs-community-meetings.ics"), "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}