Created
August 30, 2026 15:04
-
-
Save michaeluhl/ab2655429999bee0706da2a555ea76a3 to your computer and use it in GitHub Desktop.
Small Script to Filter iCal Events by Date
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /// script | |
| # requires-python = ">=3.13" | |
| # dependencies = [ | |
| # "icalendar>=7.2.2", | |
| # "requests>=2.34.2" | |
| # ] | |
| # /// | |
| from argparse import ArgumentParser, Namespace | |
| from contextlib import AbstractContextManager, nullcontext | |
| import datetime as dt | |
| from io import StringIO | |
| from pathlib import Path | |
| from sys import stdin, stdout | |
| from zoneinfo import ZoneInfo | |
| import icalendar | |
| import requests | |
| def opener(uri: str) -> AbstractContextManager: | |
| if uri == "-": | |
| return nullcontext(stdin) | |
| url = requests.utils.urlparse(uri) | |
| if url.scheme in ("http", "https"): | |
| return StringIO(requests.get(uri).content.decode()) | |
| elif url.scheme == "file": | |
| return Path.from_uri(uri).open("rt") | |
| else: | |
| return Path(uri).open("rt") | |
| def ensure_datetime(input: dt.date | dt.datetime) -> dt.datetime: | |
| return ( | |
| input | |
| if isinstance(input, dt.datetime) | |
| else dt.datetime.combine(input, time=dt.time(tzinfo=ZoneInfo("UTC"))) | |
| ) | |
| def update_zones(options: Namespace): | |
| z = ( | |
| ZoneInfo(options.timezone) | |
| if options.timezone | |
| else dt.datetime.now().astimezone().tzinfo | |
| ) | |
| if options.start_after: | |
| options.start_after = options.start_after.replace(tzinfo=z) | |
| if options.start_before: | |
| options.start_before = options.start_before.replace(tzinfo=z) | |
| def main(options: Namespace) -> None: | |
| update_zones(options) | |
| with opener(options.SOURCE) as input: | |
| cal: icalendar.Calendar = icalendar.Calendar.from_ical(input.read()) | |
| filtered: icalendar.Calendar = icalendar.Calendar.new() | |
| for event in cal.events: | |
| if ( | |
| options.start_after | |
| and ensure_datetime(event.start) < options.start_after | |
| ): | |
| continue | |
| if ( | |
| options.start_before | |
| and ensure_datetime(event.start) > options.start_before | |
| ): | |
| continue | |
| filtered.add_component(event) | |
| stdout.write(filtered.to_ical().decode("UTF8")) | |
| if __name__ == "__main__": | |
| parser = ArgumentParser(description="A script to filter iCalendar events") | |
| parser.add_argument("SOURCE", type=str, help="Source (a file or URL)") | |
| parser.add_argument( | |
| "-a", | |
| "--start-after", | |
| type=dt.datetime.fromisoformat, | |
| default=None, | |
| help="Include only events with start dates after the specified value (ISO 8601 format)", | |
| ) | |
| parser.add_argument( | |
| "-b", | |
| "--start-before", | |
| type=dt.datetime.fromisoformat, | |
| default=None, | |
| help="Include only events with start dates before the specified value (ISO 8601 format)", | |
| ) | |
| parser.add_argument( | |
| "-z", | |
| "--timezone", | |
| type=str, | |
| default=None, | |
| help="Timezone to use for date comparisons, defaults to local", | |
| ) | |
| main(parser.parse_args()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment