ICS Text Cut Off or Garbled? Check UTF-8 and Line Folding

CalendarLint EditorialPublished 5 min read
Applies to: Google Calendar, Apple Calendar, Outlook

Diagnose garbled ICS text with UTF-8 byte checks, correct content-line unfolding, and a runnable folding example that preserves Chinese characters and emoji.

On this page

When an ICS title loses characters or a description stops halfway through, compare the generated bytes, downloaded bytes, and unfolded property value. The failure may occur before the calendar app reads the file.

iCalendar uses UTF-8 by default. Its content lines use CRLF endings, and lines should be folded at no more than 75 octets, excluding the line break. This is a byte-based recommendation, not a universal rejection threshold (RFC 5545 §3.1 and character sets).

Key Takeaways

  • Count UTF-8 bytes rather than JavaScript string length when folding ICS lines.
  • Unfold by removing CRLF and exactly one following space or tab.
  • A folded physical line break differs from an escaped \n inside a text value.
  • Compare text before and after serialization; successful parsing alone does not prove it survived.

Find the stage where text changes

Keep the original file as bytes. Opening and saving it in an editor may normalize line endings or replace invalid characters, hiding the evidence.

Capture these checkpoints:

| Checkpoint | Compare | What a mismatch suggests | | --- | --- | --- | | Application value | The title or description before export | Source data is already incomplete | | Generated ICS bytes | UTF-8 encoding and physical line boundaries | Serializer or encoding problem | | Downloaded or received bytes | Compare with the generated file | Delivery or transformation needs investigation | | Unfolded property | Reconstructed logical line | Incorrect continuation handling | | Parsed property and visible text | Full parsed string versus displayed result | Parser behavior or display truncation |

If the parsed description is complete but a preview shows only its first sentence, inspect the full event details before changing the file. A shortened card is different from data loss.

Understand what a continuation space does

These two physical lines form one logical property:

SUMMARY:Design
  review

The second line starts with two spaces. Unfolding removes the line break and the first space. The second space remains part of the value, producing Design review.

With only one leading space, the result would be Designreview. The continuation marker is structural; it does not automatically insert a word separator.

Do not use .trim() on every physical line before unfolding. That can erase both the marker and meaningful whitespace. The standard's operation removes exactly the fold sequence, rather than all surrounding whitespace.

Reproduce a UTF-8 boundary with Chinese text and emoji

Run this complete example with Python 3. It does not write files. The helper folds one logical content line at Unicode character boundaries, leaving room for the continuation marker.

def fold_line(line, limit=75):
    if "\r" in line or "\n" in line:
        raise ValueError("Expected one logical content line")
    rows = []
    row = ""
    size = 0
    for char in line:
        width = len(char.encode("utf-8"))
        if size + width > limit:
            rows.append(row)
            row = " "
            size = 1
        row += char
        size += width
    rows.append(row)
    return "\r\n".join(rows)

logical = "SUMMARY:" + ("A" * 66) + "会🙂 review"
folded = fold_line(logical)
rows = folded.split("\r\n")
unfolded = folded.replace("\r\n ", "").replace("\r\n\t", "")

assert unfolded == logical
assert all(len(row.encode("utf-8")) <= 75 for row in rows)
assert "SUMMARY:Design\r\n  review".replace(
    "\r\n ", ""
) == "SUMMARY:Design review"

print("Physical line bytes:", [len(row.encode("utf-8")) for row in rows])
print("Round trip:", unfolded == logical)
print("Suffix UTF-8:", "会🙂".encode("utf-8").hex(" "))

Expected output:

Physical line bytes: [74, 15]
Round trip: True
Suffix UTF-8: e4 bc 9a f0 9f 99 82

The first line already contains 74 bytes before 会. That character needs three bytes, so the helper starts a continuation line before it. The continuation begins with one structural space.

This is a folding demonstration, not a complete ICS serializer. It does not escape text, construct parameters, or validate calendar components. Use a maintained iCalendar library for production output.

If a producer splits a multibyte character between physical lines, decoding each line independently can corrupt it. Preserve the bytes, unfold correctly, and decode strictly so invalid sequences become visible instead of silently replacing them.

Keep text escapes separate from folding

In a TEXT value such as DESCRIPTION, a literal backslash is escaped as \\, a comma as \,, a semicolon as \;, and a text newline as \n or \N (RFC 5545's TEXT definition).

For example:

DESCRIPTION:Bring notes\, sketches\; review together.\nNext steps follow.

The escaped newline belongs inside the description. A physical CRLF followed by a continuation space merely wraps the same content line.

Escape the application's text value before folding the completed content line. On reading, unfold before interpreting the property and its escapes. Do not apply these text escape rules indiscriminately to every property: URI values and parameters have their own syntax.

Fix the serializer and verify the complete value

Repair the earliest stage where the discrepancy appears. If the exporter loses a space, changing the receiving calendar's display settings will not restore it. If the original bytes are intact, avoid re-encoding them repeatedly.

Test ASCII, Chinese, emoji, a meaningful space at the fold boundary, backslashes, commas, and a multiline description. Compare the entire parsed string with the intended value after serialization and parsing.

In CalendarLint, review the source and diagnostics to locate suspect properties. For exact byte offsets, use a binary inspection tool or code like the example; a formatted source view is not a substitute for a byte comparison.

Verification scope: the Python example was executed with Python 3 on November 5, 2026, including its byte counts, whitespace check, and round-trip assertions. No current defect in a named calendar client is alleged.