Forty games, one calendar

The youth league publishes the season as a spreadsheet: forty games, six fields, dates typed by a volunteer at 11 p.m. Every family wants those games in their phone calendars, and nobody is going to type forty events by hand. The bridge between the spreadsheet and every calendar app on earth is a small, ancient, beautifully specified file format called iCalendar — .ics — and this converter builds it from your CSV.

The .ics format is one of the quiet success stories of the internet: specified in RFC 5545, it has been imported by Google Calendar, Outlook, and Apple Calendar for the better part of two decades. Anything that can produce a valid .ics file can populate anyone's calendar. That is why exports from booking systems, universities, sports leagues and conference platforms all eventually hand you one — and why being able to build one from a spreadsheet is a minor superpower.

The hard parts are not what people expect. The syntax is easy; the semantics are where schedules break — all-day events whose end dates are exclusive, timezones that shift under daylight saving, lines that must fold at 75 octets, and commas that must be escaped inside summaries. This converter handles the specification correctly and tells you what it decided along the way, and this article documents the rules so you can verify that behavior yourself.

Everything runs locally in your browser. A schedule with staff names, home addresses and appointment details is exactly the kind of file that should not be uploaded to a converter service, so it never is.

From spreadsheet to calendar in six steps

The converter is above this text. The flow, using a typical schedule export:

  1. Paste or load the CSV

    Delimiter detection handles commas, semicolons and tabs; the header row becomes the column list you will map from. The preview shows parsed rows so malformed dates surface before conversion, not after import.

  2. Map your columns

    Point the converter at your title, start, end, description and location columns. Every schedule names these differently — Subject/Event Name/What — and the mapper does not care. Optional mappings: UID from an ID column, reminders, categories, recurrence.

  3. Set the timezone handling

    Choose a named IANA timezone for timed events, UTC, or floating times. All-day events ignore timezones by definition — they are dates, not moments — and the tool treats them accordingly.

  4. Review the interpretation report

    Row counts, events with missing ends (duration applied), date-format detection results, and any rows that failed to parse. You will know exactly what the calendar file contains before you download it.

  5. Download the .ics

    One file, RFC 5545-valid: CRLF line endings, folded lines, escaped punctuation, required properties on every event. Re-importing updates events via stable UIDs rather than duplicating them.

  6. Import into your calendar

    Google Calendar: Settings → Import & Export, pick the file, choose the target calendar. Outlook and Apple Calendar open it directly. Import into a test calendar first if the schedule is high-stakes — it takes ten seconds and eliminates the one bad import that ruins a morning.

The whole trip takes about a minute. The verification habit at the end is the part worth keeping: import into a scratch calendar, glance at the week view, then import to the real one.

Inside the .ics format: what your file really contains

An .ics file is a stream of lines with the shape PROPERTY:VALUE. The outer wrapper is a VCALENDAR that must carry VERSION:2.0 and a PRODID — the identifier of whatever produced the file. Inside it, each event is a VEVENT block, and each VEVENT must carry a UID (globally unique identifier) and a DTSTAMP (the moment the event record was created). Those four properties are the spec's non-negotiables; a file missing any of them fails strict validation, and while Google is forgiving, Outlook occasionally is not.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Toolzep//CSV to ICS//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:row-2@toolzep.local
DTSTAMP:20261025T120000Z
SUMMARY:Team Standup
DTSTART;TZID=America/Chicago:20261102T090000
DTEND;TZID=America/Chicago:20261102T091500
LOCATION:Conf Room B
DESCRIPTION:Daily sync
RRULE:FREQ=WEEKLY;BYDAY=MO
END:VEVENT
END:VCALENDAR

Read the date-time values closely, because they encode the timezone decision. 20261102T090000 with a TZID parameter means 9:00 a.m. in Chicago, Central Time, with daylight saving applied automatically by the importing calendar — a November date lands in standard time, a July date in daylight. 20261025T120000Z with the Z suffix means noon UTC, an absolute moment in time. A bare 20261102T090000 with no timezone at all is a floating time: it displays as 9:00 a.m. wherever the viewer happens to be. All three are legal; they mean different things; and choosing among them is the one decision a converter cannot make for you. All-day events are different again — DTSTART;VALUE=DATE:20261225 is a date with no time component, deliberately timezone-free.

Two mechanical rules complete the picture. Lines must not exceed 75 octets: longer content is folded by inserting CRLF followed by a single space, and the importing calendar unfolds and rejoins the text. And text values — SUMMARY, LOCATION, DESCRIPTION — escape their special characters with backslashes: a literal comma becomes \, (commas otherwise separate property parameters), a semicolon becomes \;, a backslash becomes \\, and a newline inside a description is written as the two characters \n. A converter that skips the escaping produces calendar entries with mysteriously truncated titles, which is the classic symptom of an unescaped comma in the summary.

Worked example: a CSV schedule becomes a calendar

A realistic mixed schedule: a recurring standup, a one-off evening event with a timezone, and a company holiday. Three rows, three different date-time shapes — exactly the mix that breaks naive converters.

Three events, three shapes

Input

title,start,end,location,description
Team Standup,2026-11-02 09:00,2026-11-02 09:15,Conf Room B,Daily sync (repeats Mondays)
Fireworks Show,2026-07-04 20:30,2026-07-04 22:00,Riverside Park,
Company Holiday,2026-12-25,,,

Settings: timezone = America/Chicago, recurrence column mapped to description hint FREQ=WEEKLY;BYDAY=MO for row 1, all-day detection on.

Output

3 events written:

1) Team Standup — DTSTART;TZID=America/Chicago:20261102T090000, DTEND 091500, RRULE:FREQ=WEEKLY;BYDAY=MO, location and description mapped. Repeats every Monday indefinitely.

2) Fireworks Show — DTSTART;TZID=America/Chicago:20260704T203000, DTEND 20260704T220000. July 4 in Chicago is daylight time (CDT, UTC-5), so this displays correctly as 8:30 p.m. for Chicago viewers and 9:30 p.m. for East Coast viewers — no manual offset math anywhere.

3) Company Holiday — DTSTART;VALUE=DATE:20261225, DTEND;VALUE=DATE:20261226 (exclusive end: one day total). No timezone, no time, shows on December 25 in every viewer's calendar.

The detail that trips most converters is visible in event 3: DTEND for all-day events is the day after the last covered day. The CSV said the holiday is on December 25; the calendar file says it ends on December 26. That is not an off-by-one bug — it is the specification's design, the same way a hotel checkout on the 26th means you slept there on the 25th. The converter translates between the human convention in your CSV (ends on the last day) and the file convention (ends exclusively), and the interpretation report states which rows were all-day so nothing is silent.

Event 2 quietly demonstrates why named timezones beat manual offsets. If the converter had emitted UTC with a fixed offset for Chicago, a March event would land an hour off after the daylight-saving switch. By emitting TZID:America/Chicago and letting the calendar do zone math, every event lands correctly year-round. That is the difference between storing a moment and storing an intent.

Timezones and all-day events: the two hard parts

Timezone handling has exactly three defensible strategies, and the converter supports all three explicitly rather than picking silently. Named IANA timezones (TZID) are right for shared calendars whose events live in a physical place — offices, venues, fields. UTC is right when the source data is already absolute — server logs, flight departures already stored in UTC. Floating local time is right almost never, but it has one legitimate use: personal calendars that should follow the owner across timezones, like medication reminders. What is never right is guessing, which is why the timezone setting is a visible control with a default you can override.

A word on the zone names themselves: they are IANA database identifiers — America/Chicago, Europe/Berlin, Asia/Tokyo — not the abbreviations people type instinctively. CST is ambiguous (Central Standard Time in the US, China Standard Time, Cuba Standard Time); America/Chicago is not. The converter's picker offers the IANA names with search, because typing the right identifier from memory is a hobby nobody should need.

All-day events interact with timezones in the way that surprises people: they do not interact at all. An all-day event is a date on a calendar page, not a moment on the world's clock. December 25 is December 25 whether you view it from Tokyo or Toronto — which is precisely why VALUE=DATE exists as a separate form from DATE-TIME. The converter marks rows as all-day when the end date equals the start date (or is blank) and no time component exists, and the report lists them so you can confirm the interpretation matches your intent.

Column mapping: making any CSV fit

Schedules disagree about column names more than about anything else. The mapper accepts whatever you have and treats these as the canonical targets:

Calendar propertyTypical CSV column namesNotes
SUMMARY (title)title, event, subject, what, game, classRequired; commas and semicolons escaped on output
DTSTART (start)start, start_date, begins, date, fromDate or date-time; format auto-detected per column
DTEND (end)end, end_date, finishes, to, untilOptional — default duration applies when blank
LOCATIONlocation, venue, field, room, whereOptional; escaped like SUMMARY
DESCRIPTIONdescription, notes, details, commentsOptional; newlines become the literal \n sequence
UIDid, event_id, codeOptional; generated from row position when absent
RRULE (recurrence)repeat, recurrence, rruleOptional; friendly words (weekly on Mondays) or raw rules
VALARM (reminder)reminder, alert_minutesOptional; minutes before start
Eight mappings cover essentially every schedule export we have seen.

The recurrence mapping deserves one paragraph of honesty. RRULE is powerful — FREQ, INTERVAL, COUNT, UNTIL, BYDAY, BYMONTH, BYSETPOS compose into rules like second Tuesday of every month — and the friendly builder covers the common patterns. But spreadsheets also encode recurrence as data: forty rows, one per occurrence. That style converts trivially (every row is an event), and it is often the better choice anyway, because per-occurrence details — different fields, canceled weeks — cannot be expressed in a single recurring rule. Forty explicit rows beat one clever RRULE whenever the exceptions outnumber the pattern.

Edge cases: folding, escaping, and stubborn imports

The failure modes of .ics generation are small, mechanical, and entirely avoidable — which makes encountering them in the wild extra annoying. Here is the field guide.

  • The unescaped comma: a title like Board Meeting, Q4 review becomes Board Meeting with the rest silently dropped, because a comma inside an unquoted iCalendar text value is read as a parameter separator. The converter escapes commas, semicolons and backslashes in every text property. If you have seen mysteriously truncated calendar titles from other tools, this was almost always it.
  • The unfolded line: a description longer than 75 octets is invalid unless folded with CRLF + space. Some tolerant importers accept long lines anyway; some drop them; Apple Calendar has historically been picky. Folding is boring and the converter does it.
  • The double-quoted CSV date: Excel exports dates as 11/2/2026 sometimes wrapped in quotes with the locale of the exporting machine. The parser unwraps and interprets per the column-level format detection, and flags what it cannot prove.
  • The missing DTEND: events with a start but no end are legal in iCalendar, but most viewers render them as zero-length. The converter applies a default duration (configurable, 60 minutes by default) and lists the affected events in the report.
  • The duplicate UID: if your CSV's ID column has repeats — and roster exports love repeated IDs — calendars treat re-imports as updates, silently collapsing events. The report flags duplicate IDs before you find out the hard way.
  • The BOM: a UTF-8 byte-order mark at the start of the CSV turns the first header into an invisible misparse if the reader is careless. Handled; mentioned here only because it breaks so many other tools that people blame the format.

Who builds calendars from spreadsheets

The converter's regulars, in rough order of volume:

  • Sports league coordinators pushing season schedules to dozens of families at once — the canonical use case, forty games, one file.
  • School and university administrators turning term dates, exam schedules and holiday lists into subscribable calendars.
  • HR and office managers publishing shift rosters, on-call rotations and company holidays to shared team calendars.
  • Event organizers migrating conference sessions, workshop tracks and venue assignments out of planning spreadsheets.
  • Trainers and studios converting class timetables for clients — recurring classes with occasional exceptions map cleanly.
  • Volunteers of every variety, because volunteer organizations run on spreadsheets and always will.

The shared situation: the data already exists in rows, and the audience wants it in their calendar. Nobody in that position should be typing events by hand, and nobody needs a backend system to avoid it — a correctly generated .ics file is the entire bridge.

Google Apps Script versus this converter

Search csv to ics and much of what surfaces is scripts: Apps Script snippets that read a Sheet and create calendar events programmatically. Those work, and for a schedule that lives permanently in Google Sheets with a script that re-syncs on edit, the scripted approach is genuinely better. The honest comparison:

ApproachShines whenCosts
Apps Script / API automationRecurring need, same sheet every week, someone maintains the scriptSetup and auth; script maintenance; event-per-API-call rate limits on big schedules; the calendar is Google-only
This converterOne-off or occasional schedules, any source CSV, any target calendarManual re-run when the spreadsheet changes; no live sync
Manual entryTwo or three eventsBeyond three events, never again
Automation for the repeating job; a converter for the file in your hand today.

One more advantage worth naming: the .ics file is calendar-agnostic. The same file imports into Google, Outlook and Apple calendars, and can be emailed to a distribution list where every recipient imports it themselves. Scripted solutions typically create events in one specific calendar — fine when your whole organization is on one platform, limiting the moment it is not.

Private by construction

Schedules leak personal information constantly: home addresses in location fields, medical appointments in titles, staffing rotas that name who is vulnerable when. Uploading that to a conversion service to get a calendar file back is a bad trade, and this tool never asks for it. The CSV is parsed in your browser, the timezone arithmetic is local date math, and the .ics file is assembled in memory and downloaded directly. No server sees the schedule, which also means no server can lose it.

The local design has a practical bonus for the recurring user: it works offline. Coordinators running registrations at a field with terrible reception can still convert the day's schedule from a laptop, because the page needs no network once loaded.

FAQ

The questions that arrive from people mid-migration.

Will the .ics file work in Google Calendar, Outlook and Apple Calendar?

Yes. The output is RFC 5545 iCalendar — the format all three import natively. Google Calendar imports it through Settings → Import & Export, Outlook through File → Open & Export, and Apple Calendar by double-clicking the file or opening it in Calendar → File → Import. Every event carries a UID and DTSTAMP, the properties the spec requires and that calendars use to track events across re-imports.

Why did my all-day event import as two days, or show on the wrong day?

Almost always the DTEND rule: in iCalendar, the end date of an all-day event is exclusive — a Christmas event runs DTSTART;VALUE=DATE:20261225 to DTEND;VALUE=DATE:20261226, one day in total. A converter that writes the same date for both produces a zero-length event, and one that adds a full extra day produces a two-day block. The converter writes exclusive DTEND correctly and reads plain end dates in your CSV sensibly by treating an all-day row's end as the last covered day.

What date formats can the start and end columns use?

The common ones: ISO (2026-11-02 or 2026-11-02 09:00), US (11/02/2026 or 11/02/2026 9:00 AM), European (02/11/2026), and dotted (02.11.2026). Ambiguous day-first versus month-first values are resolved by scanning the whole column for decisive rows — the same detection used by our date converter — and flagged rather than guessed when the column does not resolve.

How do recurring events work?

You map one column to the recurrence rule, and its value is written into the RRULE property — for example FREQ=WEEKLY;BYDAY=MO,WE for an event repeating every Monday and Wednesday. RFC 5545 defines the rule parts: FREQ (DAILY, WEEKLY, MONTHLY, YEARLY), COUNT, UNTIL, INTERVAL, BYDAY, BYMONTH and friends. A friendly builder writes the common patterns for you; a raw column passes any valid rule through untouched.

What is a UID and does it matter if two events share one?

UID is the globally unique identifier every event must carry. Calendars use it to decide whether an incoming event is new or an update: re-importing the same UID updates the existing event instead of duplicating it. The converter generates stable UIDs (derived from your file and row) so re-imports behave sensibly — and if your CSV already has an ID column, map it and your own identifiers become the UIDs.

What timezone are the events in?

Whichever you choose. The converter can emit UTC (with the Z suffix), a named IANA timezone like America/Chicago via the TZID parameter — which preserves daylight-saving shifts across the year — or floating local times with no timezone at all, which display in whatever timezone the viewer happens to be in. For shared calendars, named timezones are almost always the right choice.

Can I convert an Excel schedule instead of a CSV?

Save the sheet as CSV from Excel first — .ics conversion works from tabular text. One caution on the way out of Excel: check that date columns did not get reformatted to your locale's layout during the save, and that times are not silently missing, since a date-only cell becomes a floating midnight event if mapped to a timed slot.

Is my schedule uploaded anywhere?

No. Parsing, timezone math, and .ics generation all run in your browser. The calendar file is assembled in memory and downloaded directly — nothing is transmitted, which matters for schedules that contain staffing details, private addresses or medical appointments.