diff --git a/.assets/screenshot-apple-cal.png b/.assets/screenshot-apple-cal.png new file mode 100644 index 0000000..80469d4 Binary files /dev/null and b/.assets/screenshot-apple-cal.png differ diff --git a/.assets/screenshot-google-cal.png b/.assets/screenshot-google-cal.png new file mode 100644 index 0000000..3588d22 Binary files /dev/null and b/.assets/screenshot-google-cal.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7275bb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/ diff --git a/README.md b/README.md index 5436664..d8c282a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Calendar - + @@ -23,6 +23,27 @@ Dates with a `~` prefix are estimates. +### Subscribe + +Subscribe to the calendar by adding this iCal link to your Google or Apple calendar: + +`https://raw.githubusercontent.com/paritytech/release-registry/main/releases-v1.ics` + +Google has the "From URL" option for this: + +![Google Calendar](.assets/screenshot-google-cal.png) + +Apple has the "New Calendar Subscription" option for this: + +![Apple Calendar](.assets/screenshot-apple-cal.png) + +## Automation + +Two scripts are currently in place to: + +- [update-readme.py](./update-readme.py) - updates the README.md file with the data from the releases.json file +- [ical.py](./ical.py) - generates an iCal file from the releases.json file + ## Roadmap - [ ] Double check dates and make the repo public diff --git a/ical.py b/ical.py new file mode 100644 index 0000000..27556cf --- /dev/null +++ b/ical.py @@ -0,0 +1,84 @@ +import json +from datetime import datetime, timedelta +from icalendar import Calendar, Event +import pytz + +def parse_date(date_str): + if isinstance(date_str, str): + return datetime.strptime(date_str, "%Y-%m-%d").date() + elif isinstance(date_str, dict) and 'estimated' in date_str: + return datetime.strptime(date_str['estimated'], "%Y-%m-%d").date() + return None + +def create_event(name, start_date, end_date=None, description=""): + event = Event() + event.add('summary', name) + event.add('dtstart', start_date) + if end_date: + event.add('dtend', end_date) + else: + event.add('dtend', start_date + timedelta(days=1)) # Make it an all-day event + description += "\n\nFull Calendar: https://github.com/paritytech/release-registry?tab=readme-ov-file#calendar" + event.add('description', description) + return event + +def generate_ical(data): + cal = Calendar() + cal.add('prodid', '-//Polkadot SDK Release Calendar//EN') + cal.add('version', '2.0') + + sdk_data = data['Polkadot SDK'] + + for release in sdk_data['releases']: + release_name = release['name'] + release_state = release['state'] + + # Release cutoff event + cutoff_date = parse_date(release['cutoff']) + if cutoff_date: + event = create_event(f"{release_name} Cutoff", cutoff_date, description=f"Cutoff for {release_name} ({release_state})") + cal.add_component(event) + + # Release publish event + publish_date = parse_date(release['publish'].get('when') or release['publish'].get('estimated')) + if publish_date: + event = create_event(f"{release_name} Release", publish_date, description=f"Release of {release_name} ({release_state})") + cal.add_component(event) + + # End of life event + eol_date = parse_date(release.get('endOfLife')) + if eol_date: + event = create_event(f"{release_name} End of Life", eol_date, description=f"End of Life for {release_name}") + cal.add_component(event) + + # Patch events + for patch in release.get('patches', []): + patch_name = patch['name'] + patch_state = patch['state'] + + # Patch cutoff event + patch_cutoff_date = parse_date(patch['cutoff']) + if patch_cutoff_date: + event = create_event(f"{patch_name} Cutoff", patch_cutoff_date, description=f"Cutoff for {patch_name} ({patch_state})") + cal.add_component(event) + + # Patch publish event + patch_publish_date = parse_date(patch['publish'].get('when') or patch['publish'].get('estimated')) + if patch_publish_date: + event = create_event(f"{patch_name} Release", patch_publish_date, description=f"Release of {patch_name} ({patch_state})") + cal.add_component(event) + + return cal.to_ical() + +# Load JSON data +with open('releases-v1.json', 'r') as f: + data = json.load(f) + +# Generate iCal +ical_data = generate_ical(data) + +# Write to file +with open('releases-v1.ics', 'wb') as f: + f.write(ical_data) + +print("iCal file 'releases-v1.ics' has been generated.") diff --git a/releases-v1.ics b/releases-v1.ics new file mode 100644 index 0000000..091dd68 --- /dev/null +++ b/releases-v1.ics @@ -0,0 +1,88 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Polkadot SDK Release Calendar//EN +BEGIN:VEVENT +SUMMARY:stable2407 Cutoff +DTSTART;VALUE=DATE:20240429 +DTEND;VALUE=DATE:20240430 +DESCRIPTION:Cutoff for stable2407 (maintained)\n\nFull Calendar: https://g + ithub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407 Release +DTSTART;VALUE=DATE:20240429 +DTEND;VALUE=DATE:20240430 +DESCRIPTION:Release of stable2407 (maintained)\n\nFull Calendar: https://g + ithub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407 End of Life +DTSTART;VALUE=DATE:20250429 +DTEND;VALUE=DATE:20250430 +DESCRIPTION:End of Life for stable2407\n\nFull Calendar: https://github.co + m/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-1 Cutoff +DTSTART;VALUE=DATE:20240814 +DTEND;VALUE=DATE:20240815 +DESCRIPTION:Cutoff for stable2407-1 (maintained)\n\nFull Calendar: https:/ + /github.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-1 Release +DTSTART;VALUE=DATE:20240815 +DTEND;VALUE=DATE:20240816 +DESCRIPTION:Release of stable2407-1 (maintained)\n\nFull Calendar: https:/ + /github.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-2 Cutoff +DTSTART;VALUE=DATE:20240828 +DTEND;VALUE=DATE:20240829 +DESCRIPTION:Cutoff for stable2407-2 (planned)\n\nFull Calendar: https://gi + thub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-2 Release +DTSTART;VALUE=DATE:20240902 +DTEND;VALUE=DATE:20240903 +DESCRIPTION:Release of stable2407-2 (planned)\n\nFull Calendar: https://gi + thub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-3 Cutoff +DTSTART;VALUE=DATE:20240911 +DTEND;VALUE=DATE:20240912 +DESCRIPTION:Cutoff for stable2407-3 (planned)\n\nFull Calendar: https://gi + thub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2407-3 Release +DTSTART;VALUE=DATE:20240916 +DTEND;VALUE=DATE:20240917 +DESCRIPTION:Release of stable2407-3 (planned)\n\nFull Calendar: https://gi + thub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2410 Cutoff +DTSTART;VALUE=DATE:20240902 +DTEND;VALUE=DATE:20240903 +DESCRIPTION:Cutoff for stable2410 (planned)\n\nFull Calendar: https://gith + ub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2410 Release +DTSTART;VALUE=DATE:20240925 +DTEND;VALUE=DATE:20240926 +DESCRIPTION:Release of stable2410 (planned)\n\nFull Calendar: https://gith + ub.com/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:stable2410 End of Life +DTSTART;VALUE=DATE:20250925 +DTEND;VALUE=DATE:20250926 +DESCRIPTION:End of Life for stable2410\n\nFull Calendar: https://github.co + m/paritytech/release-registry?tab=readme-ov-file#calendar +END:VEVENT +END:VCALENDAR diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e650896 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +icalendar==5.0.13 +python-dateutil==2.9.0.post0 +pytz==2024.1 +six==1.16.0 diff --git a/json-to-md.py b/update-readme.py similarity index 98% rename from json-to-md.py rename to update-readme.py index 31ce9d3..287c3c7 100644 --- a/json-to-md.py +++ b/update-readme.py @@ -1,3 +1,11 @@ +""" + +Update the README.md with: + +python3 update-readme.py + +""" + import json import re from typing import Dict, Any, List