Skip to content

← All notes

29 July 20263 minStandards / Encoding / Calendars

An em dash broke a calendar invite

RFC 5545 folds lines at 75 octets, not 75 characters. If your copy has em dashes, those are not the same number.

Generating an .ics file is one of those tasks that looks finished long before it is. The structure is trivial — a few key-value lines between BEGIN and END — so the first version works, opens in Google Calendar, and gets waved through.

Then the spec asks for something easy to misread. RFC 5545 says lines must not exceed 75 octets, and that longer ones are folded by inserting CRLF and a single space. The naive implementation reads "75" and reaches for length.

// Wrong, and passes every test written in ASCII
if (line.length <= 75) return line

String.length counts UTF-16 code units, not bytes. Ordinary Latin text makes the two identical, which is exactly why this survives review. An em dash is three bytes. A curly quote is three bytes. An emoji is four, and takes two code units while it is at it.

This site’s copy is full of em dashes. A description line counted at 75 characters was going out at well over 100 octets — invalid, and rejected outright by stricter parsers.

Measuring the thing the spec measures

const enc = new TextEncoder()
let current = ''
let limit = 75
// Array.from splits on code points, so a surrogate pair is never cut in half.
for (const char of Array.from(line)) {
  if (enc.encode(current + char).length > limit) {
    out.push(current)
    current = char
    limit = 74 // continuation lines lose an octet to the leading space
  } else {
    current += char
  }
}

Two details carry their weight there. Iterating with Array.from walks code points, so an emoji is never split down the middle into two invalid halves. And the limit drops to 74 after the first line, because the continuation space is itself an octet — an off-by-one that only shows up on the longest lines, which are the ones least likely to be in a test.

Any spec that says "octets" is telling you your test data is too polite.

Building something like this? Let's talk.

Start a project ↗