Skip to content

Instantly share code, notes, and snippets.

@3lpsy
Last active August 24, 2020 23:03
Show Gist options
  • Save 3lpsy/934a9987598332a94b6f6e981fcfa2dc to your computer and use it in GitHub Desktop.
Save 3lpsy/934a9987598332a94b6f6e981fcfa2dc to your computer and use it in GitHub Desktop.
Generate an iCalendar Event (ICS) File from the Command Line
#!/usr/bin/env python3
from argparse import ArgumentParser
import sys
import pytz
from datetime import datetime, timedelta, timezone
from typing import List
try:
from icalendar import Calendar, vDatetime, Event, vCalAddress, vText
except ImportError as e:
print("[!] The icalendar pacakage is required")
sys.exit(1)
SANE_FORMAT = "%Y-%m-%d %H:%M:%S"
# TODO: validators + setters, is there no std builder?
def create_event(
summary: str,
organizer: str,
attendees: List[str],
start: vDatetime,
end: vDatetime,
location: str,
output: str,
):
cal = Calendar()
cal.add("prodid", "-//calcli//mxm.dk//")
cal.add("version", "2.0")
event = Event()
event.add("summary", summary)
event.add("dtstart", start)
event.add("dtend", end)
event["location"] = vText(location)
if ":" not in organizer:
organizer_c = vCalAddress("MAILTO:")
organizer_name = organizer
else:
organizer_c = vCalAddress("MAILTO:" + organizer.split(":")[1])
organizer_name = organizer.split(":")[0]
organizer_c.params["cn"] = vText(organizer_name)
organizer_c.params["role"] = vText("CHAIR")
event["organizer"] = organizer_c
for a in attendees:
if ":" not in a:
attendee_c = vCalAddress("MAILTO:")
attendee_name = a
else:
attendee_c = vCalAddress("MAILTO:" + a.split(":")[1])
attendee_name = a.split(":")[0]
attendee_c.params["cn"] = vText(attendee_name)
attendee_c.params["ROLE"] = vText("REQ-PARTICIPANT")
event.add("attendee", attendee_c, encode=0)
cal.add_component(event)
print("[*] Calendar Data:")
print(str(cal.to_ical().decode()).replace("\r\n", "\n").strip())
print(f"[*] Saving event to {output}")
with open(output, "wb") as f:
f.write(cal.to_ical())
if __name__ == "__main__":
parser = ArgumentParser(description="Create calendar (ics) file")
parser.add_argument(
"-o", "--output", type=str, default="event.ics", help="output path",
)
parser.add_argument(
"-s", "--summary", type=str, required=True, help="event summary"
)
parser.add_argument(
"--organizer", type=str, required=True, help="organizer in 'name:email' format",
)
parser.add_argument(
"--location", type=str, default="Remote", help="location",
)
parser.add_argument(
"-a",
"--attendee",
action="append",
help="attendee(s) in 'name:email' format to invite (can be used multiple times)",
)
parser.add_argument(
"--start",
type=str,
help="event start time (must be parseable by datetime format)",
)
parser.add_argument(
"--end", type=str, help="event end time (must be parseable by datetime format)",
)
parser.add_argument(
"-f",
"--datetime-format",
type=str,
default=SANE_FORMAT,
help="format to use to parse time arguments",
)
# TODO: date validators
args = parser.parse_args()
attendees = args.attendee or []
if args.start:
start = datetime.strptime(args.start, args.datetime_format).astimezone(
tz=timezone.utc
)
else:
_now = datetime.now(timezone.utc)
_minute_delta = 60 - _now.minute
_second_delta = 60 - _now.second
_delta = timedelta(minutes=_minute_delta, seconds=_second_delta)
start = _now + _delta
if args.end:
end = datetime.strptime(args.start, args.datetime_format).astimezone(
tz=timezone.utc
)
else:
if args.start:
_now = start
_minute_delta = 0
_second_delta = 0
else:
_now = datetime.now(timezone.utc)
_minute_delta = 60 - _now.minute
_second_delta = 60 - _now.second
_delta = timedelta(hours=1, minutes=_minute_delta, seconds=_second_delta)
end = _now + _delta
print(f"[*] Event Start: {start} UTC")
print(f"[*] Event End: {end} UTC")
_duration: timedelta = end - start
print(f"[*] Event Length: {_duration.seconds / 60} Minutes")
start = vDatetime(start)
end = vDatetime(end)
create_event(
args.summary, args.organizer, attendees, start, end, args.location, args.output
)
@3lpsy
Copy link
Author

3lpsy commented Aug 24, 2020

It's not perfect, but it works. Definitely needs more error handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment