DevToys Pro

бесплатные веб-инструменты для разработчиков

Блог
Оцените нас:
Попробуйте расширение для браузера:
← Вернуться к блогу

Баги с временными метками: Секунды против миллисекунд

12 мин чтения

Вы отлаживаете интеграцию API, и даты отображаются как 1970 или 2053 год. Или, возможно, ваши временные метки работают отлично при локальной разработке, но ломаются в продакшене. Это классические симптомы путаницы с форматами временных меток: смешивание секунд и миллисекунд, неправильная обработка часовых поясов или использование неверного формата Unix epoch. Понимание того, как временные метки работают в разных системах, критически важно для надёжной обработки дат в API, базах данных и логах.

Проблема: Неопределённость Unix timestamp

Unix timestamp представляет время как количество единиц, прошедших с 1 января 1970 года 00:00:00 UTC (эпоха Unix). Но есть критическая неопределённость: какие единицы?

  • Секунды: 1704985200 (10 цифр, стандартный Unix timestamp)
  • Миллисекунды: 1704985200000 (13 цифр, стандарт JavaScript)
  • Микросекунды: 1704985200000000 (16 цифр, используется в некоторых БД)
  • Наносекунды: 1704985200000000000 (19 цифр, высокоточные системы)

Проблема? Нет универсального стандарта. Разные языки, API и системы используют разные единицы, а временные метки не несут метаданных о своём формате.

Классический баг: Дата отображается как 1970 или 2053

Вы получаете временную метку от API и конвертируете её в дату. Результат?

// API возвращает timestamp в секундах
const apiTimestamp = 1704985200;

// JavaScript ожидает миллисекунды
const date = new Date(apiTimestamp);
console.log(date.toISOString());
// Вывод: 1970-01-20T18:03:05.200Z  ❌ Неправильно!

Дата интерпретируется как 20 января 1970 года вместо 11 января 2026. Почему? Конструктор Date() в JavaScript ожидает миллисекунды, но API отправил секунды.

Решение: Конвертировать секунды в миллисекунды

// Умножить на 1000 для конвертации секунд в миллисекунды
const date = new Date(apiTimestamp * 1000);
console.log(date.toISOString());
// Вывод: 2026-01-11T10:00:00.000Z  ✅ Правильно!

И наоборот, если вы отправляете временные метки в API, который ожидает секунды, но вы предоставляете миллисекунды, даты будут отображаться в далёком будущем (год 2053+).

Как определить формат временной метки

При работе с неизвестными форматами временных меток используйте количество цифр как эвристику для определения:

function detectTimestampUnit(timestamp: number): string {
  const digits = timestamp.toString().length;
  
  if (digits === 10) return 'seconds';
  if (digits === 13) return 'milliseconds';
  if (digits === 16) return 'microseconds';
  if (digits === 19) return 'nanoseconds';
  
  return 'unknown';
}

// Примеры
console.log(detectTimestampUnit(1704985200));      // 'seconds'
console.log(detectTimestampUnit(1704985200000));   // 'milliseconds'

Эта эвристика работает до примерно 2286 года (когда Unix timestamp в секундах достигнет 10 миллиардов и станет 11-значным). Для практических целей она надёжна на десятилетия вперёд.

Форматы временных меток в разных языках программирования

Разные языки программирования используют разные соглашения:

JavaScript / TypeScript

  • Date.now() возвращает миллисекунды
  • new Date(timestamp) ожидает миллисекунды
  • date.getTime() возвращает миллисекунды
const now = Date.now();  // 1704985200000 (миллисекунды)
const date = new Date(now);
console.log(date.toISOString());  // '2026-01-11T10:00:00.000Z'

Python

  • time.time() возвращает секунды (с десятичной дробью для точности меньше секунды)
  • datetime.timestamp() возвращает секунды
import time
from datetime import datetime

now = time.time()  # 1704985200.123456 (секунды)
date = datetime.fromtimestamp(now)
print(date.isoformat())  # '2026-01-11T10:00:00.123456'

Java

  • System.currentTimeMillis() возвращает миллисекунды
  • Instant.now().toEpochMilli() возвращает миллисекунды
  • Instant.now().getEpochSecond() возвращает секунды
long millis = System.currentTimeMillis();  // 1704985200000
long seconds = millis / 1000;  // 1704985200

Go

  • time.Now().Unix() возвращает секунды
  • time.Now().UnixMilli() возвращает миллисекунды
  • time.Now().UnixNano() возвращает наносекунды
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println(now.Unix())      // 1704985200 (секунды)
    fmt.Println(now.UnixMilli()) // 1704985200000 (миллисекунды)
}

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

Базы данных также используют различные соглашения:

  • PostgreSQL: TIMESTAMP хранит значения datetime, а не числовые временные метки. Используйте EXTRACT(EPOCH FROM timestamp) для получения Unix-секунд.
  • MySQL: TIMESTAMP хранит значения datetime. Используйте UNIX_TIMESTAMP() для получения секунд.
  • MongoDB: Тип Date хранит миллисекунды с начала эпохи Unix (совместимо с JavaScript).
  • Redis: Хранит временные метки как строки или числа. Обычная практика — использовать секунды для TTL, миллисекунды для временных меток событий.

Пример PostgreSQL

-- Конвертировать timestamp в Unix-секунды
SELECT EXTRACT(EPOCH FROM NOW());  -- 1704985200.123456

-- Конвертировать Unix-секунды в timestamp
SELECT TO_TIMESTAMP(1704985200);  -- '2026-01-11 10:00:00+00'

Ловушки с часовыми поясами

Unix timestamp всегда в UTC, но для их отображения требуется контекст часового пояса. Типичные ошибки:

Ошибка #1: Отображение UTC как местного времени

const timestamp = 1704985200;  // 2026-01-11 10:00:00 UTC
const date = new Date(timestamp * 1000);

// ❌ Неправильно: Отображается в локальном часовом поясе браузера
console.log(date.toString());
// В Нью-Йорке: 'Sat Jan 11 2026 05:00:00 GMT-0500'
// В Токио: 'Sat Jan 11 2026 19:00:00 GMT+0900'

// ✅ Правильно: Явно отобразить в UTC
console.log(date.toISOString());
// '2026-01-11T10:00:00.000Z' (всегда UTC)

Ошибка #2: Создание временных меток из местного времени

// ❌ Неправильно: Предполагает локальный часовой пояс
const date = new Date('2026-01-11 10:00:00');
console.log(date.getTime() / 1000);
// Результат варьируется в зависимости от часового пояса пользователя!

// ✅ Правильно: Явно указать UTC
const utcDate = new Date('2026-01-11T10:00:00Z');
console.log(utcDate.getTime() / 1000);  // 1704985200 (последовательно)

Решение: Всегда использовать ISO 8601 с часовым поясом

При работе с читаемыми человеком датами всегда используйте формат ISO 8601 с явным указанием часового пояса:

  • UTC: 2026-01-11T10:00:00Z или 2026-01-11T10:00:00+00:00
  • Нью-Йорк (EST): 2026-01-11T05:00:00-05:00
  • Токио (JST): 2026-01-11T19:00:00+09:00

Реальный сценарий отладки

Вы интегрируетесь с внешним API, и временные метки выглядят неправильно. Вот как отладить:

Шаг 1: Проверить сырую временную метку

const apiResponse = {
  "created_at": 1704985200,
  "updated_at": 1704985200000
};

console.log('created_at digits:', apiResponse.created_at.toString().length);
// Вывод: 10 (вероятно, секунды)

console.log('updated_at digits:', apiResponse.updated_at.toString().length);
// Вывод: 13 (вероятно, миллисекунды)

Шаг 2: Протестировать конвертацию с известной датой

Конвертируйте временную метку и проверьте, соответствует ли она известной дате. Например, если в документации API сказано, что created_at — это 11 января 2026 года 10:00:00 UTC:

// Тестировать как секунды
const dateSeconds = new Date(1704985200 * 1000);
console.log(dateSeconds.toISOString());
// '2026-01-11T10:00:00.000Z' ✅ Совпадает!

// Тестировать как миллисекунды (неправильно)
const dateMillis = new Date(1704985200);
console.log(dateMillis.toISOString());
// '1970-01-20T18:03:05.200Z' ❌ Неправильный год

Шаг 3: Проверить обработку часового пояса

// Проверить, соответствует ли отображаемое время ожиданиям
const date = new Date(1704985200 * 1000);

console.log('ISO (UTC):', date.toISOString());
// '2026-01-11T10:00:00.000Z'

console.log('Local string:', date.toString());
// 'Sat Jan 11 2026 13:00:00 GMT+0300 (Москва, стандартное время)'

console.log('UTC string:', date.toUTCString());
// 'Sat, 11 Jan 2026 10:00:00 GMT'

Типичные ошибки конвертации временных меток

Ошибка: Дата на 50 лет в прошлом или будущем

Причина: Смешивание секунд и миллисекунд

Решение: Умножить на 1000, если временная метка в секундах, разделить на 1000, если в миллисекундах

Ошибка: Дата сдвинута на несколько часов

Причина: Путаница с часовыми поясами (отображение UTC как местного или наоборот)

Решение: Всегда использовать toISOString() или toUTCString() для последовательного отображения UTC

Ошибка: Временные метки работают локально, но ломаются в продакшене

Причина: Локальная машина разработки имеет другой часовой пояс, чем продакшен-серверы

Решение: Установить часовой пояс сервера в UTC, всегда использовать UTC для внутренних временных меток, конвертировать в местные часовые пояса только для отображения

Ошибка: Invalid Date при парсинге временных меток

Причина: Формат временной метки не распознан new Date()

Решение: Использовать числовые временные метки или строки ISO 8601, избегать форматов дат, зависящих от локали

Лучшие практики обработки временных меток

  1. Хранить временные метки как Unix-секунды или миллисекунды в UTC — никогда не хранить местное время без часового пояса
  2. Использовать формат ISO 8601 для читаемых человеком дат — всегда включать часовой пояс (Z или ±HH:MM)
  3. Документировать единицы временных меток в API-контрактах — указывать секунды, миллисекунды или формат ISO
  4. Определять формат по количеству цифр — 10 цифр = секунды, 13 = миллисекунды
  5. Использовать библиотеки для работы с датами при сложных операциях — например, date-fns, Luxon, Day.js (вместо сырого Date)
  6. Тестировать граничные случаи — границы года, високосные секунды, переходы на летнее время
  7. Установить часовой пояс сервера в UTC — избежать дрейфа часового пояса между окружениями

Использование инструментов конвертации временных меток

При отладке проблем с временными метками используйте конвертер временных меток для:

  • Конвертации между секундами, миллисекундами и читаемыми человеком датами
  • Тестирования значений временных меток с известными датами
  • Проверки обработки часовых поясов
  • Генерации временных меток для тестирования
  • Проверки текущего Unix времени для справки

Конвертер дат в DevToys Pro автоматически определяет формат временной метки, обрабатывает множество часовых поясов и обеспечивает мгновенную конвертацию между Unix timestamp, ISO 8601 и читаемыми человеком форматами.

Чек-лист валидации

Перед развёртыванием кода обработки временных меток проверьте:

  • ✅ Временные метки конвертированы в правильные единицы (секунды ↔ миллисекунды)
  • ✅ UTC используется для хранения и API-коммуникации
  • ✅ Часовой пояс явно указан при отображении дат
  • ✅ Протестированы граничные случаи (границы года, високосные годы)
  • ✅ В документации API указан формат временных меток
  • ✅ Часовой пояс сервера установлен в UTC
  • ✅ Используются библиотеки для работы с датами при сложной логике часовых поясов

Ключевые выводы

  • Unix timestamp может быть в секундах (10 цифр) или миллисекундах (13 цифр)
  • JavaScript ожидает миллисекунды; многие API отправляют секунды — конвертируйте соответственно
  • Всегда храните и передавайте временные метки в UTC, конвертируйте в местное время только для отображения
  • Используйте формат ISO 8601 с явным часовым поясом для читаемых человеком дат
  • Определяйте формат временной метки по количеству цифр как эвристику
  • Тестируйте конвертацию временных меток с известными датами для проверки корректности
  • Документируйте единицы временных меток в API-контрактах для предотвращения багов интеграции

Связанные инструменты: