Смещения от UTC, не кратные целому часу
· 4 мин чтения
Примерно пятая часть мира живёт на смещении, не кратном целому часу от UTC. Программа, предполагающая иное, ошибается для этих людей не слегка, а на 30 или 45 минут, что хуже.
Мысленная схема из двадцати четырёх часовых поясов ошибочна дорогостоящим образом: она порождает код, уверенно промахивающийся на 30 или 45 минут для намного более чем миллиарда человек. Ошибка в целый час хотя бы выглядит ошибкой. Ошибка в 45 минут выглядит опечаткой — и уходит в выпуск.
Полный список
Смещения на :30
| Смещение | Где |
|---|---|
| UTC-09:30 | Маркизские острова |
| UTC-03:30 | Ньюфаундленд и Лабрадор |
| UTC+03:30 | Иран |
| UTC+04:30 | Афганистан |
| UTC+05:30 | Индия, Шри-Ланка |
| UTC+06:30 | Мьянма, Кокосовые острова |
| UTC+09:30 | Центральная Австралия — Северная территория, Южная Австралия |
| UTC+10:30 | Остров Лорд-Хау (стандартное время) |
Смещения на :45
| Смещение | Где |
|---|---|
| UTC+05:45 | Непал |
| UTC+08:45 | Юго-восток Западной Австралии (Юкла, неофициально) |
| UTC+12:45 | Острова Чатем (стандартное время) |
Одна лишь Индия — это 1,4 миллиарда человек. Прибавьте Иран, Афганистан, Мьянму, Шри-Ланку и Непал, и вы уже за пятой частью планеты, ещё не считая Австралию или Канаду.
Другой вид необычного смещения
Смещения, не кратные целому часу, — известная странность. Ещё две значат не меньше.
Диапазон равен 26 часам, а не 24. Киритимати в Кирибати живёт по UTC+14; острова Бейкер и Хауленд — по UTC-12. Это 26 часов, а значит, бывают мгновения, когда где-то на Земле одновременно действуют три разные календарные даты. Всякий код, полагающий, что двухдневное окно покрывает все пояса, ошибается дважды в сутки.
Летнее время не всегда равно часу. Остров Лорд-Хау, небольшой австралийский остров с постоянным населением около 380 человек, сдвигается на 30 минут: UTC+10:30 зимой, UTC+11:00 летом. Это единственное место на Земле, где так поступают, и лучший проверочный случай для библиотеки дат: всякая реализация, жёстко задающая шестидесятиминутный сдвиг, ошибается там и больше нигде, а значит, пройдёт любой другой написанный вами тест.
Что ломается
Округление до ближайшего часа. Самый частый отказ. Код, вычисляющий смещение в целых часах, молча урезает Индию до +5 — это 30 минут ошибки для каждого пользователя в стране.
Хранение смещений целыми числами. Столбец схемы, объявленный как счёт часов, не в состоянии представить Катманду. Обнаруживается это обычно уже после того, как данные туда легли.
Сеточные интерфейсы, построенные на часовых столбцах. Всякий календарь или планировщик, раскладывающий сутки на 24 равные клетки и полагающий, что местный час каждого пояса ложится на границу клетки, расставит получасовые и 45-минутные пояса неверно. Полоса в планировщике встреч на этом сайте берёт столбцами мгновения, а не местные часы, поэтому строка +05:45 показывает 09:45 в том столбце, где строка UTC показывает 04:00, — и это правда, а не более опрятная ложь.
Арифметика длительностей через получасовой перевод. Предположение, что переход на летнее время равен ровно 3 600 000 миллисекундам, даёт неверный ответ на острове Лорд-Хау — и только там.
Как сделать правильно
Исправление то же, что чинит почти любую ошибку с часовыми поясами: никогда не вычисляйте смещение сами. Спросите платформу.
Всякая современная среда выполнения несёт базу IANA и назовёт вам смещение пояса в заданное мгновение — в минутах и верно, в том числе для Катманду и Лорд-Хау. В JavaScript это Intl.DateTimeFormat с timeZoneName: 'longOffset' либо сравнение показания часов пояса с UTC — путь, которым идёт движок этого сайта, потому что он остаётся точным даже для секундных смещений местного среднего времени в данных до 1900 года.
Затем держитесь трёх правил:
- Смещения — это минуты, никогда не часы. Если переменная зовётся
offsetHours, это уже ошибка. - Проверяйте на Катманду и Лорд-Хау. Они рушат те два допущения, которые всё остальное позволяет сохранить.
- Никогда не округляйте часовой пояс. Если показ вынужден быть приблизительным, скажите об этом; не двигайте молча часы пользователя на 30 минут ради удобства вёрстки.
Разобранный пример: что 45 минут делают с сеткой
Возьмите планировщик встреч, раскладывающий сутки на 24 столбца, по одному на час, и затеняющий рабочие часы каждого участника. Наивная реализация вычисляет каждую строку, сдвигая опорную строку на смещение пояса, выраженное в часах.
Для Лондона против Нью-Йорка это работает: строка Нью-Йорка — это строка Лондона, сдвинутая на пять клеток вправо. Для Лондона против Катманду не работает. Катманду впереди на 5 часов 45 минут, то есть на 5,75 клетки, а трёх четвертей клетки не бывает. Реализация округляет — обычно до 6 — и теперь каждое показание в этой строке на 15 минут раньше должного.
Ошибка достаточно мала, чтобы пережить обзор кода, и достаточно велика, чтобы иметь значение: встреча на 09:00, которую сетка объявляет девятью часами для Катманду, там на самом деле в 08:45, и присоединяющийся опаздывает на четверть часа к собственному дню.
Исправление — перестать считать столбцы часами. Сделайте каждый столбец мгновением и спросите платформу, что показывают местные часы каждого пояса в это мгновение. Строка +05:45 тогда покажет 09:45 там, где строка UTC показывает 04:00, — явно не по сетке, и это честная отрисовка. Планировщик встреч на этом сайте именно так и делает, а рваный край строки Катманду — достоинство, а не изъян.
Исторические смещения ещё диковиннее
До стандартизации смещения были местным средним солнечным временем и точны до секунды. База IANA их хранит.
Нидерланды с 1909 по 1937 год жили на UTC+00:19:32,13. Либерия до 1972 года была на UTC-00:44:30. Спросите среду выполнения о смещении Europe/Amsterdam на дату 1920 года — и получите точное значение, вплоть до секунд.
Поэтому функция смещения, лежащая под этим сайтом, считает в секундах и округляет до минут только на уровне отображения. Движок, хранящий минуты, тихо потерял бы 32 секунды голландской истории — чего никто бы не заметил и что всё равно было бы неверно.