How to Generate ICS Calendar Files from Text Using Python
You can generate ICS calendar files from plain text by using the generate_ics_content function in the Shubhamsaboo/awesome-llm-apps repository, which parses day-based itineraries using regex and converts them into iCalendar format with the icalendar library.
The ability to transform unstructured travel plans or schedules into downloadable calendar files is essential for AI-powered productivity tools. In the awesome-llm-apps repository, both the starter AI Travel Agent and the MCP AI Travel Planner implement a robust utility function that converts text itineraries into standard .ics files compatible with Google Calendar, Apple Calendar, and Outlook.
Understanding the generate_ics_content Function
The core functionality resides in the generate_ics_content function, implemented in two locations within the repository:
starter_ai_agents/ai_travel_agent/travel_agent.py(lines 23-60)mcp_ai_agents/ai_travel_planner_mcp_agent_team/app.py(lines 26-60)
This function accepts a string containing itinerary text and an optional start date, returning binary data representing a valid iCalendar file.
Step-by-Step Process to Generate ICS from Text
Initialize the Calendar Object
The function begins by creating an icalendar.Calendar instance and setting required properties:
from icalendar import Calendar, Event
from datetime import datetime, timedelta
import re
cal = Calendar()
cal.add('prodid', '-//AI Travel Planner//EN')
cal.add('version', '2.0')
If no start_date is provided, the function defaults to datetime.today().
Parse the Itinerary Text with Regex
The function uses a regular expression to identify day-based entries in the text:
day_pattern = re.compile(r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)', re.DOTALL)
days = day_pattern.findall(plan_text)
This pattern captures "Day 1:", "Day 2:", etc., along with the subsequent text until the next day marker or end of string.
Create All-Day Events
For each matched day, the function creates an all-day calendar event:
for day_num, day_content in days:
event_date = start_date + timedelta(days=int(day_num) - 1)
event = Event()
event.add('summary', f'Day {day_num} Itinerary')
event.add('description', day_content.strip())
event.add('dtstart', event_date.date())
event.add('dtend', (event_date + timedelta(days=1)).date())
event.add('dtstamp', datetime.now())
cal.add_component(event)
Handle Unstructured Text Fallback
If the regex finds no day patterns, the function creates a single event containing the entire text:
if not days:
event = Event()
event.add('summary', 'Travel Itinerary')
event.add('description', plan_text)
event.add('dtstart', start_date.date())
event.add('dtend', (start_date + timedelta(days=1)).date())
event.add('dtstamp', datetime.now())
cal.add_component(event)
Serialize to Binary Format
Finally, the function returns the binary iCalendar data:
return cal.to_ical()
Complete Code Example for Generating ICS Files
Here is a standalone implementation based on the repository's approach:
from icalendar import Calendar, Event
from datetime import datetime, timedelta
import re
def generate_ics_content(plan_text: str, start_date: datetime = None) -> bytes:
"""Convert text itinerary to ICS calendar format."""
if start_date is None:
start_date = datetime.today()
cal = Calendar()
cal.add('prodid', '-//AI Travel Planner//EN')
cal.add('version', '2.0')
# Parse day-based entries
day_pattern = re.compile(r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)', re.DOTALL)
days = day_pattern.findall(plan_text)
if days:
for day_num, day_content in days:
event_date = start_date + timedelta(days=int(day_num) - 1)
event = Event()
event.add('summary', f'Day {day_num} Itinerary')
event.add('description', day_content.strip())
event.add('dtstart', event_date.date())
event.add('dtend', (event_date + timedelta(days=1)).date())
event.add('dtstamp', datetime.now())
cal.add_component(event)
else:
# Fallback for unstructured text
event = Event()
event.add('summary', 'Travel Itinerary')
event.add('description', plan_text)
event.add('dtstart', start_date.date())
event.add('dtend', (start_date + timedelta(days=1)).date())
event.add('dtstamp', datetime.now())
cal.add_component(event)
return cal.to_ical()
# Example usage
itinerary = """
Day 1: Arrive in Paris, check-in at Hotel Le Meurice.
Visit the Louvre (10:00-13:00). Lunch at Café Marly.
Day 2: Day trip to Versailles. Tour the palace (09:00-12:30).
Return to Paris, dinner at Le Jules Verne.
"""
ics_data = generate_ics_content(itinerary, datetime(2024, 9, 12))
with open("paris_trip.ics", "wb") as f:
f.write(ics_data)
Integrating ICS Generation into Streamlit Applications
The repository demonstrates how to expose this functionality in a web UI. After generating the itinerary, you can add a download button:
import streamlit as st
from datetime import datetime
# Assuming generate_ics_content is defined and itinerary exists in session state
if st.session_state.itinerary:
ics_content = generate_ics_content(
st.session_state.itinerary,
datetime.combine(start_date, datetime.min.time())
)
st.download_button(
label="📅 Download Itinerary as Calendar (.ics)",
data=ics_content,
file_name="travel_itinerary.ics",
mime="text/calendar"
)
This implementation appears in travel_agent.py (lines 48-57) and app.py (lines 105-115), allowing users to export AI-generated travel plans directly into their personal calendar applications.
Summary
- The
generate_ics_contentfunction inShubhamsaboo/awesome-llm-appsconverts text itineraries into standard.icsfiles using theicalendarlibrary. - The implementation uses regex pattern
r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)'to parse day-based schedules, falling back to a single event for unstructured text. - Each parsed day becomes an all-day calendar event with appropriate dates calculated from a configurable start date.
- The binary output integrates seamlessly with Streamlit's
st.download_buttonfor immediate user download.
Frequently Asked Questions
What is the ICS file format?
The ICS (iCalendar) format is a standard internet calendaring specification defined in RFC 5545. It uses a text-based structure to represent calendar events, to-do items, and journal entries. Files typically have the .ics extension and can be imported into Google Calendar, Microsoft Outlook, Apple Calendar, and other scheduling applications. The icalendar Python library provides an object-oriented interface for creating and manipulating these files.
How does the regex pattern parse day entries?
The regex pattern r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)' uses a case-insensitive, dot-all approach to identify itinerary sections. The pattern captures the day number in the first group and the subsequent text in the second group, stopping when it encounters another "Day" heading or reaches the end of the string. The re.DOTALL flag ensures that multi-line descriptions are captured correctly, allowing day entries to span multiple lines in the input text.
Can I modify the code for timed events instead of all-day events?
Yes, you can modify the event creation logic to support specific start and end times. Instead of passing date objects to dtstart and dtend, you would pass datetime objects. You would need to parse time information from your text input (e.g., "10:00-13:00") and combine it with the date. The current implementation in travel_agent.py and app.py uses all-day events for simplicity, but the icalendar library fully supports timed events with timezone awareness.
Which Python library is used for ICS generation?
The implementation relies on the icalendar library, a widely-used Python package for creating and parsing iCalendar files. This library provides the Calendar and Event classes used to construct the calendar structure, set properties like prodid and version, and serialize the final output using the to_ical() method. The library handles the proper formatting of dates, timestamps, and text encoding required by the iCalendar specification.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →