Unix Timestamp UTC объяснён: часовые пояса, смещения и конвертация

Карта мира с линиями часовых поясов UTC и Unix-временем, связанным с местными часами

Если ты хоть раз хранил дату в базе данных, разрабатывал API или отлаживал баг в планировщике, который проявлялся только в определённых странах, - ты наверняка сталкивался с вопросом: как связаны Unix timestamp и UTC, и почему из-за этого возникают реальные ошибки в продакшене. В этой статье разберём, что такое UTC, почему Unix timestamp по определению всегда привязан к UTC, как работают смещения часовых поясов и как правильно конвертировать временные метки в JavaScript, Python и PHP. Плюс - самые частые ошибки разработчиков и способы их избежать.

Главное:

  • Unix timestamp всегда отсчитывает секунды от 1 января 1970 года, 00:00:00 UTC - никакой привязки к часовому поясу нет.
  • UTC - универсальная точка отсчёта; локальное время - это просто UTC плюс или минус смещение.
  • Чтобы правильно перевести timestamp в локальное время, нужно знать целевой часовой пояс, а не просто числовое смещение.
  • Летнее время (DST) меняет смещение для часового пояса - именно поэтому хардкодить смещения опасно.

Что такое UTC и зачем это знать?

UTC (Coordinated Universal Time) - это основной мировой стандарт времени, по которому синхронизируются часы и системы по всему миру. В отличие от часовых поясов, UTC не имеет смещения - это нулевая точка отсчёта. UTC не переходит на летнее время и не меняется ни для одной страны или региона.

До UTC существовал GMT (Greenwich Mean Time), и многие до сих пор используют эти термины как синонимы. Технически UTC и GMT немного отличаются, но для большинства задач в разработке их считают эквивалентными. UTC формально определён рекомендацией ITU-R TF.460 и поддерживается с помощью атомных часов.

Для разработчика UTC важен тем, что даёт единую, однозначную точку отсчёта. Если сервер во Франкфурте и пользователь в Лос-Анджелесе оба записывают событие в UTC, эти два timestamp всегда можно корректно сравнить. Если же каждый пишет локальное время без метаданных о часовом поясе - жди проблем.

Почему Unix timestamp всегда в UTC

Unix timestamp (его также называют epoch time или POSIX time) - это количество секунд, прошедших с 1 января 1970 года, 00:00:00 UTC. Эта точка отсчёта - полночь 1 января 1970 года по UTC - зашита в само определение. Не существует версии Unix time, которая начинается с локального времени Нью-Йорка или Токио.

Так что ответ на вопрос "всегда ли Unix timestamp в UTC?" - да, по определению. Число 1700000000 обозначает один и тот же момент времени для каждого человека на планете. Разница между пользователями только в том, как этот момент отображается в их локальном часовом поясе.

Чтобы глубже разобраться в основах этой концепции, читай нашу статью Epoch Time: основа Unix timestamp.

Привязка к UTC - одно из самых полезных свойств Unix time. Поскольку само число не несёт информации о часовом поясе, две системы в разных странах могут обмениваться timestamp и точно знать, о каком моменте идёт речь, без каких-либо дополнительных договорённостей.

UTC против локального времени и часовых поясов

UTC - это точка отсчёта. Локальное время - это то, что получается, когда поверх неё применяются правила часового пояса. Часовой пояс - это не просто фиксированное смещение: это именованный регион с набором правил, которые могут меняться со временем (прежде всего из-за перехода на летнее время).

Например, "Eastern Time" в США - это UTC-5 зимой и UTC-4 летом. В базе данных IANA timezone database этот пояс называется "America/New_York" - именно она является авторитетным источником правил для большинства языков программирования и операционных систем.

Практический вывод: когда ты сохраняешь Unix timestamp в базе данных, ты сохраняешь значение в UTC. При отображении пользователю ты конвертируешь его в локальный часовой пояс. Никогда не храни локальное время как числовое значение, считая его UTC - именно отсюда и берутся баги.

Как работают смещения UTC

Смещение UTC показывает, насколько часовой пояс отличается от UTC. Оно записывается в формате +HH:MM или -HH:MM. Несколько примеров:

  • UTC+02:00 - центральноевропейское летнее время (CEST), используется в Германии и Франции летом.
  • UTC-05:00 - восточное стандартное время (EST), используется на восточном побережье США зимой.
  • UTC+05:30 - индийское стандартное время (IST), смещение на полчаса.
  • UTC+00:00 - сам UTC, а также Великобритания зимой (GMT).

Чтобы вручную перевести UTC epoch time в локальное время, нужно прибавить или вычесть смещение в секундах. Для UTC+02:00 это 2 * 3600 = 7200 секунд. Если Unix timestamp равен 1700000000, то локальное время в UTC+02:00 будет соответствовать 1700000000 + 7200 до форматирования.

Однако делать это вручную рискованно, потому что смещения меняются с переходом на летнее время. Всегда используй специализированную библиотеку для работы с часовыми поясами, а не хардкоди смещение. Подробнее о методах конвертации - в нашем руководстве Как конвертировать Unix timestamp в дату.

Конвертация Unix timestamp в локальное время

Рассмотрим конкретный пример с Unix timestamp 1700000000, который соответствует 14 ноября 2023 года, 22:13:20 UTC. Конвертируем его в часовой пояс "America/New_York" (UTC-5 в ноябре) на трёх языках.

JavaScript

const ts = 1700000000;

// JavaScript Date принимает миллисекунды
const date = new Date(ts * 1000);

// Отображаем в локальном времени Нью-Йорка
const options = {
  timeZone: 'America/New_York',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  timeZoneName: 'short'
};

console.log(date.toLocaleString('en-US', options));
// Output: November 14, 2023 at 05:13:20 PM EST

Обрати внимание: объект Date в JavaScript хранит время внутри как миллисекунды UTC. Метод toLocaleString с опцией timeZone сам применяет нужное смещение и правила летнего времени.

Python

from datetime import datetime, timezone
import zoneinfo  # Python 3.9+

ts = 1700000000

# Создаём datetime с привязкой к UTC из timestamp
utc_dt = datetime.fromtimestamp(ts, tz=timezone.utc)

# Конвертируем в локальное время Нью-Йорка
ny_tz = zoneinfo.ZoneInfo('America/New_York')
local_dt = utc_dt.astimezone(ny_tz)

print(local_dt.strftime('%Y-%m-%d %H:%M:%S %Z'))
# Output: 2023-11-14 17:13:20 EST

Ключевой момент - использование datetime.fromtimestamp(ts, tz=timezone.utc). Если опустить аргумент tz, Python будет интерпретировать timestamp в локальном часовом поясе сервера - это частый источник багов.

PHP

$ts = 1700000000;

// Создаём объект DateTime из Unix timestamp (всегда UTC)
$dt = new DateTime('@' . $ts);

// Устанавливаем целевой часовой пояс
$dt->setTimezone(new DateTimeZone('America/New_York'));

echo $dt->format('Y-m-d H:i:s T');
// Output: 2023-11-14 17:13:20 EST

В PHP префикс @ при создании объекта DateTime говорит интерпретатору, что значение является Unix timestamp (UTC). Без него PHP может интерпретировать строку с учётом часового пояса по умолчанию, заданного на сервере.

Типичные ошибки разработчиков

Даже опытные разработчики спотыкаются на работе с часовыми поясами. Вот самые частые проблемы:

1. Предположение, что локальное время сервера - это UTC

Если сервер настроен на "Europe/Berlin" и ты вызываешь time() в PHP или datetime.now() в Python без аргумента часового пояса, ты получаешь локальное время - не UTC. Всегда явно указывай UTC при записи timestamp.

2. Хардкодинг смещений UTC вместо использования имён часовых поясов

Использование +02:00 как фиксированного смещения для Германии сломается зимой, когда страна переходит на +01:00. Всегда используй именованные часовые пояса, например Europe/Berlin, чтобы библиотека автоматически применяла правильные правила летнего времени.

3. Хранение timestamp как строк с локальным временем без метаданных о часовом поясе

Строка вида 2023-11-14 17:13:20 в базе данных неоднозначна. Это UTC? EST? IST? Если хранить timestamp как целые числа Unix или как строки ISO 8601 со смещением UTC (например, 2023-11-14T22:13:20Z), смысл будет однозначным. Читай наше сравнение Unix Timestamp против ISO 8601, чтобы определиться с выбором.

4. Игнорирование летнего времени при планировании задач

Если планировать задачу на "09:00 каждый день", добавляя 86400 секунд к времени последнего запуска, она будет смещаться на час в дни перехода на летнее/зимнее время. Используй cron-библиотеку или планировщик, который понимает правила часовых поясов.

5. Путаница между миллисекундами и секундами

JavaScript использует миллисекунды для объекта Date, тогда как большинство Unix timestamp - в секундах. Если передать timestamp в секундах в new Date() без умножения на 1000, получишь дату в январе 1970 года. Подробнее об этом - в нашей статье Секунды, миллисекунды, микросекунды: какой timestamp использовать?.

Заключение

Unix timestamp и UTC неразрывно связаны по своей природе. Timestamp - это всегда значение в UTC: количество секунд от фиксированной точки отсчёта в UTC. Локальное время - лишь формат отображения, а не отдельный вид timestamp. Самый надёжный подход: хранить всё в UTC, переводить в локальное время только в момент отображения пользователю и всегда использовать именованные часовые пояса вместо хардкодных смещений. Следуй этим правилам - и большинство багов, связанных с часовыми поясами, просто перестанут возникать. О лучших практиках хранения этих значений читай в нашем руководстве Unix timestamp в базах данных.

Конвертируй Unix timestamp в UTC или локальное время на unixtimestamp.app

Конвертируй любой Unix timestamp в UTC или локальное время - мгновенно

Вставь любой Unix timestamp и сразу увидишь его в UTC и своём локальном часовом поясе. Бесплатно, без регистрации, поддерживает секунды и миллисекунды.

Попробовать бесплатный инструмент →

Да. По определению, Unix epoch timestamp считает секунды от 1 января 1970 года, 00:00:00 UTC. Само число не несёт информации о часовом поясе - это значение в UTC. Часовой пояс становится важен только при конвертации timestamp в читаемую дату и время для отображения.

Используй встроенную библиотеку для работы с часовыми поясами в своём языке. В JavaScript - toLocaleString с опцией timeZone. В Python - datetime.fromtimestamp(ts, tz=timezone.utc).astimezone() с именованным часовым поясом. В PHP - создай DateTime из timestamp и вызови setTimezone(). Всегда используй именованные часовые пояса, а не числовые смещения.

Чаще всего это происходит потому, что функция использует часовой пояс сервера по умолчанию вместо UTC при интерпретации timestamp. В Python - если опустить аргумент tz в datetime.fromtimestamp(), будет использоваться системное локальное время. В PHP без префикса @ происходит то же самое. Всегда явно указывай UTC при создании объекта даты.

Смещение UTC - это количество часов (а иногда и минут), на которое часовой пояс опережает UTC или отстаёт от него. Например, UTC-05:00 означает пять часов позади UTC. При конвертации Unix timestamp в локальное время к значению UTC применяется это смещение. Поскольку смещения меняются с переходом на летнее время, следует использовать именованный часовой пояс, а не хардкодить смещение.

Да, и это одно из главных преимуществ Unix timestamp. Поскольку каждый timestamp является значением в UTC, любые два timestamp можно сравнивать напрямую как целые числа. Большее число всегда означает более поздний момент времени, независимо от того, где были сгенерированы эти timestamp. Конвертация часовых поясов для сравнения не нужна.