Введение: почему стандартного Date() недостаточно?
Встроенный объект Date в JavaScript — это мощный, но не всегда удобный инструмент. При разработке сложных приложений на Node.js (например, планировщиков задач, систем аналитики или финансовых сервисов) разработчики сталкиваются с рядом проблем:
- Мутабельность: методы
setMonth()изменяют исходный объект. - Ограниченная поддержка часовых поясов:
Dateработает только с локальным временем и UTC. - Сложность форматирования: нет встроенного аналога
strftime. - Парсинг строк:
new Date("2023-13-01")не выбросит ошибку, а создаст "2024-01-01".
В этой статье мы рассмотрим продвинутые подходы к работе с датами и временем в Node.js, которые помогут избежать этих проблем и писать более надежный код.
1. Intl.DateTimeFormat: форматирование без внешних зависимостей
Современный JavaScript (ES6+) предоставляет встроенный API интернационализации Intl.DateTimeFormat. Это мощный инструмент, который позволяет форматировать даты и время под любую локаль без подключения сторонних библиотек.
Базовое использование
const date = new Date('2024-03-15T14:30:00Z');
// Форматирование для русской локали
const formatterRU = new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: '2-digit',
minute: '2-digit'
});
console.log(formatterRU.format(date));
// "пятница, 15 марта 2024 г., 17:30"
Продвинутые опции: часовые пояса и относительное время
// Форматирование с учетом часового пояса
const formatterTZ = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
dateStyle: 'full',
timeStyle: 'short'
});
console.log(formatterTZ.format(date));
// "Friday, March 15, 2024 at 10:30 AM"
// Относительное форматирование (Intl.RelativeTimeFormat)
const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });
console.log(rtf.format(-5, 'day')); // "5 дней назад"
console.log(rtf.format(2, 'month')); // "через 2 месяца"
Этот подход идеален, когда нужно избежать лишних зависимостей, но требуется гибкое форматирование для пользовательского интерфейса.
2. Библиотека date-fns: модульность и функциональность
date-fns — это современная библиотека, построенная на принципах функционального программирования. Её главное преимущество — модульность: вы импортируете только те функции, которые нужны, что значительно уменьшает размер бандла.
Установка и базовые примеры
// Установка: npm install date-fns
import { format, addDays, subMonths, differenceInDays, isWeekend } from 'date-fns';
import { ru } from 'date-fns/locale';
const today = new Date();
// Форматирование с локалью
console.log(format(today, 'dd MMMM yyyy, EEEE', { locale: ru }));
// "15 марта 2024, пятница"
// Арифметика дат
const nextWeek = addDays(today, 7);
const lastMonth = subMonths(today, 1);
// Разница между датами
const diff = differenceInDays(nextWeek, today);
console.log(diff); // 7
// Проверка выходного дня
console.log(isWeekend(today)); // true или false
Сложные сценарии: сравнение и валидация
import { parse, isValid, isBefore, isAfter, startOfMonth, endOfMonth } from 'date-fns';
// Безопасный парсинг
const parsed = parse('2024-13-01', 'yyyy-MM-dd', new Date());
console.log(isValid(parsed)); // false — невалидная дата
// Работа с диапазонами
const start = startOfMonth(new Date(2024, 2, 1)); // 1 марта
const end = endOfMonth(new Date(2024, 2, 1)); // 31 марта
const targetDate = new Date(2024, 2, 15);
if (isAfter(targetDate, start) && isBefore(targetDate, end)) {
console.log('Дата находится в марте 2024');
}
date-fns отлично подходит для проектов, где важна производительность и минимальный размер зависимостей.
3. Luxon: объектно-ориентированный подход с поддержкой часовых поясов
Luxon — это наследник популярной библиотеки Moment.js, но с современным API и полной поддержкой часовых поясов через IANA-идентификаторы. Она предоставляет неизменяемые объекты DateTime.
Установка и создание объектов
// Установка: npm install luxon
import { DateTime } from 'luxon';
// Текущее время в UTC
const nowUTC = DateTime.utc();
console.log(nowUTC.toString()); // "2024-03-15T14:30:00.000Z"
// Время в конкретном часовом поясе
const inMoscow = DateTime.now().setZone('Europe/Moscow');
console.log(inMoscow.toISO()); // "2024-03-15T17:30:00.000+03:00"
// Создание из ISO-строки
const fromISO = DateTime.fromISO('2024-03-15T14:30:00', { zone: 'America/New_York' });
Арифметика, форматирование и преобразование
const dt = DateTime.fromObject({ year: 2024, month: 3, day: 15, hour: 14 }, { zone: 'Asia/Tokyo' });
// Арифметика (неизменяемые операции)
const future = dt.plus({ days: 10, hours: 5 });
console.log(future.toFormat('dd.MM.yyyy HH:mm')); // "25.03.2024 19:00"
// Разница между датами
const diff = future.diff(dt, ['days', 'hours']).toObject();
console.log(diff); // { days: 10, hours: 5 }
// Переход в другой часовой пояс
const inLondon = dt.setZone('Europe/London');
console.log(inLondon.toFormat('HH:mm')); // "05:00" (разница -9 часов)
Работа с интервалами
import { Interval } from 'luxon';
const start = DateTime.fromISO('2024-03-01');
const end = DateTime.fromISO('2024-03-31');
const interval = Interval.fromDateTimes(start, end);
// Проверка вхождения
const checkDate = DateTime.fromISO('2024-03-15');
console.log(interval.contains