Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Publish ICS

on:
push:
pull_request:
branches: [main]
workflow_dispatch:

permissions: {}

env:
FORCE_COLOR: 1

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v9.0.0
- run: uv run scripts/generate_ics.py site/conferences.ics
- run: |
cat > site/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<title>Python conferences calendar</title>
<h1>Python conferences calendar</h1>
<p>Subscribe to <a href="conferences.ics">conferences.ics</a>
in your calendar app to get all conferences listed in
<a href="https://github.com/python-organizers/conferences">
python-organizers/conferences</a>.</p>
EOF
- uses: actions/upload-pages-artifact@v5
with:
path: site

deploy:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ dist/
.~lock.*
# data
conferences.csv
conferences.ics
1 change: 0 additions & 1 deletion 2022.csv
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ PyCon Russia,2022-07-30,2022-07-31,"Moscow, Central Federal District, Russia",RU
EuroSciPy,2022-08-29,2022-09-02,"Basel, Basel-Stadt, Switzerland",CHE,University of Basel,2022-05-30,2022-05-30,https://www.euroscipy.org/2022,https://pretalx.com/euroscipy-2022/cfp,
PyCon Slovakia,2022-09-09,2022-09-11,"Bratislava, Bratislava, Slovakia",SVK,Faculty of Informatics and Information Technologies STU,,,https://2022.pycon.sk,,
PyBay,2022-09-10,2022-09-10,"San Francisco, California, United States of America",USA,Parklab Gardens,,,https://pybay.com,,https://pybay.com/sponsors-prospectus
PyBay,2022-09-10,2022-09-10,"San Francisco, California, USA",USA,Parklab Gardens,,,,,
PyCon Portugal,2022-09-24,2022-09-24,"Porto, Oporto, Portugal",PRT,University of Porto,2022-06-30,2022-06-03,https://2022.pycon.pt,https://2022.pycon.pt/talks/cfp,https://2022.pycon.pt/sponsors/sponsorship
SciPy Latin America,2022-09-26,2022-09-28,"Salta, Salta, Argentina",ARG,Universidad Nacional de Salta,,,https://pythoncientifico.ar,https://pythoncientifico.ar/pages/llamado-a-propuestas,https://pythoncientifico.ar/sponsors
PyCon Ghana,2022-10-13,2022-10-15,"Accra, Greater Accra, Ghana",GHA,,,,https://gh.pycon.org,https://gh.pycon.org/talks/speaking,https://gh.pycon.org/support-us
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ Each CSV file is [compatible with Google Calendar](https://support.google.com/ca
| Proposal URL | Proposal information | URL |
| Sponsorship URL | Sponsorship information | URL |

## Calendar Feed

Subscribe to <https://python-organizers.github.io/conferences/conferences.ics>
in your calendar app to get all listed conferences as all-day events, with
proposal deadlines and links in the event description. The feed is regenerated
from the CSV files on every push to `main`.

## Tests & Linting

There are tests to ensure that the files are written in the correct format
Expand Down
91 changes: 91 additions & 0 deletions scripts/generate_ics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Generate an iCalendar file from the year CSV files.

Usage: uv run scripts/generate_ics.py [output.ics]
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "icalendar==7.2.2",
# ]
# ///

import csv
import datetime as dt
import sys
from pathlib import Path

from icalendar import Calendar, Event

CALENDAR_NAME = "Python Conferences"
UID_DOMAIN = "python-organizers.github.io"


def make_event(row: dict[str, str], dtstamp: dt.datetime, seen_uids: set[str]) -> Event:
event = Event()

# Stable UIDs so subscribed calendars update events instead of
# duplicating them
slug = "".join(c if c.isalnum() else "-" for c in row["Subject"].lower())
uid = f"{row['Start Date']}-{slug}@{UID_DOMAIN}"
while uid in seen_uids:
uid = "x" + uid
seen_uids.add(uid)
event.add("uid", uid)

event.add("dtstamp", dtstamp)
event.add("dtstart", dt.date.fromisoformat(row["Start Date"]))
# DTEND is exclusive, so all-day events end the day after the last day
event.add("dtend", dt.date.fromisoformat(row["End Date"]) + dt.timedelta(days=1))
event.add("summary", row["Subject"])

if location := ", ".join(filter(None, [row["Venue"], row["Location"]])):
event.add("location", location)

description = "\n".join(
f"{label}: {value}"
for label, value in (
("Tutorial deadline", row["Tutorial Deadline"]),
("Talk deadline", row["Talk Deadline"]),
("Website", row["Website URL"]),
("Proposals", row["Proposal URL"]),
("Sponsorship", row["Sponsorship URL"]),
)
if value
)
if description:
event.add("description", description)
if row["Website URL"]:
event.add("url", row["Website URL"])

return event


def main() -> None:
output = Path(sys.argv[1] if len(sys.argv) > 1 else "conferences.ics")
repository_folder = Path(__file__).parents[1]
dtstamp = dt.datetime.now(dt.timezone.utc)

calendar = Calendar()
calendar.add("prodid", "-//python-organizers//conferences//EN")
calendar.add("version", "2.0")
calendar.add("calscale", "GREGORIAN")
calendar.add("method", "PUBLISH")
calendar.add("x-wr-calname", CALENDAR_NAME)
calendar.add("name", CALENDAR_NAME)

count = 0
seen_uids = set()
for csv_file in sorted(repository_folder.glob("20*.csv")):
with open(csv_file, newline="") as f:
for row in csv.DictReader(f):
calendar.add_component(make_event(row, dtstamp, seen_uids))
count += 1

output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(calendar.to_ical())
print(f"Wrote {count} events to {output}")


if __name__ == "__main__":
main()
Loading