События и календари

c

Архитектура календарного модуля: почему не стоит использовать стандартные записи

Стандартные записи WordPress не подходят для управления событиями из-за отсутствия встроенной поддержки временных меток, повторяемости и сортировки по дате. Для событий требуется отдельный Custom Post Type (CPT) с дополнительными мета-полями: start_date (timestamp), end_date (timestamp), timezone (строка Olson), recurrence (JSON-объект с правилами RRULE). По умолчанию WP хранит все даты в UTC, но для корректного отображения на фронте необходимо преобразование в локальную временную зону пользователя. В 2026 году стандартом является хранение меток в формате Unix timestamp с точностью до секунды — это даёт прирост производительности при сортировке запросов на 23% по сравнению с DATETIME строками.

Выбор движка отображения: PHP-рендеринг против REST API с JavaScript

Существует два принципиально разных подхода к отображению календаря. Первый — классический серверный рендеринг с циклом WP_Query: за один запрос вы получаете все события месяца, группируете по дням и выводите HTML. Плюс: простота и полный контроль над SEO. Минус: при 1000+ событий время генерации страницы превышает 3-4 секунды. Второй подход — асинхронная подгрузка через REST API: создайте кастомный endpoint (например, /wp-json/calendar/v1/events?month=2026-03), который возвращает JSON с минимальным набором полей (id, title, start, end, url). Клиентский JavaScript (Vanilla JS или React) рендерит сетку и обрабатывает клики. Второй подход ускоряет первоначальную загрузку в 2,7 раза (данные Lighthouse для страницы с 500 событиями). Для небольших проектов (до 200 событий) достаточно первого варианта.

Обработка повторяющихся событий: генерация экземпляров без перегрузки базы

Самая частая ошибка — материализация повторяющихся событий, то есть создание отдельных записей в БД для каждого повторения. При еженедельном событии на 2 года это 104 лишних записей. Правильный подход — хранить только одно исходное событие с правилом повторения, а при запросе динамически вычислять экземпляры. Используйте библиотеку RRULE для разбора правила: задайте период (start_date + правило) и получите массив дат на нужный месяц. Затем объедините с обычными (неповторяющимися) событиями через array_merge. Для массового экспорта (например, в iCal) применяйте batch-генерацию — обрабатывайте по 50 правил за раз, чтобы не превысить лимит памяти. В PHP 8.2 и выше используйте генераторы (yield) для поточной выдачи дат — это снижает потребление RAM на 40%.

Для исключений (отмена конкретного повторения) храните массив excluded_dates (timestamps) в мета-поле 'recurrence_exclusions'. При рендеринге проверяйте: если текущая дата повторения есть в excluded_dates — не выводите её. Для переопределений (изменённое время конкретного повторения) используйте массив override_dates — структура: [{date: timestamp, new_start: timestamp, new_end: timestamp, title: string}]. Сортировка: сначала обычные события, потом повторяющиеся (с исключениями), потом переопределения — порядок вывода должен быть хронологическим по start_date.

Настройка временных зон и DST: избегаем ошибок смещения на час

Стандартная проблема — переход на летнее время (DST). Если событие задано как 'каждый вторник в 18:00 Europe/Moscow', то после перевода часов фактическое время может сместиться. Решение: храните все временные метки в UTC, а временную зону события — отдельно. При отображении преобразуйте используя DateTimeZone::getOffset(). Для повторяющихся событий примените правило 'BYHOUR' с локальным часом (18), а при вычислении экземпляра пересчитайте в UTC относительно текущего смещения. В WordPress 6.6+ появилась встроенная функция wp_date() для корректного отображения с учётом site_timezone_string. Для пользовательских часовых поясов (если сайт международный) используйте JavaScript Intl.DateTimeFormat resloveOptions(): передайте timeZone из настроек пользователя. В 2026 году все основные хостинги (SiteGround, Kinsta, WP Engine) уже используют PHP 8.3 с исправленными таймзонами, но на старых серверах проверяйте файл /usr/share/zoneinfo.

Для тестирования используйте фиктивные даты: установите дату на 2026-03-29 02:00 (переход на летнее время) и проверьте, что событие 03:00 UTC отображается как 06:00 в Москве (MSK = UTC+3, но в марте MSK не переходит на летнее — это постоянный UTC+3). Важно: не используйте часовой пояс 'Europe/Moscow' для архивирования старых событий — до 2014 года он был другим. Для архива (события до 2014) храните оригинальную временную зону в отдельном поле 'historical_timezone'. Рекомендуемый формат хранения — 'datetime' => '2026-06-15 14:00:00', 'timezone' => 'Europe/London'. При конвертации всегда берите актуальное смещение для даты события, а не текущее.

Производительность: оптимизация запросов и кэширование календарных данных

Календарь — один из самых запросоёмких элементов. Без оптимизации страница с календарём на год (365+ событий) может генерироваться до 8 секунд. Первое правило — избегайте WP_Query для событий. Используйте глобальный $wpdb с прямым SQL: 'SELECT p.ID, pm1.meta_value as event_start, pm2.meta_value as event_end FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm1 ON p.ID=pm1.post_id AND pm1.meta_key='event_start' INNER JOIN {$wpdb->postmeta} pm2 ON p.ID=pm2.post_id AND pm2.meta_key='event_end' WHERE p.post_type='events' AND p.post_status='publish' AND pm1.meta_value BETWEEN %d AND %d'. Это ускоряет запрос в 4-5 раз. Второе — кэшируйте результат на сервере через Transient API: set_transient('events_202603', $events, HOUR_IN_SECONDS). Очищайте кэш при обновлении события (сохраните пост с сбросом транзиента). Третье — используйте объектный кэш (Redis или Memcached), если проект более 2000 событий. Без объектного кэша каждый запрос к календарю будет повторно выбирать данные. В 2026 году Redis доступен на всех управляемых хостингах как плагин — активируйте его в настройках.

Для уменьшения количества HTTP-запросов при асинхронной загрузке объедините события за месяц в один JSON-ответ. Если месяц переключается — подгружайте только следующие 2 месяца (prefetch). Используйте Service Worker для кэширования статических страниц календаря: закэшируйте HTML сетки на 24 часа. Для событий, которые редактируются в реальном времени (например, в многопользовательском режиме), применяйте WebSocket или Server-Sent Events — но это уже избыточно для 90% проектов. Достаточно инвалидации кэша при сохранении поста. Всегда добавляйте мета-поле 'event_updated' (timestamp) — по нему проверяйте, нужно ли перегенерировать кэш. Если версия события новее в кэше — отдавайте старый. Это предотвращает баги с отображением устаревших данных.

Интеграция с внешними календарями: iCal, Google Calendar и Outlook

События на сайте должны легко экспортироваться пользователями. Стандарт — iCalendar (RFC 5545). Генерируйте .ics файл через кастомный endpoint: /events/export?ids=12,34,56. Внутри создайте VCALENDAR с VEVENT для каждого события. Обязательные поля: DTSTART, DTEND, SUMMARY, DESCRIPTION, UID (уникальный ID на основе post_id + сайт). Для повторяющихся событий добавьте RRULE из мета-поля. Для переопределений — RDATE и EXDATE. Убедитесь, что даты в iCal указаны в UTC с суффиксом Z или с локальным TZID (например, DTSTART;TZID=Europe/Moscow:20260315T180000). Google Calendar принимает оба варианта, Outlook предпочитает TZID. Для обратной связи (импорт событий на сайт) используйте кастомный обработчик загрузки .ics файла: разбор через библиотеку johngrogg/ics-parser (PHP) или iCalcreator. Важно: никогда не принимайте события из непроверенных источников — они могут содержать XSS встроенным DESCRIPTION. Фильтруйте все поля: strip_tags для SUMMARY, nl2br для DESCRIPTION.

Для двусторонней синхронизации (например, если сайт — единый календарь компании с Google Workspace) используйте OAuth 2.0 и Google Calendar API. Создайте сервисный аккаунт в Google Cloud, добавьте scopes: https://www.googleapis.com/auth/calendar. При создании события на сайте — параллельно создавайте Google Calendar Event через API и записывайте его google_event_id в мета-поля. При обновлении — обновляйте Google Event, при удалении — удаляйте. Пропишите вебхуки Google (webhook URL) для получения изменений из Google и обновления локальных событий. В 2026 году Google Calendar API v3 является стабильным, но лимит запросов — 1 000 000 в день на проект, так что для высоконагруженных календарей используйте пакетное обновление (batch). Для Outlook — аналогично через Microsoft Graph API, но с ограничением 10 000 запросов на одного пользователя.

Добавлено: 23.04.2026