Import external iCal feeds into your calendar
Pull events from other systems into Northwind's calendar — e.g., a vendor's release calendar.
Published Sep 10, 2025
Northwind depends on a handful of vendors whose release dates, maintenance windows, and webinars live in their own iCal feeds. Google Calendar does support subscribing to a URL, but the refresh is slow and the events end up on a separate overlay that nobody enables. The releases get missed anyway.
This script pulls an iCal feed, parses out the events, and creates real events on a dedicated Northwind calendar — first-class, searchable, and visible to everyone subscribed. It also de-duplicates on every run so you can schedule it without piling up copies.
What you’ll need
- A dedicated calendar to import into (so you can show/hide the whole feed at
once). Grab its ID from Calendar settings and paste it into
TARGET_CAL. - The HTTPS URL of the iCal feed. Most vendors publish one under Settings → Integrations → Calendar.
- Nothing else — the parser handles standard
BEGIN:VEVENTblocks withSUMMARY,DTSTART, andDTENDlines, which covers the vast majority of feeds.
The script
// The calendar to import events into.
const TARGET_CAL = '1abcImportedCalId';
/**
* Fetches an iCal feed, parses the events, and creates any that are
* not already on the target calendar. Safe to run repeatedly.
*/
function importIcal(icalUrl) {
if (!icalUrl) {
Logger.log('No URL passed to importIcal — nothing to do.');
return;
}
const cal = CalendarApp.getCalendarById(TARGET_CAL);
if (!cal) {
Logger.log('Target calendar not found — check TARGET_CAL.');
return;
}
// 1. Pull the feed. muteHttpExceptions so we log a useful message
// instead of throwing on a 404 or a flaky vendor.
const res = UrlFetchApp.fetch(icalUrl, { muteHttpExceptions: true });
if (res.getResponseCode() !== 200) {
Logger.log('Fetch failed: ' + res.getResponseCode());
return;
}
const events = parseIcal(res.getContentText());
Logger.log('Parsed ' + events.length + ' events from feed.');
// 2. For each event, look for an existing one with the same title in
// the same window. If found, skip — otherwise create it.
let created = 0;
for (const e of events) {
const matches = cal.getEvents(e.start, e.end, { search: e.title });
if (matches.length) continue;
cal.createEvent(e.title, e.start, e.end);
created++;
}
Logger.log('Created ' + created + ' new events.');
}
/**
* Splits an iCal string on BEGIN:VEVENT and pulls SUMMARY/DTSTART/DTEND
* out of each block. Good enough for most vendor feeds.
*/
function parseIcal(text) {
const events = [];
const blocks = text.split(/BEGIN:VEVENT/).slice(1);
for (const b of blocks) {
const title = (b.match(/SUMMARY:(.+)/) || [])[1] || 'Event';
const start = parseDate((b.match(/DTSTART(?:;[^:]+)?:([^\r\n]+)/) || [])[1]);
const end = parseDate((b.match(/DTEND(?:;[^:]+)?:([^\r\n]+)/) || [])[1]);
if (start && end) events.push({ title: title.trim(), start, end });
}
return events;
}
/**
* Parses an iCal DTSTART/DTEND value into a Date. Handles the common
* `YYYYMMDDTHHMMSS` UTC form; ignores anything more exotic.
*/
function parseDate(s) {
if (!s) return null;
const m = s.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/);
if (!m) return null;
return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
}
/**
* Wrapper to run on a schedule against the Northwind vendor feed.
* Add one of these per feed you want to mirror.
*/
function importVendorFeed() {
importIcal('https://vendor.example.com/calendar.ics');
}
How it works
importIcalvalidates the URL and opens the target calendar, bailing out with a log line on either failure.- It fetches the feed with
muteHttpExceptions: trueso a transient 5xx from the vendor logs a message instead of crashing. - It hands the raw text to
parseIcal, which splits onBEGIN:VEVENTto get one block per event, then runs three small regexes to pull out the summary and the start/end timestamps. parseDatematches the commonYYYYMMDDTHHMMSSformat used by most feeds. Anything more exotic (timezones, all-day withVALUE=DATE) is skipped — fine for a vendor mirror, and you find out fast if you need more.- Back in
importIcal, it asks the target calendar for any existing event with the same title in the same window. Thesearchoption does a text match, which is enough to dedupe a stable feed. - Anything not already there gets created. The log line at the end tells you how much was new on this run.
Example run
Say the vendor’s feed contains a release window:
BEGIN:VEVENT
SUMMARY:Acme Cloud — v4.2 release
DTSTART:20250915T080000Z
DTEND:20250915T100000Z
END:VEVENT
On the first run, the Northwind import calendar gets a new “Acme Cloud — v4.2 release” event for 8am–10am UTC on 15 September 2025. On every later run, the search finds the existing event and skips it — no duplicates, no churn. When the vendor adds a new release, the next scheduled run picks it up.
Trigger it
Run it nightly so new feed entries show up by the morning:
- In the Apps Script editor, open Triggers (clock icon).
- Add a trigger for
importVendorFeed, event source Time-driven, type Day timer, time 2am to 3am. - Approve the authorisation prompt the first time.
Add one wrapper function per feed you want to mirror, and one trigger per wrapper.
Watch out for
- The parser is deliberately small. It handles
SUMMARY,DTSTART, andDTENDwith UTC timestamps — about 90% of feeds in the wild. If yours hasVTIMEZONE,RRULE, or all-dayVALUE=DATEentries you need, swap in a dedicated iCal library instead. - The dedup is by title within a time window. If the vendor renames an event
(“v4.2 release” → “v4.2 release (delayed)”) the next run will create a
second event. For a strict match, store the iCal
UIDin the event’s description and search on that. - Events created here are not synced. If the vendor moves a release a day later, your calendar still shows the old time. The honest fix is to wipe and re-import; the cheap fix is to delete by hand.
- A flaky vendor feed should not crash the run. The
muteHttpExceptionsflag plus the response-code check above keep the script polite about transient failures.
Related
Schedule personal habits and routines
Block recurring habits on Awadesh's calendar — gym, walks, deep-work mornings.
Updated Nov 5, 2025
Sync birthdays and anniversaries to Calendar
Populate recurring personal dates from a Sheet — the Northwind team rituals calendar.
Updated Nov 1, 2025
Generate recurring events with custom exceptions
Handle complex recurrence rules in code — every Tuesday except UK bank holidays.
Updated Oct 28, 2025
Auto-reschedule low-priority conflicts
Move flexible Northwind events around fixed ones — focus blocks bend, client calls don't.
Updated Oct 16, 2025
Build a contract-renewal calendar
Track Northwind's recurring-revenue renewal dates as calendar events for proactive sales.
Updated Oct 8, 2025