Building an iCalendar Invitation App with Python

Umair Akbar
6 min readNov 3, 2024

--

Python talking to a human

As a developer, I often find myself needing to create and send calendar invitations programmatically. Whether it’s for scheduling meetings, webinars, or events, having an application that can handle iCalendar invites seamlessly is invaluable. In this blog post, I’ll walk you through the journey of building a comprehensive iCalendar invitation application using Python. We’ll delve into the technical aspects, challenges faced, and how we integrated features like time zone handling and calendar API integrations.

Table of Contents

  1. Introduction
  2. Initial Requirements
  3. Choosing the Right Tools
  4. Building the GUI with Tkinter
  5. Creating iCalendar Files
  6. Enhancing Time Zone Handling
  7. Adding Recurrence and Reminders
  8. Integrating with Google Calendar API
  9. Integrating with Microsoft Outlook Calendar API
  10. Ensuring Compatibility with iOS Devices
  11. Final Code and Usage Instructions
  12. Closing Remarks

Introduction

Calendar invitations are a staple in both professional and personal scheduling. The iCalendar format (.ics files) is widely accepted across various platforms like Google Calendar, Outlook, and Apple Calendar. However, creating and managing these invites programmatically can be cumbersome.

I set out to build a Python application with a user-friendly GUI that allows users to create and send iCalendar invitations effortlessly. The goal was to include advanced features like time zone support, recurrence rules, reminders, and direct integration with popular calendar services.

Initial Requirements

Before diving into coding, I outlined the key requirements for the application:

  • User-Friendly GUI: Utilize Tkinter to create an intuitive interface.
  • Event Details Input: Allow users to input event title, start/end date and time, location, and description.
  • Time Zone Support: Enable selection of time zones to handle global scheduling.
  • Recurrence Rules: Support recurring events (daily, weekly, monthly, yearly).
  • Reminders: Allow setting multiple reminders before the event.
  • Attendees Management: Facilitate adding attendees’ email addresses.
  • Email Integration: Send invitations via email with the .ics file attached.
  • Calendar API Integration: Directly add events to Google Calendar and Outlook Calendar.
  • Compatibility: Ensure the invitations work seamlessly across devices, including iOS.

Choosing the Right Tools

To meet these requirements, I chose the following tools and libraries:

  • Tkinter: The standard Python interface to the Tk GUI toolkit, perfect for building the application interface.
  • icalendar: A powerful library to create and manipulate iCalendar files.
  • pytz: Handles accurate and cross-platform time zone calculations.
  • tkcalendar: Provides date picker widgets for Tkinter.
  • smtplib and email modules: For sending emails with attachments.
  • Google Calendar API: Allows integration with Google Calendar.
  • Microsoft Graph API: Enables integration with Outlook Calendar.
  • msal (Microsoft Authentication Library): For handling authentication with Microsoft services.

Building the GUI with Tkinter

The first step was to design the GUI using Tkinter. I structured the interface using tabs to separate event details, email settings, and application settings.

# Creating the main window and notebook tabs
self.root = tk.Tk()
self.notebook = ttk.Notebook(root)
self.event_tab = ttk.Frame(self.notebook)
self.email_tab = ttk.Frame(self.notebook)
self.settings_tab = ttk.Frame(self.notebook)

Event Details Tab

This tab includes input fields for:

  • Event Title
  • Start and End Date/Time (using DateEntry and Combobox)
  • Time Zone Selection (pytz.all_timezones)
  • Location
  • Description (ScrolledText)
  • Recurrence Options
  • Reminders
  • Attendees’ Emails
  • Organizer’s Email

Email Settings Tab

Allows customization of:

  • Email Subject
  • Email Body

Application Settings Tab

Includes:

  • SMTP Server Details
  • Email Password (for sending emails)

Creating iCalendar Files

With the GUI in place, the next task was to generate .ics files based on user input.

from icalendar import Calendar, Event, Alarm, vCalAddress, vText
cal = Calendar()
cal.add('prodid', '-//Custom iCalendar Event//example.com//')
cal.add('version', '2.0')
cal.add('method', 'REQUEST')
event = Event()
event.add('summary', title)
event.add('dtstart', start_dt)
event.add('dtend', end_dt)
event.add('location', vText(location))
event.add('description', description)

Handling Attendees and Organizer

# Organizer
organizer = vCalAddress(f"MAILTO:{organizer_email}")
organizer.params['cn'] = vText('Organizer')
event['organizer'] = organizer
# Attendees
for email in attendees_emails.split(','):
attendee = vCalAddress(f"MAILTO:{email.strip()}")
attendee.params['cn'] = vText(email.strip())
attendee.params['ROLE'] = vText('REQ-PARTICIPANT')
event.add('attendee', attendee)

Enhancing Time Zone Handling

Time zone support is crucial for events involving participants from different regions. Using pytz, I ensured accurate time zone localization.

import pytz
tz = pytz.timezone(timezone_str)
start_dt = datetime.combine(start_date, datetime.strptime(start_time, '%H:%M').time())
start_dt = tz.localize(start_dt)
end_dt = datetime.combine(end_date, datetime.strptime(end_time, '%H:%M').time())
end_dt = tz.localize(end_dt)

Creating VTIMEZONE Component

To ensure compatibility with iOS devices, we need to include the VTIMEZONE component in the .ics file.

def create_timezone_component(self, tz):
timezone = Timezone()
timezone.add('tzid', tz.zone)
timezone.add('x-lic-location', tz.zone)
# Handle standard and daylight transitions...
return timezone
# Adding to calendar
timezone_component = self.create_timezone_component(tz)
cal.add_component(timezone_component)

Adding Recurrence and Reminders

Recurrence Rules

Supporting recurring events required adding RRULEs to the event.

if recurrence != 'None':
rrule = {'freq': recurrence.upper()}
if until_date:
until_dt = datetime.combine(until_date, datetime.max.time())
until_dt = tz.localize(until_dt)
rrule['until'] = until_dt
event.add('rrule', rrule)

Multiple Reminders

Users can enter multiple reminders, each on a new line.

for reminder in reminders:
minutes_before = int(reminder)
alarm = Alarm()
alarm.add('ACTION', 'DISPLAY')
alarm.add('DESCRIPTION', f"Reminder for event {title}")
alarm.add('TRIGGER', timedelta(minutes=-minutes_before))
event.add_component(alarm)

Integrating with Google Calendar API

To allow users to add events directly to their Google Calendar, I integrated the Google Calendar API.

Setting Up Credentials

  • Enable the Google Calendar API in Google Cloud Console.
  • Download credentials.json and place it in the application directory.

Authentication and Event Creation

from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
def add_to_google_calendar(self):
scopes = ['https://www.googleapis.com/auth/calendar.events']
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', scopes=scopes)
self.google_credentials = flow.run_local_server(port=0)
service = build('calendar', 'v3', credentials=self.google_credentials)
event = self.create_google_calendar_event()
created_event = service.events().insert(calendarId='primary', body=event).execute()
messagebox.showinfo("Success", f"Event created: {created_event.get('htmlLink')}")

Constructing the Event Payload

def create_google_calendar_event(self):
event = {
'summary': title,
'location': location,
'description': description,
'start': {
'dateTime': start_datetime.isoformat(),
'timeZone': timezone_str,
},
'end': {
'dateTime': end_datetime.isoformat(),
'timeZone': timezone_str,
},
'attendees': [{'email': email} for email in attendees_emails],
'reminders': {
'useDefault': False,
'overrides': [{'method': 'popup', 'minutes': int(rem)} for rem in reminders],
},
}
# Add recurrence if applicable
return event

Integrating with Microsoft Outlook Calendar API

Similarly, integrating with Outlook Calendar required using the Microsoft Graph API.

Setting Up Credentials

  • Register the application in Azure Portal.
  • Obtain the client ID and set the appropriate permissions.

Authentication and Event Creation

import msal
def add_to_outlook_calendar(self):
client_id = 'your_client_id_here' # Replace with your client ID
authority_url = 'https://login.microsoftonline.com/common'
scopes = ['https://graph.microsoft.com/Calendars.ReadWrite']
app = msal.PublicClientApplication(client_id, authority=authority_url)
result = app.acquire_token_interactive(scopes)
if 'access_token' in result:
self.microsoft_access_token = result['access_token']
self.create_outlook_calendar_event()

Constructing the Event Payload

def create_outlook_calendar_event(self):
event = {
"subject": title,
"body": {
"contentType": "HTML",
"content": description
},
"start": {
"dateTime": start_datetime.isoformat(),
"timeZone": timezone_str
},
"end": {
"dateTime": end_datetime.isoformat(),
"timeZone": timezone_str
},
"location": {
"displayName": location
},
"attendees": [{
"emailAddress": {
"address": email,
"name": email
},
"type": "required"
} for email in attendees_emails],
}
headers = {
'Authorization': f'Bearer {self.microsoft_access_token}',
'Content-Type': 'application/json'
}
response = requests.post('https://graph.microsoft.com/v1.0/me/events', headers=headers, json=event)
if response.status_code == 201:
messagebox.showinfo("Success", "Event created in Outlook Calendar.")
else:
messagebox.showerror("Error", f"Failed to create event. Status code: {response.status_code}\n{response.text}")

Ensuring Compatibility with iOS Devices

iOS devices can be particular about .ics file formats. To ensure the “Add to Calendar” option appears on iOS devices, I made several adjustments:

  • Include METHOD:REQUEST: Specifies the calendar method.
  • Add VTIMEZONE Components: Defines time zone information.
  • Set Correct MIME Types and Headers: When attaching the .ics file.
  • Use CRLF Line Endings: As per the iCalendar specification.
# Ensuring CRLF line endings
ics_content = cal.to_ical().replace(b'\n', b'\r\n')

Final Code and Usage Instructions

The complete code for the application is available here. Before running the application:

Prerequisites

  • Install Required Libraries
pip install icalendar pytz tkcalendar google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client msal requests
  • Set Up API Credentials
  • For Google Calendar, place credentials.json in the application directory.
  • For Outlook Calendar, replace 'your_client_id_here' with your actual client ID.

Running the Application

Save the code as icalendar_app.py and run:

python icalendar_app.py

Using the Application

  • Event Details Tab: Fill in event information.
  • Email Settings Tab: Customize the email invitation.
  • Application Settings Tab: Enter SMTP details.
  • Actions:
  • Save as Template: Save event details.
  • Load Template: Load saved templates.
  • Save .ics Locally: Save the .ics file.
  • Create and Send Invite: Send email invitations.
  • Add to Google Calendar: Add event to Google Calendar.
  • Add to Outlook Calendar: Add event to Outlook Calendar.

Closing Remarks

Building this iCalendar invitation application was a fulfilling project that tackled real-world scheduling challenges. By integrating advanced features and ensuring cross-platform compatibility, the application serves as a robust tool for event management.

For developers looking to expand this project, consider:

  • OAuth2 Authentication for Email: To enhance security.
  • Support for Other Calendar Services: Like Apple Calendar via CalDAV.
  • Improved Error Handling and Logging: For better debugging.

--

--

Umair Akbar
Umair Akbar

No responses yet