Работа с базами данных

Работа с датами и временем в контексте баз данных требует строгой дисциплины в выборе форматов, единиц хранения и стратегии сериализации. Luxon предоставляет слой абстракции над стандартным объектом Date, устраняя множество проблем, связанных с часовыми поясами, локалями и неоднозначностью временных меток.

Представление даты и времени в Luxon при взаимодействии с БД

В Luxon центральной сущностью является DateTime. Этот объект не предназначен для прямого хранения в базе данных. Его задача — представление момента времени с учётом временной зоны, локали и формата.

Перед записью в базу данных DateTime всегда приводится к одному из канонических форматов:

  • ISO 8601 строка
  • Unix timestamp (миллисекунды или секунды)
  • SQL timestamp (зависит от СУБД)
  • JSON-строка (как правило, ISO)

Ключевой принцип: база данных не должна хранить «объект времени», она хранит только его представление.


Форматы хранения времени

ISO 8601 как универсальный стандарт

Наиболее безопасный формат для обмена между приложением и базой данных — ISO 8601.

Luxon предоставляет метод:

dateTime.toISO()

Пример результата:

2026-05-23T14:35:12.123Z

Особенность:

  • сохраняется абсолютный момент времени
  • включается информация о временной зоне (или UTC)
  • легко сериализуется в JSON
  • совместим с большинством СУБД

Unix timestamp как числовое представление

Unix timestamp удобен для индексации и сравнений.

В Luxon:

dateTime.toMillis()

или

dateTime.toSeconds()

Пример:

const dt = DateTime.now().toUTC();
const ts = dt.toMillis();

Преимущества:

  • высокая производительность при сравнении
  • компактное хранение (BIGINT)
  • отсутствие проблем с локалями

Недостатки:

  • потеря читаемости
  • необходимость конвертации при отладке

SQL timestamp

Многие СУБД поддерживают собственный тип:

  • PostgreSQL: TIMESTAMP / TIMESTAMPTZ
  • MySQL: DATETIME / TIMESTAMP

Luxon не взаимодействует с SQL напрямую, но обеспечивает корректную подготовку значения:

dateTime.toUTC().toISO()

или

dateTime.toJSDate()

Сериализация Luxon DateTime перед записью в БД

Перед сохранением данных важно привести DateTime к стабильному виду.

Приведение к UTC

Основная ошибка при работе с датами — сохранение локального времени без нормализации.

import { DateTime } fr om "luxon";

const dt = DateTime.local().setZone("Europe/Berlin");
const normalized = dt.toUTC();

Результат:

  • единая точка отсчёта
  • отсутствие смещения при смене сервера
  • корректная агрегация данных

Подготовка записи для SQL

const event = {
  title: "Meeting",
  start_time: DateTime.now().toUTC().toISO()
};

SQL:

INS ERT INTO events (title, start_time)
VALUES ($1, $2);

Подготовка числового timestamp

const event = {
  created_at: DateTime.now().toMillis()
};

Используется при схемах:

created_at BIGINT NOT NULL

Десериализация данных из базы

Данные из базы всегда приходят в одном из трёх видов:

  • строка ISO
  • число (timestamp)
  • SQL datetime строка

Из ISO строки

import { DateTime } from "luxon";

const dt = DateTime.fromISO(row.start_time);

Из Unix timestamp

const dt = DateTime.fromMillis(row.created_at);

Из SQL строки

const dt = DateTime.fromSQL(row.start_time);

SQL формат зависит от базы:

2026-05-23 14:35:12

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

Базы данных не должны интерпретировать временные зоны на уровне бизнес-логики. Luxon выполняет эту задачу до записи.

Нормализация всех значений в UTC

Стандартный подход:

const utcTime = DateTime.now().toUTC();

Сохранение:

utcTime.toISO();

Восстановление:

const dt = DateTime.fromISO(dbValue, { zone: "utc" });

Хранение локального времени (антипаттерн)

DateTime.local().toISO(); // нежелательно для БД

Проблемы:

  • неоднозначность при смене часового пояса
  • ошибки при миграции серверов
  • некорректные отчёты

Работа с ORM и Luxon

Prisma

Prisma возвращает Date, поэтому требуется конвертация:

const dt = DateTime.fromJSDate(record.createdAt);

Перед записью:

data.createdAt = DateTime.now().toUTC().toJSDate();

Sequelize

Sequelize может автоматически конвертировать Date, но Luxon используется для логики:

const dt = DateTime.fromJSDate(user.createdAt);

Knex

При использовании Knex контроль форматов полностью на стороне приложения:

await knex("events").insert({
  start_time: DateTime.now().toUTC().toISO()
});

JSON API и база данных

Часто возникает двойная сериализация: БД → API → клиент.

Стандартная схема передачи

Из БД:

const dt = DateTime.fromISO(row.start_time);

В API:

return {
  start_time: dt.toISO()
};

Альтернативный формат: epoch

return {
  start_time: dt.toMillis()
};

Выбор зависит от архитектуры:

  • ISO — читаемость и совместимость
  • epoch — производительность и компактность

Сравнение стратегий хранения

ISO 8601

Подходит для:

  • REST API
  • логирования
  • интеграций

Характеристики:

  • человекочитаемый формат
  • поддержка временных зон
  • стандарт ISO

Unix timestamp

Подходит для:

  • high-load систем
  • аналитики
  • событийных потоков

Характеристики:

  • быстрые сравнения
  • минимальный размер
  • отсутствие локализации

SQL timestamp

Подходит для:

  • прямой работы с SQL
  • отчётности
  • legacy систем

Типичные ошибки при использовании Luxon с БД

Смешивание локального времени и UTC

DateTime.local().toISO(); // риск
DateTime.now().toUTC().toISO(); // корректно

Повторная интерпретация уже нормализованных данных

DateTime.fromISO(dbValue).toLocal();

Приводит к смещению времени.


Потеря информации о зоне

DateTime.fromJSDate(date); // зона неявна

Правильнее:

DateTime.fromJSDate(date, { zone: "utc" });

Хранение DateTime напрямую

// нельзя
ins ert({ time: DateTime.now() });

База данных не понимает объект Luxon.


Индексация и производительность

При проектировании схемы хранения времени важно учитывать:

  • числовые timestamp быстрее индексируются
  • ISO строки требуют дополнительного парсинга при сортировке
  • TIMESTAMP типы в SQL оптимизированы для диапазонных запросов

Пример диапазонного запроса:

SEL ECT *
FR OM events
WH ERE start_time BETWEEN '2026-05-01' AND '2026-06-01';

При epoch:

WHERE start_time BETWEEN 1714521600000 AND 1717200000000;

Практический шаблон архитектуры

Слой записи

function serializeDateTime(dt) {
  return dt.toUTC().toISO();
}

Слой чтения

function deserializeDateTime(val ue) {
  return DateTime.fromISO(val ue, { zone: "utc" });
}

Унификация модели

const event = {
  start: serializeDateTime(DateTime.now()),
};

Использование Luxon как единого временного слоя

Luxon выступает как промежуточный слой между:

  • бизнес-логикой
  • базой данных
  • API
  • внешними сервисами

Ключевой принцип архитектуры: база данных хранит данные, Luxon управляет смыслом времени.