Часові пояси і чутливість
1) Базові принципи
UTC як транспорт і зберігання. Всі серверні таймстемпи і ключі сортування - в UTC. Перетворення в локальний «стінний» час - на краю (edge/UI) або в спеціально виділеному сервісі форматування.
Зона ≠ зміщення.'Europe/Kyiv'- це не просто'UTC + 02:00`: правила змінюються з часом. Зберігайте ідентифікатори IANA (tzdb) в профілі користувача/об'єкта, а не "+ 03:00».
Чітке розрізнення годин.
Wall clock (людський час, схильний до DST).
UTC clock (універсальна шкала).
Monotonic clock (для вимірювання тривалостей і таймаутів). Ніколи не обчислюйте таймаути на «стінних» годинниках.
Ідempotентність і толерантність до тремтіння часу. Системи повинні коректно переживати невеликі стрибки NTP/зміщення.
2) Модель даних і API-контракти
Події: 'occurred _ at'( UTC, RFC 3339),'timezone'( IANA), опціонально'wall _ time'( локальний зі збереженням зміщення при створенні).
Періоди: використовуйте напівзакриті інтервали'[ start, end)'в UTC; для людиночитаних розкладів зберігайте вихідний вираз + зону.
Повторювані правила: серіалізуйте як RRULE/cron-еквівалент + IANA-зона. Планування делегуйте рушію, який розуміє DST.
Формат часу в API: ISO 8601/RFC 3339 з явним'Z'або зміщенням, наприклад'2025-10-31T17:00:00Z`. Не передавайте «плаваючі» рядки без зміщення.
Версіонування: зміна бізнес-правил часу (наприклад, перехід країни на постійний UTC + 1) - це міграція конфігурацій і перерахунок розкладів; враховуйте в схемах версій.
3) Літній час (DST): амбігуїтети і пропуски
Дубльовані локальні часи. Восени локальне "02:30" може трапитися двічі. Планувальник в зоні повинен розрізняти'2025-10-26T02:30 + 03'і'2025-10-26T02:30+02`.
Пропущені локальні часи. Навесні «перескакують» хвилинні інтервали (наприклад,'02:00–03:00'не існує). Планувальник зобов'язаний визначити стратегію: перенести на'03:00', пропустити або виконати «якомога швидше».
Рекомендація: зберігайте завдання як «локальне правило + зона», а фактичні інстанси матеріалізуйте в UTC заздалегідь (rolling window), з фіксацією обраної політики на DST.
4) Leap seconds и time smear
Leap second. Додаткова секунда іноді вставляється в UTC. Більшість бізнес-процесів не повинні «бачити» 23:59:60.
Smear (розмазування). Деякі середовища м'яко розподіляють коригування на вікно (наприклад, ± 12 годин), щоб уникнути стрибка.
Практика: узгодьте єдину політику часу для всього кластера (NTP/смир), логуйте її в метаданих і тримайте в runbook.
5) Планувальники і cron-патерни
Небезпека «простого cron». Класичний cron не знає DST і IANA-зон. Використовуйте рушії, де розклад прив'язаний до зони (Quartz-клас, хмарні Scheduler-сервіси, Kubernetes CronJob із зоною через контролер/адмісіон).
Матеріалізація розкладів. Для надійності матеріалізуйте найближчі N запусків в UTC (наприклад, на 7-30 днів), зберігайте cursor і детермінуйте політику при DST.
Ідempotентність завдань. Ключ deduplication: `(job_id, scheduled_at_utc)`; повторний запуск не повинен дублювати побічні ефекти.
Ковзання годин. При тривалих паузах/інцидентах вирішіть, чи робити catch-up (виконати пропущене) або skip. Конфігуруйте per-job.
6) Час у протоколах і чергах
Подієві шини (Kafka/Pulsar). Зберігайте'event _ time'і'ingest _ time'окремо. Для ретроспективних перерахунків використовуйте'event _ time'.
Ідемпотентні споживачі. При повторній доставці орієнтуйтеся на ключ події і'event _ time', а не на зміщення в партії.
Сортування та вікна. Вікна «за 24 години за локальним часом магазину» обчислюйте як UTC-інтервали, отримані з локальних правил конкретної зони на конкретну дату.
7) Логи, трасування, метрики
Єдиний таймзонний стандарт: всі технічні логи і метрики в UTC (із зазначенням'Z'). Відображення в дашбордах - локалізоване для користувача.
Трасування: передавайте'trace _ start _ utc','duration _ ms'на монотонічних годинниках. Ніколи не віднімайте «стінні» таймстемпи.
Бізнес-звіти: формуйте «добу» в зоні домену (наприклад,'Europe/Paris'для французького податку), а не по UTC. Чітко документуйте.
8) Призначені для користувача профілі та контент
Профіль: `preferred_timezone` (IANA), `preferred_locale`, `currency`, `week_start` (Mon/Sun).
Мультизонні сутності: для команд/організацій зберігайте «зону домену» (наприклад, магазину/юрособи) незалежно від персональної зони учасника.
Нотифікації: обчислюйте «тихі години» в зоні користувача; відправку шедуліті з UTC-вікна, з безпекою при DST.
9) Анти-патерни
Зберігати тільки локальний час без зсуву/зони.
Жорстко прошивати зміщення'+ hh:mm'замість IANA-ідентифікатора.
Вважати тривалості через різницю двох «стінних» таймстемпів.
Планувати по cron без підтримки зон/DST.
Робити аналітику «по добі» в UTC, коли норматив вимагає локальну зону.
Припускати незмінність правил зони (країни змінюють політику часу).
10) Тестування часу
Контрольовані годинники. Ін'єктуйте «годинник» в код (Clock/TimeProvider) для детермінованих тестів.
Набори кейсів:- Перехід на літній/зимовий час (дублі/пропуски).
- Переміщення користувача між зонами (зміна'preferred _ timezone').
- Зміна правил в tzdb (оновлення бази - регресійні тести).
- Зміщення NTP, затримана доставка подій.
- Фаззі-тести. Випадкові зони, дати, формати; порівняння з еталонною бібліотекою.
11) Спостережуваність і експлуатація
Алерти: розсинхронізація NTP, відставання оновлення tzdb, сплески «невиконаних» cron-завдань при DST.
Дашборди: розподіл подій за зонами/локальною добою; лічильники catch-up/skip.
Runbook: процедури при зміні правил часу в юрисдикції; порядок оновлення tzdb; комунікація з власниками розкладів.
12) Патерни реалізації
Time Normalization Gateway. Тонкий сервіс, що нормалізує вхідні часи до RFC 3339 UTC, валідує зони (IANA) і доповнює контекст.
Local-Day Builder. Бібліотека/сервіс, який з «локального дня» і зони будує точні UTC-межі'[ start _ utc, end_utc)', враховуючи DST.
Schedule Materializer. Планувальник, який зберігає правила у вигляді «локальний вираз + зона», матеріалізує майбутні інстанси в UTC і управляє колізіями/пропусками.
Dual-Timestamp Events. Події з полями'occurred _ at _ utc','wall _ time _ local','timezone'. Для UI підставляється локальне, для систем - UTC.
13) Чек-лист архітектора
1. Чи всюди зберігається UTC?
2. Чи є у сутностей IANA-зона і дата-політика домену?
3. Планувальник розуміє DST і матеріалізує інстанси в UTC?
4. Логи/метрики - в UTC; звіти - в доменній зоні?
5. Таймаути/ретраї - на монотонічних годинниках?
6. Оновлення tzdb автоматизовано і моніториться?
7. Тести покривають зміну правил, дублі/пропуски хвилин?
14) Міні-рецепти (псевдокод)
Перетворення локального «робочого дня» в UTC-інтервал
function localDayToUtcInterval(dateLocal, tz):
startLocal = combine(dateLocal, 00:00) in tz endLocal = startLocal + 1 day startUtc = toUTC(startLocal) // учитывает DST endUtc = toUTC(endLocal)
return [startUtc, endUtc)
Матеріалізація повторюваного розкладу
inputs: rrule, tz, windowStartUtc, windowEndUtc for each localOccurrence in expand(rrule, tz, [windowStartUtc, windowEndUtc] projected to tz):
emit occurrence { scheduled_at_utc = toUTC(localOccurrence), tz }
Ідемпотентний ключ запуску задачі
dedupe_key = hash(job_id + scheduled_at_utc.toString())
15) Безпека та відповідність
Аудит: зберігайте обидві проекції часу (UTC і локальну), щоб узгодити призначені для користувача претензії ("мені обіцяли до 23:00 по Лімі") з серверною хронологією.
Регуляторика: звітні періоди формуються в необхідних зонах (податки, відповідальні ігрові ліміти, маркетингові обмеження «по годинах»).
Приватність: часовий пояс - персональні налаштування, але не точно ідентифікуючі дані; обробляйте в рамках загальних політик конфіденційності.
Висновок
«Чутливість до часу» - це не про формат дати, а про архітектурні межі відповідальності: де зберігати, де перетворювати, як планувати і як доводити коректність. Уніфікація на рівні UTC, явні IANA-зони, грамотні планувальники, подвійні таймстемпи і монотонічні годинники перетворюють час з джерела інцидентів в передбачуваний інфраструктурний сервіс.
Пов'язані статті розділу «Архітектура та Протоколи» (рекомендується):
- GeoDNS і гео-маршрутизація; Балансування навантаження; Спостережуваність подій у часі; Cron-патерни і матеріалізація розкладів; Регіональні обмеження та локальні звітні добу.