Task 304: .ICS File Format

Task 304: .ICS File Format

File Format Specifications for .ICS

The .ICS file format is used for iCalendar data, a standard for exchanging calendar and scheduling information. It is defined in RFC 5545 (Internet Calendaring and Scheduling Core Object Specification) by the IETF. The format is text-based, human-readable, and designed for interoperability across calendar applications like Google Calendar, Outlook, and Apple Calendar. It supports components such as events (VEVENT), tasks (VTODO), and time zones (VTIMEZONE).

List of all the properties of this file format intrinsic to its file system:

  • File Extension: .ics
  • MIME Type: text/calendar
  • Character Encoding: UTF-8 (default; other charsets can be specified via MIME parameters)
  • Line Terminator: CRLF (\r\n)
  • Line Length Limit: 75 octets recommended (excluding the line break)
  • Line Folding: Enabled for lines exceeding 75 octets, using a CRLF followed by a single SPACE or horizontal TAB
  • File Signature: Starts with "BEGIN:VCALENDAR" (case-sensitive, followed by CRLF)
  • File Trailer: Ends with "END:VCALENDAR" (case-sensitive, preceded by CRLF)

Two direct download links for files of format .ICS:

Ghost blog embedded HTML JavaScript for drag-and-drop .ICS file dump:

ICS File Property Dumper
Drag and drop a .ICS file here

Python class for .ICS handling:

import os

class IcsHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.content = None
        self.properties = {}

    def read(self):
        with open(self.filepath, 'r', encoding='utf-8') as f:
            self.content = f.read()
        self.decode()

    def decode(self):
        if not self.content:
            raise ValueError("No content read from file.")
        ext = os.path.splitext(self.filepath)[1].lower()
        self.properties['File Extension'] = ext if ext == '.ics' else 'Invalid'
        self.properties['MIME Type'] = 'text/calendar'
        self.properties['Character Encoding'] = 'UTF-8'
        self.properties['Line Terminator'] = 'CRLF' if '\r\n' in self.content else 'Unknown'
        lines = self.content.split('\r\n')
        max_length = max(len(line) for line in lines)
        has_folding = any(line.startswith(' ') or line.startswith('\t') for line in lines)
        self.properties['Line Length Limit'] = f'Observed max: {max_length} octets (recommended <=75)'
        self.properties['Line Folding'] = 'Yes' if has_folding else 'No'
        self.properties['File Signature'] = 'BEGIN:VCALENDAR' if self.content.strip().startswith('BEGIN:VCALENDAR') else 'Invalid/Missing'
        self.properties['File Trailer'] = 'END:VCALENDAR' if self.content.strip().endswith('END:VCALENDAR') else 'Invalid/Missing'

    def print_properties(self):
        if not self.properties:
            print("No properties decoded.")
            return
        print("ICS File Properties:")
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, output_path=None):
        if not self.content:
            raise ValueError("No content to write.")
        path = output_path or self.filepath
        with open(path, 'w', encoding='utf-8') as f:
            f.write(self.content)
        print(f"File written to {path}")

# Example usage:
# handler = IcsHandler('sample.ics')
# handler.read()
# handler.print_properties()
# handler.write('output.ics')

Java class for .ICS handling:

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class IcsHandler {
    private String filepath;
    private String content;
    private Map<String, String> properties = new HashMap<>();

    public IcsHandler(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\r\n");
            }
        }
        content = sb.toString();
        decode();
    }

    private void decode() {
        if (content == null) {
            throw new IllegalStateException("No content read from file.");
        }
        String ext = filepath.substring(filepath.lastIndexOf('.') + 1).toLowerCase();
        properties.put("File Extension", ext.equals("ics") ? ".ics" : "Invalid");
        properties.put("MIME Type", "text/calendar");
        properties.put("Character Encoding", "UTF-8");
        properties.put("Line Terminator", content.contains("\r\n") ? "CRLF" : "Unknown");
        String[] lines = content.split("\r\n");
        int maxLength = 0;
        boolean hasFolding = false;
        for (String line : lines) {
            if (line.length() > maxLength) maxLength = line.length();
            if (line.startsWith(" ") || line.startsWith("\t")) hasFolding = true;
        }
        properties.put("Line Length Limit", "Observed max: " + maxLength + " octets (recommended <=75)");
        properties.put("Line Folding", hasFolding ? "Yes" : "No");
        properties.put("File Signature", content.trim().startsWith("BEGIN:VCALENDAR") ? "BEGIN:VCALENDAR" : "Invalid/Missing");
        properties.put("File Trailer", content.trim().endsWith("END:VCALENDAR") ? "END:VCALENDAR" : "Invalid/Missing");
    }

    public void printProperties() {
        if (properties.isEmpty()) {
            System.out.println("No properties decoded.");
            return;
        }
        System.out.println("ICS File Properties:");
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputPath) throws IOException {
        if (content == null) {
            throw new IllegalStateException("No content to write.");
        }
        String path = (outputPath != null) ? outputPath : filepath;
        try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path), StandardCharsets.UTF_8))) {
            bw.write(content);
        }
        System.out.println("File written to " + path);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     IcsHandler handler = new IcsHandler("sample.ics");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("output.ics");
    // }
}

JavaScript class for .ICS handling (Node.js compatible):

const fs = require('fs');

class IcsHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.content = null;
        this.properties = {};
    }

    read() {
        this.content = fs.readFileSync(this.filepath, 'utf-8');
        this.decode();
    }

    decode() {
        if (!this.content) {
            throw new Error('No content read from file.');
        }
        const ext = this.filepath.toLowerCase().split('.').pop();
        this.properties['File Extension'] = (ext === 'ics') ? '.ics' : 'Invalid';
        this.properties['MIME Type'] = 'text/calendar';
        this.properties['Character Encoding'] = 'UTF-8';
        this.properties['Line Terminator'] = this.content.includes('\r\n') ? 'CRLF' : 'Unknown';
        const lines = this.content.split('\r\n');
        let maxLength = 0;
        let hasFolding = false;
        lines.forEach(line => {
            if (line.length > maxLength) maxLength = line.length;
            if (line.startsWith(' ') || line.startsWith('\t')) hasFolding = true;
        });
        this.properties['Line Length Limit'] = `Observed max: ${maxLength} octets (recommended <=75)`;
        this.properties['Line Folding'] = hasFolding ? 'Yes' : 'No';
        this.properties['File Signature'] = this.content.trim().startsWith('BEGIN:VCALENDAR') ? 'BEGIN:VCALENDAR' : 'Invalid/Missing';
        this.properties['File Trailer'] = this.content.trim().endsWith('END:VCALENDAR') ? 'END:VCALENDAR' : 'Invalid/Missing';
    }

    printProperties() {
        if (Object.keys(this.properties).length === 0) {
            console.log('No properties decoded.');
            return;
        }
        console.log('ICS File Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }

    write(outputPath = null) {
        if (!this.content) {
            throw new Error('No content to write.');
        }
        const path = outputPath || this.filepath;
        fs.writeFileSync(path, this.content, 'utf-8');
        console.log(`File written to ${path}`);
    }
}

// Example usage:
// const handler = new IcsHandler('sample.ics');
// handler.read();
// handler.printProperties();
// handler.write('output.ics');

C class for .ICS handling (using C++ for class support and file handling):

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <algorithm>

class IcsHandler {
private:
    std::string filepath;
    std::string content;
    std::map<std::string, std::string> properties;

public:
    IcsHandler(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::in | std::ios::binary);
        if (!file) {
            throw std::runtime_error("Failed to open file.");
        }
        std::string line;
        content.clear();
        while (std::getline(file, line, '\n')) {
            if (!line.empty() && line.back() == '\r') line.pop_back();
            content += line + "\r\n";
        }
        file.close();
        decode();
    }

    void decode() {
        if (content.empty()) {
            throw std::runtime_error("No content read from file.");
        }
        size_t dotPos = filepath.find_last_of('.');
        std::string ext = (dotPos != std::string::npos) ? filepath.substr(dotPos + 1) : "";
        std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
        properties["File Extension"] = (ext == "ics") ? ".ics" : "Invalid";
        properties["MIME Type"] = "text/calendar";
        properties["Character Encoding"] = "UTF-8";
        properties["Line Terminator"] = (content.find("\r\n") != std::string::npos) ? "CRLF" : "Unknown";
        std::string::size_type pos = 0;
        int maxLength = 0;
        bool hasFolding = false;
        while ((pos = content.find("\r\n", pos)) != std::string::npos) {
            std::string::size_type start = (pos == 0) ? 0 : content.rfind("\r\n", pos - 2) + 2;
            int len = pos - start;
            if (len > maxLength) maxLength = len;
            if (start < content.size() && (content[start] == ' ' || content[start] == '\t')) hasFolding = true;
            pos += 2;
        }
        properties["Line Length Limit"] = "Observed max: " + std::to_string(maxLength) + " octets (recommended <=75)";
        properties["Line Folding"] = hasFolding ? "Yes" : "No";
        std::string trimmed = content;
        trimmed.erase(0, trimmed.find_first_not_of(" \t\r\n"));
        trimmed.erase(trimmed.find_last_not_of(" \t\r\n") + 1);
        properties["File Signature"] = (trimmed.rfind("BEGIN:VCALENDAR", 0) == 0) ? "BEGIN:VCALENDAR" : "Invalid/Missing";
        properties["File Trailer"] = (trimmed.find("END:VCALENDAR", trimmed.size() - 13) != std::string::npos) ? "END:VCALENDAR" : "Invalid/Missing";
    }

    void printProperties() const {
        if (properties.empty()) {
            std::cout << "No properties decoded." << std::endl;
            return;
        }
        std::cout << "ICS File Properties:" << std::endl;
        for (const auto& pair : properties) {
            std::cout << pair.first << ": " << pair.second << std::endl;
        }
    }

    void write(const std::string& outputPath = "") const {
        if (content.empty()) {
            throw std::runtime_error("No content to write.");
        }
        std::string path = outputPath.empty() ? filepath : outputPath;
        std::ofstream file(path, std::ios::out | std::ios::binary);
        if (!file) {
            throw std::runtime_error("Failed to write file.");
        }
        file << content;
        file.close();
        std::cout << "File written to " << path << std::endl;
    }
};

// Example usage:
// int main() {
//     try {
//         IcsHandler handler("sample.ics");
//         handler.read();
//         handler.printProperties();
//         handler.write("output.ics");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }