Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ical2rem
Convert iCalendar (.ics) files into Remind format.
Designed to work on real-world calendar data (Google Calendar, SOGo, Outlook/Teams exports). Full RFC 5545 coverage is intentionally out of scope — see Limitations.
Features
Event types supported
| iCalendar pattern | Remind output |
|---|---|
| All-day single | REM YYYY-MM-DD MSG … |
| All-day multi-day | REM date THROUGH date MSG … |
| Timed event (UTC, local, TZID) | REM date AT HH:MM DURATION HH:MM MSG … |
Yearly recurrence (FREQ=YEARLY) |
REM MMM DD MSG … |
Weekly recurrence (FREQ=WEEKLY) |
REM Mon Wed YYYY-MM-DD *7 UNTIL … MSG … |
Daily recurrence (FREQ=DAILY) |
REM date *N UNTIL … MSG … |
Monthly by day-of-month (BYMONTHDAY) |
REM N FROM … UNTIL … MSG … |
Monthly by nth weekday (BYDAY=nWD) |
REM Wd N FROM … UNTIL … MSG … |
Exceptions (EXDATE) |
PUSH-OMIT-CONTEXT + OMIT + SKIP + POP-OMIT-CONTEXT |
Overrides (RECURRENCE-ID) |
OMIT on original slot + single REM with new date/time |
Cancellations (STATUS:CANCELLED) |
OMIT only, no REM |
Duration via DURATION instead of DTEND |
Handled transparently |
Alarms (VALARM ACTION:DISPLAY/AUDIO) |
AT … +n / ++n / SCHED / WARN |
| Conference URL (Google Meet, Teams) | INFO "Url: …" |
| Windows timezone names (Outlook) | Resolved to IANA via CLDR table |
Metadata lines
Each reminder can include optional INFO lines (all suppressible via flags):
REM \
INFO "UID: …" \
INFO "Calendar: MYCAL" \
INFO "Location: …" \
INFO "Description: …" \
INFO "Url: …" \
…
Installation
From source
Requires opam and OCaml >= 5.0.
git clone https://git.donadeo.net/pdonadeo/ical2rem
cd ical2rem
opam install . --deps-only
dune build
dune install
The binary is installed as ical2rem.
Dependencies
icalendar— iCal parsertimedesc+timedesc-tzdb.full+timedesc-tzlocal.unix— date/time + timezone handlingcmdliner— CLIppx_deriving.show— debug printers
Usage
ical2rem [OPTION]… FILE…
Output goes to stdout and can be redirected to a .rem file.
Pass - as a filename to read from standard input. - may appear at most once,
but can be freely mixed with regular files:
curl https://example.com/calendar.ics | ical2rem - > calendar.rem
ical2rem work.ics - personal.ics > all.rem
Examples
Convert a single calendar:
ical2rem personal.ics > personal.rem
Convert multiple calendars into one file:
ical2rem work.ics personal.ics > all.rem
Sort output chronologically (oldest first):
ical2rem --sort asc personal.ics > personal.rem
Run on a server in UTC, output in a specific timezone:
ical2rem --timezone Europe/Rome personal.ics > personal.rem
Strip all metadata lines from output:
ical2rem --no-uuid --no-source --no-location --no-description --no-conference-url personal.ics
Use stdin as input calendar:
curl https://example.com/calendar.ics | ical2rem - > calendar.rem
Override calendar name in INFO lines (single file only):
ical2rem --source "Work" work.ics > work.rem
Show diagnostic warnings (skipped events, unsupported rules, etc.):
ical2rem --verbose personal.ics > personal.rem
All options
| Option | Description |
|---|---|
FILE… |
One or more .ics files to convert; use - for standard input (at most once) |
-z, --timezone TZ |
Target timezone for output (default: local) |
--sort asc|desc|original |
Sort order by date (default: desc); original preserves processing order (sorted by UID within each file, last file first) |
--source NAME |
Override calendar name (single file only) |
-v, --verbose |
Print diagnostic messages on stderr |
--no-uuid |
Omit INFO "UID: …" lines |
--no-source |
Omit INFO "Calendar: …" lines |
--no-location |
Omit INFO "Location: …" lines |
--no-description |
Omit INFO "Description: …" lines |
--no-conference-url |
Omit INFO "Url: …" lines |
--version |
Print version and exit |
--help |
Print help and exit |
Output format examples
All-day single event
REM \
INFO "UID: abc@google.com" \
INFO "Calendar: PERSONAL" \
2026-06-15 MSG Birthday party
Timed event with alarm
REM \
INFO "UID: xyz@google.com" \
INFO "Calendar: WORK" \
2026-05-27 AT 10:00 +30 DURATION 01:00 MSG %"Team standup%" (%b %3)
Weekly recurrence with exception
PUSH-OMIT-CONTEXT
OMIT 6 Oct 2025
REM \
INFO "UID: …" \
INFO "Calendar: WORK" \
Mon 2025-09-01 *7 UNTIL 2025-12-31 SKIP AT 09:00 +15 MSG %"Standup%" (%b %3)
POP-OMIT-CONTEXT
Recurring event with modified occurrence
PUSH-OMIT-CONTEXT
OMIT 3 Jun 2015
REM \
INFO "UID: …" \
INFO "Calendar: PERSONAL" \
Wed 1 FROM 2015-06-03 UNTIL 2015-06-09 SKIP MSG Monthly payment
POP-OMIT-CONTEXT
REM \
INFO "UID: …" \
INFO "Calendar: PERSONAL" \
2015-06-09 MSG Monthly payment
Limitations
Recurrence rules (RRULE)
FREQ=WEEKLY INTERVAL=N(N > 1): supported (maps to*7N).FREQ=MONTHLY INTERVAL=N(N > 1): not supported, event skipped with warning.FREQ=MONTHLY BYDAY=WDwithout position (every Monday of the month): not supported.BYSETPOS: not supported.FREQ=YEARLYwithBYMONTH/BYDAYvariants: not supported, only simple yearly (same day every year).RDATE(additional isolated dates): not supported, warning emitted.
Alarms (VALARM)
ACTION:EMAIL: ignored silently.TRIGGER;VALUE=DATE-TIME(absolute datetime trigger): ignored silently.- Positive triggers (after the event): ignored silently.
RELATED=ENDtriggers: the offset is applied as if it wereRELATED=START(no warning emitted).REPEAT/DURATION(repeating alarms) on all-day events: ignored.
Other
ATTENDEE,CATEGORIES, visibility/status fields: ignored.VTIMEZONEcomponents: ignored; TZID names are resolved directly via IANA or the built-in Windows→IANA CLDR table.