iCalendar subscriptions
Stability: stable (org-extensions-stability)
ics shows the events of iCalendar (.ics) calendars in the agenda, read-only, next to your org entries: a Google or Outlook "secret address", any http(s):// or webcal:// subscription link, or a local file:
require("org").setup({
extensions = {
ics = {
calendars = {
{ name = "Work", url = "https://calendar.google.com/.../x.ics" },
{ name = "Home", path = "~/cal/home.ics", tags = { "home" } },
},
},
},
})
Each calendar takes name, url or path, and optionally category (the agenda's category column, default the name), tags (for agenda tag filters), refresh (minutes), face and agenda = false (import only).
Events appear in agenda day and week views with their times in the time grid, the calendar's category and, with show_location, the location after the title. An event spanning days (all-day, or timed and crossing midnight) shows (1/3): like an org date range: a timed one with its start time on the first day and its end time on the last. They are added after the diary; like diary lines they can't be edited or visited from the agenda. They are left out of lists such as the TODO list, and of agendas restricted to a file or subtree (< in the dispatcher): a calendar is not part of any org file, so a restriction to one leaves it out, like the diary. The expansion of a calendar is kept per date range and display zone until the file changes, so redrawing an agenda doesn't expand it again.
Subscriptions
A calendar with a url is fetched with curl in the background into cache_dir (stdpath("cache")/org/ics), when org.nvim starts and then whenever the copy is older than refresh minutes (checked every minute and when an agenda is built); an open agenda is redrawn when new data arrives. The agenda always reads the cached copy, so it works offline. A failed fetch, a reply that isn't iCalendar (a login page) or one cut off before END:VCALENDAR keeps the old copy, is reported once, and is tried again after refresh minutes. auto_refresh = false fetches only on :Org ics_refresh. webcal:// is fetched as https://.
Actions
ics_refreshfetch every subscribed calendar now and redraw the agenda;:Org ics_refresh NAMEfetches one (names complete)ics_importon an agenda line of an event: add it toimport_fileas a heading with its timestamp, LOCATION, URL, CALENDAR and ICS_UID properties and the description as body text. Elsewhere: pick one of the nextimport_daysdays' events with vim.ui.select(). Only that occurrence of a repeating event is copied (with an ICS_RECURRENCE_ID property). An event already imported (same ICS_UID and ICS_RECURRENCE_ID) is not added again; when its time changed, the timestamp line of that heading is updated.
Neither has a default key; map them in mappings or run them with :Org.
What is read
- Line unfolding, text escapes, VEVENT with DTSTART and DTEND or
DURATION: dates (VALUE=DATE, all day), UTC times (...Z), floating
times and TZID= times.
- RRULE with FREQ YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY or
SECONDLY and INTERVAL, COUNT, UNTIL, BYDAY (MO, 2TU, -1FR),
BYMONTHDAY, BYMONTH, BYYEARDAY, BYHOUR, BYMINUTE, BYSECOND, BYSETPOS
and WKST; RDATE; EXDATE. A yearly Feb 29 skips non-leap years.
- RECURRENCE-ID: a changed or cancelled instance replaces the one it
overrides.
- STATUS:CANCELLED events are left out.
Not read: BYWEEKNO (ignored), RDATE periods, VTODO, VJOURNAL, VALARM
(no reminders), attendees and free/busy. A HOURLY or finer rule is
expanded up to 100,000 steps per range.
Time zones
Times are shown in the system's local zone, or intimezone(an IANA name such as "Europe/Paris") when set. ATZIDis resolved, in order, as: 1. a name fromtz_aliases, your own{ TZID = "IANA/Name" }table; 2. a zone of the system time zone database (/usr/share/zoneinfoor$TZDIR), also inside Mozilla-style paths such as/mozilla.org/20050126_1/Europe/Berlin; its zoneinfo (TZif) file is read in Lua, transitions and the rule for later years, so daylight saving time follows the system's rules (a time skipped by a change uses the offset before it, a repeated one is its first occurrence, as RFC 5545 says); 3. the calendar's own VTIMEZONE (STANDARD and DAYLIGHT rules); 4. a built-in table of common Windows zone names ("W. Europe Standard Time", "Pacific Standard Time", ...) sent by Outlook and Exchange. A zone none of these know is read as local time, and:checkhealth orglists it. Without a zone database (e.g. on Windows) only VTIMEZONE, UTC and floating times convert correctly.
Options
calendarsthe calendars (above) ({})refreshminutes between fetches (60)auto_refreshfetch stale calendars on startup and every minute (true)cache_dirwhere fetched calendars are kept (stdpath("cache") .. "/org/ics")curlfetch command,-o FILE URLis appended ({ "curl", "-fsSL", "--max-time", "30" })timezoneIANA zone to show times in (nil: the system's)tz_aliasesextra TZID -> IANA zone names ({})show_locationappend " (location)" to titles (true)formatfunction(event, calendar)returning the title (nil)facehighlight group of the titles ("OrgAgendaDiary")import_filefileics_importappends to (nil:default_notes_file)import_daysdays aheadics_importoffers outside the agenda (30)
The parser is usable on its own: require("org.extensions.ics.parser").parse(text) returns the events, and .occurrences(calendar, from_day, to_day, { timezone = ... }) expands them. Other views read the configured calendars with require("org.extensions.ics").events(from_day, to_day) (day numbers as org.date:days()): plain tables sorted by start, with calendar, category, uid, title, summary, location, description, url, all_day, start/stop (wall-clock seconds, stop exclusive), start_day/end_day, start_time/end_time (minutes, nil when all day) and timestamp (the org timestamp). The agenda's source is require("org.agenda.items").day_sources.ics.
:checkhealth org shows each calendar's event count and fetch age, failed fetches, unknown zones, and whether curl is installed (needed only for url calendars).