Все записи

Часовые пояса: почему это больно

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

Смещение это не пояс

Самая частая ошибка выглядит так: сохранить время встречи как UTC плюс смещение и считать задачу решённой. Для прошедших событий это работает. Для будущих нет.

Правила меняются. Россия отказалась от перевода часов в 2014 году, Волгоград перешёл на другое время в конце 2020 года, и такие решения принимаются по всему миру каждый год, иногда с предупреждением за несколько недель. Если встреча назначена на 10 утра по местному времени через полгода, то важно именно местное время. Смещение от UTC к тому моменту может оказаться другим.

Поэтому мы храним не смещение, а название пояса вроде Europe/Moscow, а базу правил обновляем отдельно. Когда правила где-то меняются, будущие события пересчитываются сами.

Три вида времени

В формате iCalendar время события бывает трёх видов.

  • С указанным поясом. Встреча в 10:00 по Москве. Самый понятный случай.
  • В UTC. Точный момент, без привязки к месту.
  • Плавающее. Просто «9 утра», без пояса. Будильник на завтрак в отпуске должен звонить в девять там, где вы находитесь.

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

События на весь день

Отдельная история с событиями на весь день. День рождения 12 марта это дата, а не интервал времени. Но часть сервисов хранит такие события как интервал с полуночи до полуночи в UTC. Человек в Москве видит 12 марта, а его родственник во Владивостоке получает два дня подряд или сдвиг на день.

Мы приводим такие события к чистой дате при получении и передаём дальше именно дату, а если сервис настаивает на времени, вычисляем его так, чтобы день получился тем же в поясе получателя.

Несуществующий час

В странах, где переводят часы, раз в году есть час, которого нет, и час, который случается дважды. Встреча в 2:30 ночи в день перевода весной просто не существует. В день перевода осенью она случается два раза. Разные сервисы решают это по-разному: сдвигают вперёд, сдвигают назад, показывают ошибку.

Для нас важнее всего не изменить событие при простой пересылке. Если время двусмысленно, мы оставляем его таким, каким оно было, и не пытаемся нормализовать без надобности.

Что мы проверяем

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

Каждая новая находка становится ещё одним случаем в этом наборе. Набор растёт быстрее, чем нам хотелось бы, но ни разу не было так, чтобы он оказался лишним.