Время кажется простым, пока не попробуешь написать программу, которая обрабатывает его правильно. Событие, назначенное на «15:00», означает разное в Токио, Лондоне и Нью-Йорке. Летнее время сдвигает часы вперёд или назад — но не везде и не в одни и те же даты. Временная метка, которая выглядит корректной в вашем локальном окружении, ломается в продакшне, потому что сервер находится в другом часовом поясе.
Часовые пояса — одна из самых стабильно запутанных тем в разработке программного обеспечения. Эта статья объясняет, как они работают, откуда берётся сложность и как избежать самых распространённых ошибок.
Почему существуют часовые пояса
Земля совершает оборот на 360 градусов за 24 часа, а это значит, что солнце находится в наивысшей точке в разные моменты в зависимости от вашей долготы. До XIX века каждый город устанавливал часы по местному солнечному времени — полдень наступал, когда солнце было в зените. Это работало, пока железные дороги не связали удалённые города и расписание поездов не потребовало единых, согласованных часов.
В 1884 году делегаты от 25 стран собрались на Международной меридианной конференции в Вашингтоне и договорились разделить мир на 24 стандартных часовых пояса, каждый с отступом от нулевого меридиана (0 градусов долготы), проходящего через Гринвич, Англия.
На практике границы часовых поясов следуют политическим границам, а не аккуратным линиям долготы. Китай охватывает пять географических зон, но использует единое официальное время (UTC+8). Индия использует UTC+5:30 — получасовое смещение. Непал — UTC+5:45. Реальная карта часовых поясов выглядит хаотично.
UTC и GMT
GMT (Greenwich Mean Time) — среднее солнечное время в Королевской обсерватории в Гринвиче. Более века оно служило мировым временным ориентиром.
UTC (Coordinated Universal Time) заменил GMT в качестве международного стандарта в 1972 году. UTC основан на атомных часах, а не на астрономических наблюдениях, что делает его гораздо более точным. Для большинства практических целей UTC и GMT показывают одно и то же время, но UTC — это правильный технический ориентир.
Почему «UTC», а не «CUT»? Аббревиатура — это компромисс между английским «Coordinated Universal Time» (CUT) и французским «Temps Universel Coordonné» (TUC). Ни одна сторона не получила свой вариант аббревиатуры, поэтому было выбрано UTC как языково-нейтральная альтернатива.
Летнее время: организованный хаос
Около 70 стран применяют летнее время (DST), переводя часы на один час вперёд весной и назад осенью. Цель — привести часы бодрствования в соответствие со световым днём. Результат — полугодовой источник багов.
Основные сложности:
- Не универсально. Бо́льшая часть Африки, Азии и Южной Америки не использует летнее время. В США Аризона и Гавайи не переводят часы.
- Разные даты. ЕС переводит часы в последнее воскресенье марта и октября. США — во второе воскресенье марта и первое воскресенье ноября. Они не синхронизированы на протяжении нескольких недель каждый год.
- Неоднозначное время. Когда часы переводятся назад, час с 1:00 до 2:00 ночи проходит дважды. Временная метка «1:30 ночи» в этот день неоднозначна.
- Пропущенное время. Когда часы переводятся вперёд, часа с 2:00 до 3:00 ночи не существует. Встреча, назначенная на 2:30 ночи в этот день, никогда не наступит.
- Политические изменения. Правительства могут (и делают) менять правила летнего времени с минимальным уведомлением. Россия приняла постоянное летнее время в 2011 году, затем перешла на постоянное зимнее время в 2014 году. Марокко неоднократно менял правила DST.
ISO 8601: универсальный формат даты
Для исключения неоднозначности международный стандарт ISO 8601 определяет чёткий формат даты и времени:
2026-03-29T14:30:00Z
2026-03-29T14:30:00+02:00
2026-03-29T14:30:00-05:00
Tразделяет дату и время.Zозначает UTC (часовой пояс «Zulu» в военной терминологии).+02:00или-05:00— смещение от UTC.
Этот формат однозначен, сортируется как обычный текст и повсеместно понимается библиотеками для работы с датами. В случае сомнений используйте ISO 8601.
Unix-временные метки
Unix-временная метка (также называемая epoch time или POSIX time) — это число секунд, прошедших с 1 января 1970 года, 00:00:00 UTC — момента, известного как эпоха Unix.
| Человекочитаемый формат | Unix-временная метка |
|---|---|
| 1970-01-01 00:00:00 UTC | 0 |
| 2000-01-01 00:00:00 UTC | 946684800 |
| 2026-03-29 12:00:00 UTC | 1774987200 |
Unix-временные метки не имеют часового пояса — они всегда в UTC. Это делает их идеальными для хранения и сравнения времени в программном обеспечении. Конвертация в локальный часовой пояс выполняется только на уровне отображения.
Проблема 2038 года: системы, хранящие Unix-временные метки в виде 32-битного знакового целого, переполнятся 19 января 2038 года в 03:14:07 UTC. Максимальное значение (2 147 483 647) обернётся в отрицательное число, которое интерпретируется как декабрь 1901 года. Большинство современных систем используют 64-битные целые, которые не переполнятся ещё 292 миллиарда лет.
База данных часовых поясов IANA
Программному обеспечению нужны не просто смещения UTC — ему нужна полная история и будущие правила для каждого региона, включая переходы на летнее время, политические изменения и исторические аномалии. Эта информация хранится в Базе данных часовых поясов IANA (также называемой базой Олсона или tzdata).
Она использует идентификаторы вроде America/New_York, Europe/Paris, Asia/Tokyo. Каждая запись кодирует полную историю смещений UTC и правил летнего времени для данного местоположения.
Вот почему никогда не следует хранить часовой пояс как фиксированное смещение вроде «+02:00». Смещение сообщает вам текущую разницу с UTC, но ничего не говорит о правилах летнего времени. Europe/Paris — это UTC+1 зимой и UTC+2 летом. Идентификатор IANA учитывает и то, и другое.
Распространённые баги в программном обеспечении
- Хранение локального времени без часового пояса. Значение вроде
2026-03-29 14:30:00бессмысленно без указания часового пояса. Всегда храните UTC или включайте смещение. - Допущение, что смещение UTC равно часовому поясу. UTC+2 в марте может быть UTC+3 в июле (если регион переходит на летнее время). Храните идентификатор IANA, а не смещение.
- Игнорирование переходов DST в планировании. Ежедневная задача в 2:30 ночи будет пропущена раз в год и выполнена дважды раз в год, если вы не обрабатываете летнее время.
- Допущение, что в сутках 24 часа. В дни перевода часов сутки длятся 23 или 25 часов. Вычисление «завтра в это же время» добавлением 86 400 секунд даст расхождение в час.
- Наивное использование JavaScript
Date.new Date("2026-03-29")парсится как UTC в одних движках и как локальное время в других. Всегда явно указывайте часовой пояс.
Лучшие практики для разработчиков
- Храните время в UTC. Конвертируйте в локальный часовой пояс пользователя только на уровне представления.
- Используйте идентификаторы часовых поясов IANA (
America/New_York), а не фиксированные смещения (-05:00). - Используйте ISO 8601 для сериализации. Он однозначен и парсится повсеместно.
- Используйте зрелую библиотеку дат. В JavaScript используйте
Intl.DateTimeFormatили библиотеку вродеdate-fns-tz. В Python —zoneinfo(3.9+) илиpytz. В Java —java.time.ZonedDateTime. - Поддерживайте
tzdataв актуальном состоянии. Правительства меняют правила летнего времени. Вашей операционной системе и среде выполнения нужны актуальные данные часовых поясов. - Тестируйте с несколькими часовыми поясами. Не предполагайте, что ваш сервер и пользователи находятся в одном часовом поясе.
Узнать больше
Время обманчиво сложно, но правила хорошо задокументированы, а инструменты зрелы. Главное — уважать эту сложность, а не делать вид, что её нет.
- Cron-выражения — демистификация — планирование задач с учётом часовых поясов
- Генератор хешей и Тестер Regex — ещё инструменты для разработчиков на ToolK
