Skip to content

Instantly share code, notes, and snippets.

@swateek
Created December 8, 2025 10:17
Show Gist options
  • Select an option

  • Save swateek/a554eea7838423310453714e72601368 to your computer and use it in GitHub Desktop.

Select an option

Save swateek/a554eea7838423310453714e72601368 to your computer and use it in GitHub Desktop.
Create Calendar Events From a Simple CSV file

CSV to ICS Converter

This script converts a CSV file containing event details into an ICS file that can be imported into Google Calendar (and other calendar applications).

Usage

  1. Prepare your CSV file: The CSV file should have the following columns (case-insensitive):

    • start date (Format: YYYY-MM-DD HH:MM or YYYY-MM-DD for all-day)
    • end date (Format: YYYY-MM-DD HH:MM or YYYY-MM-DD)
    • description
    • repeats (Optional. Values: MONTHLY, ANNUAL or YEARLY)

    Example events.csv:

    start date,end date,description,repeats
    2025-12-10 10:00,2025-12-10 11:00,Team Meeting,MONTHLY
    2025-12-25,2025-12-25,Christmas,ANNUAL
    
  2. Run the script: Pass the CSV file as an argument. The output ICS file will have the same name as the input file, but with a .ics extension.

    python3 csv_to_ics.py <input_csv_file>

    Example:

    python3 csv_to_ics.py events.csv

    This will generate events.ics.

  3. Import to Calendar:

    • Open Google Calendar.
    • Go to Settings > Import & export.
    • Select the generated .ics file and import it.
import csv
import datetime
import uuid
import sys
import os
def create_ics_event(start_dt, end_dt, description, is_all_day=False, repeats=None):
"""Creates a single ICS event string."""
dt_format_full = "%Y%m%dT%H%M%S"
dt_format_date = "%Y%m%d"
now = datetime.datetime.now().strftime(dt_format_full)
uid = str(uuid.uuid4())
if is_all_day:
end_dt_exclusive = end_dt + datetime.timedelta(days=1)
dt_start_str = f"DTSTART;VALUE=DATE:{start_dt.strftime(dt_format_date)}"
dt_end_str = f"DTEND;VALUE=DATE:{end_dt_exclusive.strftime(dt_format_date)}"
else:
dt_start_str = f"DTSTART:{start_dt.strftime(dt_format_full)}"
dt_end_str = f"DTEND:{end_dt.strftime(dt_format_full)}"
event = [
"BEGIN:VEVENT",
f"DTSTAMP:{now}",
f"UID:{uid}",
dt_start_str,
dt_end_str,
f"SUMMARY:{description}",
f"DESCRIPTION:{description}"
]
if repeats:
freq = repeats.upper().strip()
if freq == "ANNUAL":
freq = "YEARLY"
if freq in ["MONTHLY", "YEARLY"]:
event.append(f"RRULE:FREQ={freq}")
event.append("END:VEVENT")
return "\n".join(event)
def parse_date(date_str):
"""Parses date string and returns datetime object and boolean indicating if it's all-day."""
formats = [
("%Y-%m-%d %H:%M", False),
("%Y-%m-%d", True)
]
for fmt, is_all_day in formats:
try:
return datetime.datetime.strptime(date_str, fmt), is_all_day
except ValueError:
continue
raise ValueError(f"Unknown date format: {date_str}")
def convert_csv_to_ics(csv_file):
"""Converts CSV events to ICS file."""
# Determine output filename
base, ext = os.path.splitext(csv_file)
ics_file = f"{base}.ics"
try:
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
reader.fieldnames = [name.lower().strip() for name in reader.fieldnames]
events = []
for row in reader:
try:
start_str = row['start date']
end_str = row['end date']
desc = row['description']
repeats = (row.get('repeats') or '').strip() # Handle None if row is shorter than header
start_dt, start_is_all_day = parse_date(start_str)
end_dt, end_is_all_day = parse_date(end_str)
is_all_day = start_is_all_day or end_is_all_day
if is_all_day:
start_dt = start_dt.replace(hour=0, minute=0, second=0, microsecond=0)
end_dt = end_dt.replace(hour=0, minute=0, second=0, microsecond=0)
events.append(create_ics_event(start_dt, end_dt, desc, is_all_day, repeats))
except ValueError as e:
print(f"Skipping row due to date parse error: {row}. Error: {e}")
except KeyError as e:
print(f"Skipping row due to missing column: {row}. Missing: {e}")
with open(ics_file, 'w', encoding='utf-8') as f:
f.write("BEGIN:VCALENDAR\n")
f.write("VERSION:2.0\n")
f.write("PRODID:-//My Calendar//CSV to ICS//EN\n")
f.write("CALSCALE:GREGORIAN\n")
for event in events:
f.write(event + "\n")
f.write("END:VCALENDAR\n")
print(f"Successfully created {ics_file} with {len(events)} events.")
except FileNotFoundError:
print(f"Error: File {csv_file} not found.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python csv_to_ics.py <input_csv>")
sys.exit(1)
input_csv = sys.argv[1]
convert_csv_to_ics(input_csv)
start date end date description repeats
2025-12-10 10:00 2025-12-10 11:00 Team Meeting MONTHLY
2025-12-12 14:00 2025-12-12 15:30 Project Demo
2025-12-15 09:00 2025-12-15 17:00 Workshop
2025-12-25 2025-12-25 Christmas ANNUAL
2026-01-01 2026-01-02 New Year Retreat YEARLY
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My Calendar//CSV to ICS//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20251208T095334
UID:ca89315b-fff9-48f9-94cf-65a3c6f7b9e8
DTSTART:20251210T100000
DTEND:20251210T110000
SUMMARY:Team Meeting
DESCRIPTION:Team Meeting
RRULE:FREQ=MONTHLY
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20251208T095334
UID:08e2aded-960c-4e1b-bc69-97860044f722
DTSTART:20251212T140000
DTEND:20251212T153000
SUMMARY:Project Demo
DESCRIPTION:Project Demo
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20251208T095334
UID:a248baed-1db8-43d7-aa09-7a78aa8c074a
DTSTART:20251215T090000
DTEND:20251215T170000
SUMMARY:Workshop
DESCRIPTION:Workshop
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20251208T095334
UID:e1e0e5f1-6579-4770-885b-a4c55f662f5a
DTSTART;VALUE=DATE:20251225
DTEND;VALUE=DATE:20251226
SUMMARY:Christmas
DESCRIPTION:Christmas
RRULE:FREQ=YEARLY
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20251208T095334
UID:0b6e7a81-409c-4fb9-ba03-21f6e7513a08
DTSTART;VALUE=DATE:20260101
DTEND;VALUE=DATE:20260103
SUMMARY:New Year Retreat
DESCRIPTION:New Year Retreat
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment