Exchange_iCloud_Calendar_Sync

0

Описание

AppleScript для односторонней синхронизации событий из Exchange-календаря в iCloud-календарь на macOS.

Языки

  • AppleScript100%
README.md

Exchange → iCloud Calendar Sync

AppleScript для односторонней синхронизации событий из Exchange-календаря в iCloud-календарь на macOS.

Зачем это нужно

На Mac календарь Exchange (Outlook) и iCloud — это два разных мира. iPhone/Apple Watch показывают оба, но:

  • Exchange-встречи могут не попадать в виджеты
  • На Apple Watch уведомления от Exchange приходят криво или не приходят
  • Если хочешь видеть рабочий календарь в iCloud-виджете — нужен «зеркальный» календарь

Этот скрипт берёт все события из Exchange-календаря (включая повторяющиеся!) и копирует их в iCloud-календарь. Запускается по расписанию — ты просто видишь актуальное расписание везде.


Что внутри

ФайлНазначение
exchange_sync_v4.applescript
Основной скрипт синхронизации
exchange_sync_cleanup.applescript
Удаляет все синхронизированные события из целевого календаря
calendar_diagnostic.applescript
Диагностика — показывает что Calendar.app видит в твоих календарях

Быстрый старт

1. Создай целевой календарь

Открой Calendar.app → Файл → Новый календарь → назови его

iCLOUD Work Mirror
(или как хочешь, но тогда поменяй настройку в скрипте).

Календарь должен быть именно в iCloud, не локальный.

2. Настрой скрипт

Открой

exchange_sync_v4.applescript
в Script Editor (Редактор скриптов). В самом верху найди блок настроек:

Как узнать точное название Exchange-календаря:

  1. Открой Calendar.app
  2. В левой панели посмотри как называется твой рабочий календарь (обычно под заголовком «Exchange»)
  3. Скопируй название точно как написано — с учётом пробелов и регистра

3. Запусти

В Script Editor нажми ▶ Запустить (Cmd+R). Первый раз macOS спросит разрешение на доступ к Calendar.app — разреши.

4. Настрой автозапуск (опционально)

Чтобы синхронизация работала автоматически, используй launchd (встроенный планировщик macOS).

Создай файл

~/Library/LaunchAgents/com.user.exchange-sync.plist
:

Замени

ТВОЙ_ЮЗЕРНЕЙМ
на своё имя пользователя. Затем:


Как это работает

Общая схема

Exchange-календарь (EX Work) iCloud-календарь (Work Mirror) ┌──────────────────────────┐ ┌──────────────────────────┐ │ Стендап 10:00 (повтор) │──────▶ │ Стендап 10:00 пн │ │ Митинг 14:00 │──────▶ │ Стендап 10:00 вт │ │ 1-on-1 16:00 (повтор) │──────▶ │ Стендап 10:00 ср │ │ │──────▶ │ Митинг 14:00 │ │ │──────▶ │ 1-on-1 16:00 пн │ │ │──────▶ │ 1-on-1 16:00 чт │ └──────────────────────────┘ └──────────────────────────┘

Пошагово (что делает скрипт при каждом запуске)

Шаг 1 — Проверка. Скрипт проверяет что оба календаря (источник и цель) существуют. Если нет — ошибка с понятным сообщением.

Шаг 2 — Чтение всех событий. Скрипт читает все события из Exchange-календаря. Именно все, не только за период — потому что Calendar.app не умеет разворачивать повторяющиеся события по диапазону дат (подробнее ниже).

Шаг 3 — Обработка каждого события:

  • Обычное событие (без повторов): если дата попадает в диапазон «вчера → +30 дней» — берём его.
  • Повторяющееся событие: скрипт читает правило повтора (RRULE) и сам вычисляет все даты вхождений в нашем диапазоне. Поддерживаются: ежедневные, еженедельные (в т.ч. по конкретным дням — пн, ср, пт), ежемесячные, ежегодные повторы.
  • Дубликаты одного и того же события (Calendar.app иногда возвращает их) отбрасываются по UID.
  • Каждому вхождению присваивается уникальный ID:
    UID события + метка времени
    . Например, для стендапа в понедельник и стендапа во вторник — это два разных ID, хотя UID события один.

Шаг 4 — Сравнение с целевым календарём. Скрипт читает целевой iCloud-календарь и строит индекс всех событий, которые были созданы предыдущими синхронизациями (они содержат метку

[SYNC-ID:...]
в описании).

Шаг 5 — Синхронизация. Для каждого события из источника:

СитуацияДействие
В целевом нет такого IDСоздаём новое событие
ID есть, но данные изменились (название, время, место)Обновляем существующее
ID есть, данные совпадаютНичего не делаем

Шаг 6 — Очистка. Если в целевом календаре есть событие с меткой

[SYNC-ID:...]
, но в источнике такого события больше нет (удалили или перенесли) — скрипт удаляет его из iCloud-календаря. Так календарь не зарастает мусором.

Шаг 7 — Отчёт. В лог (Console.app → Все сообщения) выводится подробный отчёт:

======================================== EXCHANGE SYNC - REPORT ======================================== Range: 08.02.2026 00:00 -> 11.03.2026 00:00 Source: 47 events Time: 3 min 12 sec ======================================== RESULTS: Created: 12 Updated: 3 Deleted: 1 -- Created -- NEW: Дейли EDU | 10.02.2026 10:00 [repeat] NEW: Дейли EDU | 11.02.2026 10:00 [repeat] NEW: FW: DSW Трек фин-юр | 10.02.2026 12:30 [repeat] ... -- Deleted -- DEL: Старый митинг ======================================== Sync completed OK ========================================

Плюс macOS-уведомление с краткой статистикой:

+12 ~3 -1
.


Повторяющиеся события — как это работает

Проблема

Calendar.app через AppleScript не разворачивает повторяющиеся события. Если у тебя стендап каждый день с 1 января, Calendar.app вернёт одно событие с

start date: 1 января
и строкой
recurrence: FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR
.

Он не создаёт отдельные экземпляры для каждого понедельника, вторника и т.д. — для AppleScript это одно событие.

Решение

Скрипт сам парсит строку RRULE (стандарт iCalendar RFC 5545) и вычисляет все даты вхождений в заданном диапазоне.

Поддерживаемые типы повторов

RRULEПримерПоддержка
FREQ=DAILY
Каждый день / каждые N дней
FREQ=WEEKLY
Каждую неделю
FREQ=WEEKLY;BYDAY=MO,WE,FR
По конкретным дням недели
FREQ=MONTHLY
Каждый месяц
FREQ=YEARLY
Каждый год
INTERVAL=N
Каждые N периодов
COUNT=N
Не более N повторений
UNTIL=20260401T205959Z
До конкретной даты

Не поддерживается

  • BYDAY=2TU
    (второй вторник месяца) — порядковые префиксы игнорируются, берётся только день
  • BYMONTHDAY
    ,
    BYSETPOS
    — экзотические правила
  • EXDATE
    — исключения дат (если одно вхождение удалено из серии)
  • Разные часовые пояса в UNTIL

Для 95% рабочих календарей этого достаточно.


Скрипт очистки

exchange_sync_cleanup.applescript
— удаляет из целевого календаря все события, созданные синхронизацией.

Когда использовать:

  • Что-то пошло не так и нужно начать с чистого листа
  • Перед первым запуском новой версии скрипта
  • Если нужно полностью отключить синхронизацию

Как использовать:

  1. Открой в Script Editor
  2. Проверь что
    targetCalName
    совпадает с настройкой в основном скрипте
  3. Нажми ▶ Запустить
  4. Подтверди удаление в диалоговом окне

Скрипт удаляет только события с меткой

[SYNC-ID:...]
в описании. Если ты вручную создавал события в целевом календаре — они останутся.


Диагностический скрипт

calendar_diagnostic.applescript
— показывает сырые данные из Calendar.app. Полезен для отладки.

Что покажет:

  • Сколько событий возвращает фильтр по дате vs. всего в календаре
  • Список повторяющихся событий с их RRULE-строками
  • Попадают ли повторяющиеся события в фильтр по дате (спойлер: нет)
  • Поиск по конкретному названию (по умолчанию DSW)

Ограничения

  • Односторонняя синхронизация. Exchange → iCloud. Изменения в iCloud-календаре НЕ попадают обратно в Exchange.
  • Не редактируй события в целевом календаре вручную — при следующей синхронизации скрипт перезапишет их.
  • Производительность. Скрипт читает все 690+ событий из Exchange. На большом календаре может работать 1-3 минуты.
  • macOS only. AppleScript работает только на Mac.
  • Нет синхронизации участников и напоминаний. Копируются: название, время, место, описание, статус «весь день». Не копируются: участники, напоминания, вложения.

Устранение неполадок

Скрипт не компилируется

Убедись что открываешь файл именно в Script Editor (Редактор скриптов), а не в текстовом редакторе. Кодировка файла должна быть UTF-8.

«Календарь не найден»

Название календаря в настройках должно точно совпадать с тем, что показывает Calendar.app. Откройте Calendar.app и сверьте — с учётом пробелов, регистра, спецсимволов.

Повторяющиеся события не синхронизируются

Запусти

calendar_diagnostic.applescript
и проверь:

  1. Есть ли у события поле
    recurrence
    (не
    missing value
    )
  2. Какой формат RRULE — поддерживается ли он (см. таблицу выше)

Дубликаты в целевом календаре

  1. Запусти
    exchange_sync_cleanup.applescript
    — удалит все синхронизированные события
  2. Запусти
    exchange_sync_v4.applescript
    заново

Где смотреть логи

Console.app (Консоль) → в поиске набери

osascript
. Там будет подробный отчёт о каждой синхронизации.

Или в Script Editor → меню Окно → Журнал (Window → Log) — если запускаешь вручную.


Системные требования

  • macOS 10.14 Mojave и выше
  • Calendar.app с подключённым Exchange-аккаунтом
  • iCloud-аккаунт с включённым календарём
  • Разрешение на автоматизацию (Системные настройки → Конфиденциальность → Автоматизация)

Лицензия

MIT — делай что хочешь.