Работа с временными данными в клиент-серверных приложениях почти
всегда включает этап преобразования «сырых» ответов API в структуры,
пригодные для дальнейшей обработки. В JavaScript этим промежуточным
слоем часто выступают строки формата ISO 8601, Unix timestamps или
нестандартизированные представления дат. Библиотека Luxon предоставляет
строгую модель работы со временем через объект DateTime,
что делает десериализацию ключевым этапом интеграции с внешними
источниками данных.
Типичный JSON-ответ может содержать даты в различных форматах:
"2026-05-23T14:30:00Z"17164794001716479400000"2026-05-23 14:30:00""Sat, 23 May 2026 14:30:00 GMT"Каждый формат требует собственного подхода при преобразовании в
DateTime.
Luxon не выполняет автоматическую десериализацию JSON, поэтому
преобразование выполняется вручную или через промежуточные механизмы
(например, reviver в JSON.parse).
Наиболее надёжный формат для обмена временем — ISO 8601. Luxon поддерживает его напрямую:
import { DateTime } from "luxon";
const json = `{
"createdAt": "2026-05-23T14:30:00Z"
}`;
const data = JSON.parse(json);
const createdAt = DateTime.fromISO(data.createdAt);
console.log(createdAt.toString());
Метод fromISO автоматически определяет:
Z → UTC)При отсутствии зоны время интерпретируется как локальное, что требует осторожности при работе с серверными данными.
Многие API возвращают время в виде числовых меток.
const timestamp = 1716479400;
const dt = DateTime.fromSeconds(timestamp);
const timestampMs = 1716479400000;
const dt = DateTime.fromMillis(timestampMs);
Разделение этих двух случаев критично: ошибка в единицах измерения приводит к смещению даты на десятки тысяч лет.
Реальные API часто смешивают форматы. Для этого создаётся слой нормализации:
function parseDate(value) {
if (typeof value === "string") {
return DateTime.fromISO(value);
}
if (typeof value === "number") {
if (value < 1e12) {
return DateTime.fromSeconds(value);
}
return DateTime.fromMillis(value);
}
return null;
}
Такой подход позволяет централизовать логику десериализации и исключить дублирование кода.
JSON.parse поддерживает функцию преобразования
значений:
const json = `{
"createdAt": "2026-05-23T14:30:00Z",
"updatedAt": "2026-05-22T10:00:00Z"
}`;
const data = JSON.parse(json, (key, value) => {
if (key.toLowerCase().includes("at") && typeof value === "string") {
return DateTime.fromISO(value);
}
return value;
});
Такой механизм позволяет автоматически преобразовывать все поля,
содержащие даты, без ручного вызова fromISO для каждого
значения.
Важно учитывать ограничение: reviver не различает семантику поля, поэтому эвристики по имени ключа должны быть строго определены.
Некоторые API используют локальные форматы дат, не совместимые с ISO.
const dt = DateTime.fromRFC2822("Sat, 23 May 2026 14:30:00 GMT");
const dt = DateTime.fromSQL("2026-05-23 14:30:00");
Luxon поддерживает SQL-формат без дополнительного парсинга, что полезно при работе с legacy-бэкендами.
Одной из ключевых проблем является отсутствие или некорректность временной зоны.
const dt = DateTime.fromISO("2026-05-23T14:30:00", {
zone: "utc"
});
Варианты поведения:
zonesetZone: true при необходимости
сохранения исходной зоныconst utc = DateTime.fromISO(value).toUTC();
Стандартизация к UTC часто используется как промежуточный слой при десериализации API-ответов.
API-ответы обычно содержат смешанные структуры:
const response = {
id: 1,
name: "Event",
createdAt: "2026-05-23T14:30:00Z",
schedule: {
start: "2026-05-24T09:00:00Z",
end: "2026-05-24T11:00:00Z"
}
};
Для преобразования в «обогащённую» модель применяется гидратация:
function hydrateEvent(data) {
return {
...data,
createdAt: DateTime.fromISO(data.createdAt),
schedule: {
start: DateTime.fromISO(data.schedule.start),
end: DateTime.fromISO(data.schedule.end)
}
};
}
Такой слой отделяет сетевой формат от доменной модели приложения.
В некоторых архитектурах преобразование откладывается до момента использования:
class EventModel {
constructor(raw) {
this.raw = raw;
}
get createdAt() {
return DateTime.fromISO(this.raw.createdAt);
}
}
Преимущество — минимизация работы при загрузке данных. Недостаток — повторный парсинг при каждом обращении.
При частых обращениях к одним и тем же данным создаётся кэш:
class EventModel {
constructor(raw) {
this.raw = raw;
this._createdAt = DateTime.fromISO(raw.createdAt);
}
get createdAt() {
return this._createdAt;
}
}
Такой подход фиксирует момент преобразования и устраняет повторные вычисления.
Luxon возвращает объект Invalid DateTime при ошибках
парсинга.
const dt = DateTime.fromISO("invalid-date");
if (!dt.isValid) {
console.log(dt.invalidReason);
}
При десериализации это состояние требует централизованной обработки:
function safeParseISO(value) {
const dt = DateTime.fromISO(value);
return dt.isValid ? dt : null;
}
После преобразования данные часто приводятся к единому формату:
const normalized = {
createdAt: DateTime.fromISO(data.createdAt).toUTC().toISO(),
localView: DateTime.fromISO(data.createdAt).toLocal()
};
При работе с сетевыми библиотеками десериализация часто выносится в interceptor.
async function fetchEvent(id) {
const res = await fetch(`/api/events/${id}`);
const data = await res.json();
return hydrateEvent(data);
}
axios.interceptors.response.use((response) => {
if (response.data.createdAt) {
response.data.createdAt = DateTime.fromISO(response.data.createdAt);
}
return response;
});
Для сложных JSON используется рекурсивный обход:
function deepParseDates(obj) {
if (Array.isArray(obj)) {
return obj.map(deepParseDates);
}
if (obj && typeof obj === "object") {
const result = {};
for (const key in obj) {
const value = obj[key];
if (typeof value === "string" && key.includes("At")) {
result[key] = DateTime.fromISO(value);
} else {
result[key] = deepParseDates(value);
}
}
return result;
}
return obj;
}
При десериализации важно учитывать различие между:
Luxon позволяет фиксировать каждую из этих точек:
const serverTime = DateTime.fromISO(data.createdAt, { zone: "utc" });
const receivedAt = DateTime.now();
Это создаёт основу для анализа задержек и синхронизации состояния.
Хотя тема сосредоточена на десериализации, важно учитывать обратное преобразование:
const payload = {
createdAt: DateTime.now().toISO()
};
При несогласованной десериализации возникает расхождение типов между входом и выходом системы, что требует симметричной логики преобразования.
В TypeScript часто отделяются «сырые» и «обогащённые» модели:
type ApiEvent = {
createdAt: string;
};
type DomainEvent = {
createdAt: DateTime;
};
Функция преобразования становится контрактом между слоями:
function toDomain(data: ApiEvent): DomainEvent {
return {
createdAt: DateTime.fromISO(data.createdAt)
};
}
При обработке больших массивов событий:
const events = data.map(e => ({
...e,
createdAt: DateTime.fromISO(e.createdAt)
}));
Ключевыми факторами становятся:
Десериализация должна быть:
Luxon обеспечивает эти свойства через явные методы
fromISO, fromMillis, fromSeconds,
что исключает скрытую магию преобразования и делает поведение
предсказуемым в распределённых системах