Convert ICS to CSV

Converting ICS to CSV turns a calendar export into rows you can sort and total, which is what anybody counting meeting hours or auditing a schedule actually wants. One thing has to be understood before you total anything: a recurring event is a single row, because the repeat rule has no column and cannot be given one.

  • Where it runs In your browser. The file is never uploaded.
  • Rebuilt CSV works differently from an ICS, so this is not the gradual degradation a lossy codec applies. What CSV can express is reproduced faithfully; what it has no equivalent for does not survive at all.
  • File size limit Up to 100 MB per file, free, without an account.
  • Worth knowing Repeating fields — several phone numbers, several attendees — are collected into one cell rather than spread across invented columns. Recurrence rules, alarms and attachments have no place in a table and are left out.

Up to 100 files at once. Mixed formats are fine.

The questions a spreadsheet can answer about a calendar

A calendar application is built for looking forward, and it is poor at looking back in aggregate. How many hours went to one client last quarter. Which recurring meeting has quietly grown to fourteen people. How much of a week is booked before anybody schedules anything. Whether a room was used on the days it was blocked. None of those are a view in a calendar and all of them are one pivot table.

So the export exists to be counted. That framing matters because it decides which of the things this conversion cannot do are serious: losing an event’s colour is nothing, and losing the fact that an event repeated is everything. The next two sections are about that.

A repeating event appears once in the CSV

This is the part to read before trusting any total. In the calendar format a recurring series is stored as one event carrying a recurrence rule — the daily stand-up for the past year is one `VEVENT` with an `RRULE` saying weekly on weekdays until a date, plus perhaps a handful of `RDATE` additions and `EXDATE` exceptions for the days it was cancelled.

The columns produced here are the event’s own properties: summary, start, end, location, description, organiser, status, identifier and attendees. `RRULE`, `RDATE` and `EXDATE` are not among them, so the series contributes exactly one row, showing the first occurrence’s times. A quarter’s worth of stand-ups is one thirty-minute row. Anything that sums the duration column will be wrong by however much the recurring events actually consumed, which in most working calendars is the majority of the time.

Expanding the series before exporting the ICS

The fix is upstream rather than downstream, and it is easier than it sounds. Both Google Calendar and Outlook, when asked to export or print a date range rather than a whole calendar, emit the individual occurrences instead of the rule — so exporting "this quarter" gives you a file where every stand-up is its own `VEVENT` and every one becomes a row.

Where that is not available, expansion is a scripted job rather than a spreadsheet one: Python’s `icalendar` with `dateutil.rrule`, or the `ical-expander` package for Node, will walk a series between two dates and produce the occurrences. Either way, do the expansion while the file is still an ICS. Once it is a table the rule is gone and nothing can reconstruct it, because the table never carried it.

The nine columns an ICS event becomes

In order: `summary`, `start`, `end`, `location`, `description`, `organizer`, `status`, `uid`, `attendees`. Every event produces a row with all nine, empty where the event had no such property, so the table is rectangular regardless of how uneven the source is.

Two of them repay attention. `status` distinguishes confirmed, tentative and cancelled events, and cancelled ones still produce rows — filter them out before counting, or a cancelled all-day workshop will be counted as time spent. And `uid` is a stable identifier the calendar assigned, which is the only reliable way to deduplicate when two exports overlap; sorting by it and removing duplicates is far safer than matching on summary and start.

Time zones: some cells carry a Z and some do not

Timestamps are rewritten from the compact calendar form into ISO 8601, so `20260301T080000Z` becomes `2026-03-01T08:00:00Z`, which every spreadsheet recognises. What is not done is any shifting between zones.

A value written in UTC keeps its trailing Z. A value qualified by a `TZID` parameter — `DTSTART;TZID=Europe/Berlin:20260301T080000` — is local to that zone, and it is written through as `2026-03-01T08:00:00` with no Z and no offset. Converting it would require a time-zone database, and assuming UTC would move every appointment by an hour or more. The result is a column where some cells declare their zone and some do not, and the table does not say which zone the others were in. If you are totalling durations that is harmless, since start and end share a zone. If you are comparing times across a distributed team it is not, and the zone has to come from the source file.

All-day events, and the end date that is a day late

An all-day event is written with date values rather than timestamps, and it converts to a plain `2026-03-01` in the start and end columns with no time part. That is unambiguous and it sorts correctly, which is more than can be said for most date handling.

The catch is in the format rather than the conversion: for a date value the end is exclusive. A one-day event on the 3rd is stored as starting on the 3rd and ending on the 4th, and a three-day workshop from the 3rd ends on the 6th. Subtract a day before reporting a date range to a person, and be careful with any formula computing duration — the arithmetic is right for the format and wrong for the sentence somebody will write from it.

Events that declare a duration instead of an end

The calendar format allows an event to state a start and a duration rather than a start and an end, and some producers use that form for everything. Only the end property is mapped to a column, so those events arrive with a populated start and an empty end.

It is quiet rather than damaging, and it is very easy to miss in a large table because empty cells look like optional data. If the end column is sparse, that is the reason — sort by it and see whether the blanks cluster around one source. Recovering the value means going back to the ICS and reading the duration property, which is a job for a script rather than a spreadsheet.

Attendees and organisers arrive as names where the file had them

An invitation stores an attendee as an address in the value and a display name in a parameter. The readable half is the one kept, so the cell holds "Anna Weber" where the invitation carried a common name and `[email protected]` where it did not. Several attendees are joined with a semicolon and a space into a single cell. The organiser is treated the same way, in its own column.

That is a deliberate choice over inventing `attendee1` through `attendee9`, which would truncate the tenth and leave nine mostly-empty columns for everybody else. Counting attendees is then a formula on the cell rather than a count of columns — in a spreadsheet, the length of the cell minus the length with semicolons removed, plus one. Splitting the cell into rows is a Power Query step or a two-line script if you need one row per person.

Only events are read, so a task list produces nothing

The calendar format carries more than appointments. Tasks are `VTODO`, journal entries are `VJOURNAL`, free-and-busy blocks are `VFREEBUSY`, and alarms sit inside events as `VALARM`. Only `VEVENT` is read here.

A file containing nothing but tasks therefore stops with "No calendar entries were found in this file", which is accurate rather than a failure — an export from a reminders application is not a calendar of events even though it shares the extension. Alarms, attachments and per-attendee response status are dropped from the events that are read, for the same reason recurrence is: a table has no shape for them and inventing columns would be worse than omitting them.

Opening the CSV without damaging the timestamps

Import rather than open. In Excel, Data then From Text/CSV, and set the start and end columns to Text on the way in unless you want Excel to reinterpret them — its date parsing will happily strip the offset, reformat according to the machine’s locale, and turn an ISO string into something that no longer sorts as text. The identifier column should be Text as well.

Descriptions are the other thing to watch. Meeting descriptions routinely contain commas, quotation marks and real line breaks — a pasted agenda, a dial-in block — and all three are handled by wrapping the value in quotation marks with internal quotes doubled, which is the RFC 4180 convention every competent importer reads. A naive split on commas in a shell pipeline will not, so use a real CSV reader or convert to TSV instead.

How to turn an ICS calendar export into a spreadsheet

  1. Export a date range rather than a whole calendar, so recurring events arrive expanded.
  2. Drop the .ics onto this page; each event becomes a row in your browser.
  3. Import the CSV with the timestamp columns set to Text, then filter out cancelled events.

ICS and CSV: a recurrence model against a flat table

ICS compared with CSV
ICSCSV
Full nameiCalendarComma-Separated Values
File extension.ics, .ical.csv
Media typetext/calendartext/csv
First published19981972
SpecificationRFC 5545RFC 4180
LicensingOpen standardOpen standard
Standing todayCurrentCurrent
Opens in a browserNo browserNo browser
Considered insteadXLSX, JSON, Parquet

Opening the result

The usual programs do not overlap: ICS opens in Google Calendar, Apple Calendar and Microsoft Outlook, CSV in Microsoft Excel, LibreOffice Calc and pandas — so whoever receives the result needs something from the second list.

What each format is for

ICS was published in 1998. The specification is RFC 5545, and it is worth reading if the file has to outlive the tool that wrote it.

CSV dates from 1972, specified as RFC 4180. Microsoft Excel, LibreOffice Calc and pandas all read it.

CSV was published in 1972 and ICS in 1998. The older one is generally the safer file to hand to somebody; the newer one usually does the job in fewer bytes.

ICS to CSV: recurrence, time zones and all-day events

Are my ICS files uploaded anywhere?

No. This conversion runs entirely inside your browser, so the file never leaves your device. You can confirm it yourself: open the network tab of your browser's developer tools and convert something. You will see the page load, plus the analytics and advertising the site is paid for with — and nothing carrying your file.

Does a weekly meeting become one row or fifty-two?

One. The recurrence rule is a separate property that is not among the columns, so a repeating event appears once with its first occurrence’s times. Any total computed from the table will therefore be far too low unless you expand the series first.

Why do some start times end in Z and others do not?

Because the export mixed forms. A value written in UTC keeps its Z; one qualified by a TZID parameter is local to that zone and is passed through unchanged rather than shifted, because shifting it would need a time-zone database and guessing would move the appointment.

Why is the end date of an all-day event the following day?

That is how the format defines it — for a date value the end is exclusive, so a one-day event on the 3rd is written as starting on the 3rd and ending on the 4th. Subtract a day before reporting it to anybody.

Some rows have no end time. Why?

Because those events declare a duration rather than an end, and only the end property is mapped to a column. The information is in the file and not in the table; if it matters, the duration has to be recovered from the source.

What happens to attendees?

They are collected into one cell separated by semicolons, using the display name where the invitation carried one and the address otherwise. That keeps the table a fixed width rather than inventing an attendee1 to attendee9 that would truncate the tenth.

Is my calendar uploaded?

No. The file is parsed and rewritten by JavaScript in this tab. A calendar export is a record of where somebody was and who they were with, which is exactly the category that should not be passing through a web service.

More about these formats