Перейти к содержимому
LeedyBox
Поддержать

Как устроены метки времени Unix

Метка времени Unix — это одно число, обозначающее момент времени. Её легко хранить и сравнивать, но единицы измерения, часовые пояса и текстовые форматы становятся причиной многих ошибок при конвертации.

Что такое Unix-время

Unix-время (его также называют временем эпохи, epoch time, или временем POSIX) — это количество секунд, прошедших с 1970-01-01 00:00:00 UTC, то есть с начала эпохи Unix. 0 — это сам момент начала эпохи, 1700000000 — 2023-11-14 22:13:20 UTC, а отрицательные значения обозначают моменты до 1970 года: -1 — это 1969-12-31 23:59:59 UTC.

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

Unix-время не учитывает високосные секунды (секунды координации): в каждых сутках ровно 86 400 секунд. Когда к UTC добавляется високосная секунда, системы повторяют секунду или распределяют её на более длительный период («размазывание», smearing), поэтому разница между моментами по разные стороны от високосной секунды на одну секунду меньше физически прошедшего времени.

Секунды, миллисекунды, микросекунды или наносекунды?

Многие системы считают в более мелких единицах, а по самому числу не видно, в каких именно. Один и тот же момент может выглядеть как четыре совершенно разных числа:

Единица 2023-11-14 22:13:20 UTC Цифр (2001–2286) Где часто встречается
Секунды 1700000000 10 date +%s, многие API, claims exp в JWT
Миллисекунды 1700000000000 13 Date.now() в JavaScript, System.currentTimeMillis() в Java
Микросекунды 1700000000000000 16 Некоторые базы данных и системы журналирования
Наносекунды 1700000000000000000 19 UnixNano() в Go, time.time_ns() в Python

Для современных дат быстрее всего посчитать цифры. Конвертер LeedyBox использует ту же идею: значения до 1011 читаются как секунды, до 1014 — как миллисекунды, до 1017 — как микросекунды, а более крупные — как наносекунды. Для дат, далёких от сегодняшнего дня, это эмпирическое правило не срабатывает, поэтому проверяйте единицу, если результат выглядит странно.

Важна и точность. Числа в JavaScript точно представляют целые значения только до 253 (около 9 × 1015), поэтому значение в наносекундах, например 1700000000123456789, теряет последние цифры, если разобрать его как обычное число. Храните такие значения в виде строк или используйте BigInt; конвертер делает это внутри. Чтобы прочитать claim exp токена, декодируйте его с помощью инструмента Base64 / URL / JWT.

Часовые пояса и летнее время

Чтобы показать метку времени как дату и время на часах, нужен часовой пояс. UTC — это точка отсчёта; местное время добавляет смещение, например +09:00 в Сеуле или −05:00 в Нью-Йорке зимой.

Отдавайте предпочтение названиям часовых поясов IANA, например Asia/Seoul или America/New_York, а не фиксированным смещениям и аббревиатурам: название несёт в себе историю пояса и правила перехода на летнее время, а аббревиатура вроде CST обозначает сразу несколько поясов. Правила берутся из базы данных часовых поясов, которая меняется несколько раз в год, а браузеры, операционные системы и языки программирования поставляют собственные её копии, поэтому результаты для прошлых или будущих дат могут различаться.

Летнее время создаёт два неудобных случая при переводе местного времени в метку времени:

  • Пропуск: когда часы переводят вперёд, некоторых местных моментов времени не существует. В America/New_York время 2026-03-08 02:30 пропускается, потому что часы перескакивают с 02:00 на 03:00.
  • Наложение: когда часы переводят назад, некоторые местные моменты времени наступают дважды. 2026-11-01 01:30 наступает один раз по EDT (UTC−04:00) и ещё раз через час по EST (UTC−05:00).

Библиотеки разрешают эти случаи по-разному. Конвертер LeedyBox при пропуске берёт более позднее допустимое время (здесь 03:30), а при наложении — более раннее из двух, и поясняет это в примечании. С той же проблемой сталкиваются задания по расписанию; см. руководство «Как читать выражения cron».

ISO 8601, RFC 3339 и даты HTTP

Если момент времени должен быть понятен человеку, записывайте его в формате ISO 8601 или в его более строгом интернет-профиле RFC 3339:

2023-11-14T22:13:20Z
2023-11-15T07:13:20+09:00
2023-11-14T22:13:20.500Z

Z означает UTC. С Z или смещением строка однозначна. Строка без них, например 2023-11-14T22:13:20, — это местное время в неизвестном часовом поясе, и разные парсеры читают её по-разному.

Заголовки HTTP, например Last-Modified и Expires, используют более старый формат, производный от формата даты электронной почты (RFC 2822, ныне RFC 5322), в котором время указывается по GMT: Tue, 14 Nov 2023 22:13:20 GMT.

Проблема 2038 года

Многие старые системы хранят Unix-время в 32-битном целом числе со знаком. Его наибольшее значение, 2147483647, соответствует 2038-01-19 03:14:07 UTC. Через секунду счётчик переполняется и становится -2147483648, что читается как 1901-12-13 20:45:52 UTC. Современные операционные системы и языки в основном используют 64-битные значения времени, которых хватит на сотни миллиардов лет, но 32-битные поля всё ещё встречаются во встраиваемых устройствах, форматах файлов, столбцах баз данных и сетевых протоколах. Если ваш код или данные хранят секунды в 32 битах, запланируйте переход заранее и уже сейчас тестируйте даты после 2038 года.

Метки времени в коде и базах данных

JavaScript работает с миллисекундами:

Math.floor(Date.now() / 1000);                  // текущее Unix-время в секундах
new Date(1700000000 * 1000).toISOString();      // "2023-11-14T22:13:20.000Z"
Date.parse("2026-10-03T09:00:00+09:00") / 1000; // 1790985600

В Python time.time() возвращает секунды в виде числа с плавающей точкой. Передавайте tz=timezone.utc, иначе fromtimestamp() вернёт местное время компьютера без часового пояса:

import time
from datetime import datetime, timezone

int(time.time())                                     # секунды
datetime.fromtimestamp(1700000000, tz=timezone.utc)  # 2023-11-14 22:13:20+00:00
datetime.fromisoformat("2026-10-03T09:00:00+09:00").timestamp()  # 1790985600.0

В SQL функции зависят от СУБД:

-- PostgreSQL
SELECT extract(epoch FROM now());   -- секунды с дробной частью
SELECT to_timestamp(1700000000);    -- timestamp with time zone
-- MySQL
SELECT UNIX_TIMESTAMP();
SELECT FROM_UNIXTIME(1700000000);   -- в часовом поясе сеанса

Несколько привычек при хранении предотвращают большинство проблем:

  • Храните уже произошедшие моменты в UTC — либо в столбце типа «метка времени с часовым поясом», либо как 64-битное целое число, единица которого указана в имени столбца, например created_at_ms.
  • Будущие местные события, например встречу в 09:00 в Сеуле, храните как местные дату и время вместе с названием пояса IANA и вычисляйте метку времени по мере необходимости, чтобы последующее изменение правил не сдвинуло встречу.
  • Не смешивайте единицы в одном столбце: ошибка в единице измерения даёт расхождение в 1000 раз и более.

Часто задаваемые вопросы

Почему моя метка времени превращается в дату в 1970 году?

Скорее всего, значение прочитано не в той единице: секунды, прочитанные как миллисекунды, попадают в январь 1970 года, а миллисекунды, прочитанные как секунды, — на десятки тысяч лет в будущее. Посчитайте цифры или выберите единицу в конвертере.

Зависит ли метка времени Unix от моего часового пояса?

Нет. Она отсчитывает секунды от фиксированного момента в UTC. От часового пояса зависят только отображаемые для неё дата и время.

Может ли Unix-время представить високосную секунду?

Нет. В Unix-времени каждые сутки считаются равными 86 400 секундам, поэтому у момента UTC вроде 23:59:60 нет собственной метки времени.