Friday, December 26, 2025

Data: .ICS to .CSV converter, vibe-coded

Calendar (.ics) File Structure

BEGIN:VCALENDAR
BEGIN:VEVENT
CREATED:20151219T021727Z
DTEND;TZID=America/Toronto:20170515T110000
DTSTAMP:20151219T021727Z
DTSTART;TZID=America/Toronto:20170515T100000
LAST-MODIFIED:20151219T021727Z
RRULE:FREQ=DAILY;UNTIL=20170519T035959Z
SEQUENCE:0
SUMMARY:Meeting
TRANSP:OPAQUE
UID:21B97459-D97B-4B23-AF2A-E2759745C299
END:VEVENT
END:VCALENDAR

Web App

ICS to CSV Converter

📁 Click to select or drag & drop an .ics file



Python

#!/usr/bin/env python3
import re, csv, sys
from pathlib import Path

if len(sys.argv) != 2:
sys.exit("Usage: python3 ics_to_csv.py <filepath.ics>")

input_path = Path(sys.argv[1])
output_path = input_path.with_suffix('.csv')

events = re.findall(r'BEGIN:VEVENT(.*?)END:VEVENT', input_path.read_text(), re.DOTALL)

with open(output_path, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['Date', 'Summary'])
writer.writeheader()
for event in events:
if (date := re.search(r'DTSTART;VALUE=DATE:(\d{8})', event)) and (summary := re.search(r'SUMMARY:(.*)', event)):
writer.writerow({'Date': f"{date[1][:4]}-{date[1][4:6]}-{date[1][6:]}", 'Summary': summary[1].strip()})

print(f"Converted {len(events)} events\nOutput: {output_path}")

JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');

if (process.argv.length !== 3) {
console.error("Usage: node ics_to_csv.js <filepath.ics>");
process.exit(1);
}

const inputPath = process.argv[2];
const outputPath = inputPath.replace(/\.ics$/i, '.csv');
const content = fs.readFileSync(inputPath, 'utf8');
const events = content.match(/BEGIN:VEVENT([\s\S]*?)END:VEVENT/g) || [];

const rows = events.map(event => {
const date = event.match(/DTSTART;VALUE=DATE:(\d{8})/)?.[1];
const summary = event.match(/SUMMARY:(.*)/)?.[1]?.trim();
return date && summary ? `${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6)},"${summary.replace(/"/g, '""')}"` : null;
}).filter(Boolean);

fs.writeFileSync(outputPath, `Date,Summary\n${rows.join('\n')}`);
console.log(`Converted ${events.length} events\nOutput: ${outputPath}`);

C#

using System.Text.RegularExpressions;

if (args.Length != 1) { Console.Error.WriteLine("Usage: ics_to_csv <filepath.ics>"); return 1; }

var input = args[0];
var output = Path.ChangeExtension(input, ".csv");
var content = File.ReadAllText(input);
var events = Regex.Matches(content, @"BEGIN:VEVENT([\s\S]*?)END:VEVENT");

var rows = events.Select(e => {
var date = Regex.Match(e.Value, @"DTSTART;VALUE=DATE:(\d{8})").Groups[1].Value;
var summary = Regex.Match(e.Value, @"SUMMARY:(.*)").Groups[1].Value.Trim();
return date.Length == 8 && summary.Length > 0 ? $"{date[..4]}-{date[4..6]}-{date[6..]},\"{summary.Replace("\"", "\"\"")}\"" : null;
}).Where(r => r != null);

File.WriteAllText(output, $"Date,Summary\n{string.Join("\n", rows)}");
Console.WriteLine($"Converted {events.Count} events\nOutput: {output}");
return 0;

Go

package main

import (
"fmt"
"os"
"regexp"
"strings"
)

func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "Usage: go run ics_to_csv.go <filepath.ics>")
os.Exit(1)
}

input := os.Args[1]
output := strings.TrimSuffix(input, ".ics") + ".csv"
content, _ := os.ReadFile(input)
eventRe := regexp.MustCompile(`BEGIN:VEVENT([\s\S]*?)END:VEVENT`)
dateRe := regexp.MustCompile(`DTSTART;VALUE=DATE:(\d{8})`)
summaryRe := regexp.MustCompile(`SUMMARY:(.*)`)
events := eventRe.FindAllString(string(content), -1)
var rows []string
for _, event := range events {
if dateMatch := dateRe.FindStringSubmatch(event); dateMatch != nil {
if summaryMatch := summaryRe.FindStringSubmatch(event); summaryMatch != nil {
date := dateMatch[1]
summary := strings.TrimSpace(summaryMatch[1])
rows = append(rows, fmt.Sprintf("%s-%s-%s,\"%s\"", date[:4], date[4:6], date[6:], strings.ReplaceAll(summary, `"`, `""`)))
}
}
}
os.WriteFile(output, []byte("Date,Summary\n"+strings.Join(rows, "\n")), 0644)
fmt.Printf("Converted %d events\nOutput: %s\n", len(events), output)
}


No comments: