Files
ical2rem/README.md
Paolo Donadeo cbdf7e3d36
Some checks failed
Release Binaries / build (amd64, ocaml/opam:ubuntu-22.04-ocaml-5.4, linux/amd64, linux-amd64, , sudo apt install -y upx) (release) Successful in 1m22s
Release Binaries / build (arm64, ocaml/opam:alpine-ocaml-5.4, linux/arm64, linux-arm64, OCAMLPARAM='_,ccopt=-static,cclib=-static', sudo apk add upx) (release) Failing after 2m50s
docs: fix outdated output examples in README
2026-06-19 23:50:06 +02:00

214 lines
6.3 KiB
Markdown

# ical2rem
Convert iCalendar (`.ics`) files into [Remind](https://dianne.skoll.ca/projects/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](#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](https://opam.ocaml.org/) and OCaml >= 5.0.
```bash
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`](https://github.com/roburio/icalendar) — iCal parser
- [`timedesc`](https://github.com/daypack-dev/timere) + `timedesc-tzdb.full` + `timedesc-tzlocal.unix` — date/time + timezone handling
- [`cmdliner`](https://erratique.ch/software/cmdliner) — CLI
- [`ppx_deriving.show`](https://github.com/ocaml-ppx/ppx_deriving) — 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:
```bash
curl https://example.com/calendar.ics | ical2rem - > calendar.rem
ical2rem work.ics - personal.ics > all.rem
```
### Examples
Convert a single calendar:
```bash
ical2rem personal.ics > personal.rem
```
Convert multiple calendars into one file:
```bash
ical2rem work.ics personal.ics > all.rem
```
Sort output chronologically (oldest first):
```bash
ical2rem --sort asc personal.ics > personal.rem
```
Run on a server in UTC, output in a specific timezone:
```bash
ical2rem --timezone Europe/Rome personal.ics > personal.rem
```
Strip all metadata lines from output:
```bash
ical2rem --no-uuid --no-source --no-location --no-description --no-conference-url personal.ics
```
Use stdin as input calendar:
```bash
curl https://example.com/calendar.ics | ical2rem - > calendar.rem
```
Override calendar name in `INFO` lines (single file only):
```bash
ical2rem --source "Work" work.ics > work.rem
```
Show diagnostic warnings (skipped events, unsupported rules, etc.):
```bash
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=WD` without position (every Monday of the month): **not supported**.
- `BYSETPOS`: **not supported**.
- `FREQ=YEARLY` with `BYMONTH`/`BYDAY` variants: **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=END` triggers: the offset is applied as if it were `RELATED=START` (no warning emitted).
- `REPEAT`/`DURATION` (repeating alarms) on all-day events: ignored.
### Other
- `ATTENDEE`, `CATEGORIES`, visibility/status fields: ignored.
- `VTIMEZONE` components: ignored; TZID names are resolved directly via IANA or the built-in Windows→IANA CLDR table.