Привет, Хабр! Меня зовут Денис Басковский. Я фронтенд-разработчик в билетном сервисе Ticketland.ru (принадлежит МТС Live). В одном из пет-проектов мне понадобилось управлять календарными событиями: ставить время начала и окончания, добавлять комментарии и посылать оповещения. Обычно такая информация хранится и передается в .ics-файлах, описанных в спецификации iCalendar. Благодаря этому формату многие современные календарные приложения могут синхронизировать данные между собой.

В этой статье я расскажу об особенностях этой спецификации, приведу примеры использования .ics-файлов и поделюсь своей библиотекой на TypeScript для их генерации. Все подробности — под катом.


Что может iCalendar

Унификация работы с календарями прошла несколько этапов:

  • Первый шаг сделала в 1996 году компания Versit Consortium, которая разработала стандарт vCalendar. 

  • В 1998 году IETF опубликовал стандарт RFC 2445, основанный на опыте использования vCalendar.

  • В 2009 году на замену RFC 2445 появились RFC 5545 и его расширение iCalendar протоколом iTIP, описанным в RFC 5546.

  • В 2012 году RFC 6638 добавил поддержку планирования (scheduling) в CalDAV на основе протокола iTIP.

Из достоинств этого формата можно отметить:

  • Универсальность. Сейчас iCalendar используется всеми современными календарными приложениями и сервисами: Microsoft Outlook, Google Calendar, Apple Calendar и так далее. Он обеспечивает им совместимость и позволяет синхронизироваться. 

  • Легкость обмена данными. Синхронизация между разными приложениями происходит с помощью экспорта и импорта .ics-файлов.

  • Гибкость. iCalendar поддерживает сложные сценарии использования: повторяющиеся события, задачи с приоритетами, уведомления. 

  • Поддержка протокола CalDAV. Позволяет организовать работу с календарными данными в распределенных системах, обеспечивая синхронизацию и управление событиями между разными устройствами и приложениями через HTTP-запросы к серверу, поддерживающему CalDAV. Подробно об этом расскажу ниже. 

Нужно учитывать, что при создании сложных повторяющихся событий с большим числом исключений структура ics-файла будет усложняться, а размер файла увеличиваться.

Что входит в файл календаря

Пример .ics-файла:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//id//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:12345678-1234-1234-1234-123456789012
DTSTART:20240816T150000Z
SUMMARY:title
DESCRIPTION:description
END:VEVENT
END:VCALENDAR

Файлы .ics используют текстовые данные и включают компоненты, каждый из которых служит для своей цели. Вот они:

  • VJOURNAL хранит записи в журнале. С его помощью можно вести личные заметки, записывать события в течение дня. 

  • VEVENT описывает события или встречи и включает дату и время начала и окончания, местоположение, информации об организаторе и участниках, он также может содержать информацию о повторениях (RRULE), статус события, категорию и пр. Применяется для создания событий в календаре, отправки и получения откликов.

  • VTODO описывает параметры задачи: срок выполнения, приоритет, текущий статус и прогресс.

  • VALARM настраивает оповещения, связанные с VEVENT или VTODO. С его помощью можно завести оповещение, настроить тип, время и регулярность уведомления.

  • VFREEBUSY используется для описания периодов свободного или занятого времени.

Пример использования

В распределенных системах клиентские приложения — например, календари на мобильных устройствах или десктопах — работают с событиями на серверах через протокол CalDAV и сохраняют полную совместимость с форматом iCalendar.

Google Calendar использует CalDAV для синхронизации с внешними клиентами, поддерживающими этот протокол. А Google Calendar API, хоть и использует собственную структуру данных для работы с событиями, поддерживает стандарт iCalendar. Так появляется возможность синхронизироваться с клиентами, которые работают только c iCalendar.

Microsoft Outlook поддерживает импорт и экспорт .ics-файлов, а это позволяет обмениваться событиями с другими календарными системами. Microsoft Exchange Server использует .ics-файлы для обмена событиями и задачами между Outlook и другими клиентами.

Как работать с .ics-файлами

У .ics-файла текстовый тип и mime-type text/calendar. В самом простом варианте его можно создать вручную в текстовом редакторе и проверить на ошибки с помощью любого стороннего валидатора, поддерживающего RFC 5545.

Другой вариант создания .ics-файлов — использовать готовые библиотеки. Для JavaScript я нашел две:

  • icalendar.js — заброшенный проект, последняя правка была десять лет назад. Функционирует только в Nodejs, требует полифилы для работы в браузере.

  • rrule.js — более живой проект, специализирующийся на генерации и управлении повторяющимися событиями (RRULE). Эта библиотека предназначена для использования как в Node.js, так и в браузерах, однако для создания полных .ics-файлов могут потребоваться дополнительные библиотеки. Размер библиотеки 687 Кб, есть зависимость пакета tslib.

В своем пет-проекте я использую стандарт RFC 5545. Так как готовые варианты библиотек меня не устроили, я реализовал создание ics-файлов в собственной библиотеке ical-browser на TypeScript. В отличие от rrule.js и icalendar.js библиотека работает в том числе и в браузерах без создания тяжелых зависимостей. У меня много планов по ее доработке — пишите в личку, если хотите помочь или нужна какая-то дополнительная функциональность.

Пример работы с ical-browser

Сначала установим пакет:

npm install ical-browser

Подключим библиотеку к своему проекту, поддерживающему ESM-модули. Работать будем с типами Event, Todo, Journal и Alarm:

import { todo as createTodo,
  event as createEvent,
  journal as createJournal,
  alarm as createAlarm,
  default as icalendar, } from 'ical-browser'

// Раньше можно было использовать любое значение, но для повышения безопасности требуется уникальное значение идентификатора, например uuidv1
const uid = 'c7614cff-3560-4a00-9152-d25cc1fe077d',

const event = createEvent({
    uid,
    location: 'Online',
    geo: [37.5739497,-85.7399606],
    summary: 'Event summary',
    description: 'Event description',
    stamp: new Date(),
    start: new Date('2024-01-01T10:10:00.611Z'),
    end: new Date('2024-01-02T10:12:00.611Z'),
    // Прикладываем документ в формате base64. В данном случае это будет пиксель в формате gif
    attach: [
      'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQEAAAAACwAAAAAAQABAAACAkQBADs=',
    ],
    organizer: 'Jane Doe',
    attendee: 'CN=John Smith:mailto:john.smith@example.com',
    url: new URL('https://example.com#event'),
})

const todo = createTodo({
    uid,
    stamp: new Date(),
    due: new Date(),
    summary: 'Task summary',
    description: 'Task description',
    priority: 1,
    status: 'CONFIRMED',
})

const journal = createJournal({
    uid,
    stamp: new Date(),
    due: new Date(),
    summary: 'Journal summary',
    description: 'Journal description',
})

const alarm = createAlarm({
    uid,
    trigger: '-PT5M',
    description: 'Reminder',
    action: 'DISPLAY',
})

// Cклеиваем данные и генерируем файл
const str = icalendar('ID’, { event, todo, journal, alarm })
const file = new File([new TextEncoder().encode(str)], 'example.ics', {
  type: 'text/calendar',
})

После запуска пробуем открыть созданный файл:

В Apple Calendar мы увидим новое событие
В Apple Calendar мы увидим новое событие

Важно учитывать, что время берется с указанием таймзоны. Поэтому в моем примере значением new Date('2024-01-01T10:10:00.611Z') для Москвы станет 13:10.

Итог

iCalendar — это гибкий и мощный стандарт для работы с календарными данными, который поддерживается большинством современных приложений и сервисов. Файлы .ics легко создаются и импортируются во все самые популярные приложения для управления временем. 

Надеюсь, моя статья помогла вам лучше понять стандарт iCalendar и то, как интегрировать его в свои проекты. Если у вас возникли вопросы или есть чем дополнить статью — пишите в комментариях.

Комментарии (4)


  1. format1981
    03.09.2024 06:42
    +4

    Дата публикации поста не случайно была выбрана?


    1. DvoiNic
      03.09.2024 06:42
      +5

      И где раздел VROTATE ? :-)


  1. aleksejs1
    03.09.2024 06:42

    Писать свою либу для этого - это сложная затея. В примерах вы генерируете файлы, а потом читаете их же. Но в рельной жизни, вы генерируете файлы, а читает их кто-то другой (это ещё не страшно), а вот когда генерирует файлы кто-то другой, а вашей либе надо их прочитать - вот тут начинается веселье.

    Календари крупных компаний вообще ушли от этих стандартов, и у них для синхронизаций есть собственные нативные API. Да, они поддерживают импорт и экспорт, но и у них не всё так гладко. При переносе ивентов с айфона на андройд или обратно, могут появляться артефакты.

    А данные, врутри этих файлов далеко уходят за спецификацию календарей. Чего только стоит одно только переименование Kiev в Kyiv. Ещё не факт что вам нормально удастся читать все часовые поеса всех операционных систем.


    1. qertis Автор
      03.09.2024 06:42

      Согласен, надстройки свои у каждого производителя, тот же MS Office использует свой собственный стандарт.
      Для чтения стандартных iCal файлов могу порекомендовать библиотеку ical.js.