Работа с часовыми поясами

timeago.js не имеет встроенного механизма управления часовыми поясами. Библиотека вычисляет разницу между двумя timestamp-значениями, оба из которых привязаны к UTC. Поэтому часовой пояс не влияет на результат при правильном использовании входных данных.


Принцип работы с временными зонами

Вычисление разницы внутри библиотеки:

const diff = Date.now() - new Date(input).getTime();

Оба значения — Date.now() и .getTime() — возвращают UTC-миллисекунды. Это означает:

  • локальный часовой пояс пользователя не влияет на вычисление разницы;
  • сервер и клиент получат одинаковый результат, если входная дата зафиксирована в UTC.

UTC-строки — безопасный вариант

import { format } from 'timeago.js';

// Строка явно в UTC — результат одинаков в любом часовом поясе
format('2025-05-26T10:00:00Z', 'ru');

Строки со смещением

// UTC+3 — тоже корректно
format('2025-05-26T13:00:00+03:00', 'ru');

Оба примера выше математически эквивалентны, так как оба указывают на один и тот же момент UTC.


Ловушка: строки без часового пояса

// Без суффикса — браузер интерпретирует как локальное время
format('2025-05-26T10:00:00'); // поведение зависит от браузера

В Chrome и Firefox такая строка трактуется как локальное время. В старых Safari — поведение могло быть иным.

Если сервер отдаёт строки без пояса и подразумевает UTC:

const rawDate = '2025-05-26T10:00:00';
format(rawDate + 'Z', 'ru'); // явно добавить Z

Проблема с “2025-05-26” (только дата)

format('2025-05-26');

ECMAScript трактует строку YYYY-MM-DD как UTC-полночь. Это означает: если пользователь в UTC+3, то “26 мая” будет показано как “3 часа назад” в 00:00 UTC, хотя пользователь ждёт “сегодня в полночь”.

Явное добавление локального времени решает проблему:

// Если дата предназначена для локального времени:
const localMidnight = new Date(2025, 4, 26); // месяц 0-based
format(localMidnight, 'ru');

Работа с серверным временем в другом часовом поясе

Если сервер возвращает локальное время сервера (например, UTC+5) без явного указания пояса:

const serverLocalTime = '2025-05-26T10:00:00'; // UTC+5 без указания

// Некорректно: браузер считает это локальным временем клиента
format(serverLocalTime, 'ru'); // ошибка, если у клиента не UTC+5

Правильное решение — всегда передавать UTC с суффиксом Z или со смещением:

const serverUTC = '2025-05-26T05:00:00Z'; // UTC+5 → UTC = -5 часов
format(serverUTC, 'ru'); // корректно

Принудительная интерпретация как UTC

Если приходят строки без пояса и их нужно считать UTC:

function parseAsUTC(str) {
  if (str.endsWith('Z') || str.includes('+') || str.includes('-', 10)) {
    return new Date(str);
  }
  return new Date(str + 'Z');
}

format(parseAsUTC('2025-05-26T10:00:00'), 'ru');

Принудительная интерпретация как локальное время

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

function parseAsLocal(str) {
  // Парсим компоненты и создаём Date как локальное время
  const [date, time = '00:00:00'] = str.split('T');
  const [year, month, day] = date.split('-').map(Number);
  const [hour, minute, second] = time.split(':').map(Number);
  return new Date(year, month - 1, day, hour, minute, second);
}

format(parseAsLocal('2025-05-26T10:00:00'), 'ru');

Временные зоны в SSR

На сервере Node.js часовой пояс определяется переменной окружения TZ:

TZ=Europe/Moscow node server.js

Это влияет на new Date() без явного пояса. Для предсказуемого поведения:

  1. Хранить даты в UTC.
  2. Передавать их в ISO-формате с суффиксом Z.
  3. Не полагаться на системный часовой пояс сервера.

Использование Intl.DateTimeFormat для отображения временно́й зоны

timeago.js не показывает временны́е зоны в строке вывода. Если нужен вывод типа “2 часа назад (UTC+3)”, это реализуется постобработкой:

import { format } from 'timeago.js';

function formatWithZone(date, locale = 'ru') {
  const relative = format(date, locale);
  const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  return `${relative} (${zone})`;
}

Конвертация между часовыми поясами

timeago.js не предоставляет инструментов для конвертации зон. Для этого используются специализированные библиотеки:

import { zonedTimeToUtc } from 'date-fns-tz';
import { format } from 'timeago.js';

// Создать дату в конкретной зоне и конвертировать в UTC
const localDate = zonedTimeToUtc('2025-05-26T10:00:00', 'America/New_York');
format(localDate, 'en_US');

Практические рекомендации

  • Всегда хранить и передавать даты в UTC (ISO 8601 с Z).
  • Никогда не передавать строки без часового пояса, если источник неизвестен.
  • При получении дат от серверных API уточнить формат и добавить явное смещение при необходимости.
  • Для SSR передавать relativeDate явно, чтобы избежать расхождений между сервером и клиентом.