JSON Toolkit

В каком формате метки времени в JSON

Главное: Эпохи и ISO-строки имеют свои компромиссы, но именно их смешивание реально ломает всё. Используйте ISO 8601 со смещением, храните UTC, отображайте в часовом поясе пользователя.

Начинающий~3 мин чтенияОпубликовано 2026-10-30Обновлено 2026-10-30
Выбор формата метки времени для JSON API

Главное: используйте ISO 8601 с явным смещением в передаче, храните UTC с завершающим Z и конвертируйте только при отображении; если вы вместо этого выбираете числовые эпохи, глобально зафиксируйте секунды против миллисекунд и поместите единицу в имя поля.

Почему 2026-10-30 14:30:00 недостаточно

Эта строка не несёт зону. 14:30 в Шанхае и 14:30 в Лондоне различаются на восемь часов, и нет способа понять, что имеется в виду. new Date('2026-10-30 14:30:00') разбирается в локальной зоне среды выполнения, поэтому сервер и браузер выводят два разных момента из одной полезной нагрузки — тот тип бага, который никогда не воспроизводится.

Голая 2026-10-30 — вовсе не момент. Подходит для дней рождения и периодов оплаты, неверна для времени создания.

Два рабочих формата

Строки ISO 8601

{
  "createdAt": "2026-10-30T14:30:00+08:00",
  "updatedAt": "2026-10-30T06:30:00Z"
}

Читаемо, сортируемо при согласованном формате, с точностью до миллисекунд при необходимости, и самодокументируемо.

Числовые эпохи

{ "createdAt": 1793519400 }

Компактно и однозначно, но нечитаемо, и секунды против миллисекунд — постоянная угроза. Если выбираете эпохи, стандартизируйте единицу и поместите её в имя поля: createdAtMs гораздо безопаснее, чем createdAt.

Соглашения, которые стоит зафиксировать рано

Пункт Соглашение
Формат передачи ISO 8601 с Z или +08:00
Зона хранения UTC, чтобы летнее время никогда не входило в картину
Точность Миллисекунд достаточно
Именование полей createdAt, updatedAt, expiresAt
Чистые даты Отдельное поле date, например birthday: "1990-01-01"
Длительности Целое плюс единица, например timeoutMs: 3000

Отображение

new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'Europe/London',
}).format(new Date('2026-10-30T06:30:00Z'));

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

Три повторяющихся бага

Голые даты разбираются как полночь UTC. Согласно спецификации ECMAScript, строки только-даты являются UTC, а строки дата-время — локальными. Эта несогласованность — надёжный источник багов со смещением на день.

Зона базы данных расходится с сериализатором. datetime в MySQL не имеет зоны; запишите локальное, читайте как UTC, и всё сдвигается на смещение.

Летнее время. Хранение будущих локальных времён создаёт моменты, которые не существуют или происходят дважды. В UTC таких пробелов нет.

Вопросы и ответы

Как выбрать между ISO 8601 и числовыми эпохами?

Используйте ISO 8601, когда полезную нагрузку читают люди, когда нужен лексикографический порядок сортировки или когда значение должно нести свою зону; цена — примерно дюжина лишних байтов и библиотека парсинга. Используйте числовые эпохи, когда компактность и тривиальная кросс-языковая обработка важнее, но глобально зафиксируйте секунды против миллисекунд и укажите, какую единицу содержит поле.

Должны ли голая дата и метка времени использовать одно поле?

Нет. Голая 2026-10-30 — это календарный день без времени и без зоны, тогда как поле метки времени должно нести точный момент. Держите дни рождения и периоды оплаты в отдельном поле date, а для времени создания или обновления используйте ISO 8601-поле At.

Дополнительное чтение