При обработке тысяч и миллионов дат производительность становится критически важной. Ошибки в организации вычислений приводят к:
Библиотека date-fns предоставляет функциональный подход к обработке дат, что особенно полезно в высоконагруженных системах:
Все функции в Date-fns возвращают новый объект Date, не
изменяя исходный.
import { addDays } from 'date-fns'
const original = new Date()
const upd ated = addDays(original, 5)
console.log(original)
console.log(upd ated)
Такой подход упрощает масштабируемую обработку массивов дат:
const upd atedDates = dates.map(date => addDays(date, 1))
Однако при миллионах операций это означает создание миллионов
объектов Date.
Наиболее частые причины деградации:
Одно из самых затратных действий — преобразование строк в объекты
Date.
Плохой пример:
records.forEach(record => {
const date = parseISO(record.createdAt)
if (isToday(date)) {
process(record)
}
})
Если обработка выполняется многократно, строки будут постоянно парситься заново.
Более эффективный подход:
const normalized = records.map(record => ({
...record,
createdAtDate: parseISO(record.createdAt)
}))
После нормализации:
normalized.forEach(record => {
if (isToday(record.createdAtDate)) {
process(record)
}
})
Date-fns хорошо сочетается со стандартной сортировкой JavaScript.
import { compareAsc } from 'date-fns'
events.sort((a, b) => compareAsc(a.date, b.date))
Если данные уже находятся в ISO-формате:
records.sort((a, b) => (
a.createdAt.localeCompare(b.createdAt)
))
Такой вариант зачастую быстрее, чем:
compareAsc(parseISO(a.createdAt), parseISO(b.createdAt))
поскольку исключает создание объектов Date.
import { format } from 'date-fns'
const grouped = {}
for (const item of records) {
const key = format(item.date, 'yyyy-MM-dd')
if (!grouped[key]) {
grouped[key] = []
}
grouped[key].push(item)
}
format() — достаточно тяжёлая функция. При миллионах
вызовов это становится заметно.
Иногда выгоднее использовать:
const key = item.date.toISOString().slice(0, 10)
Это уменьшает накладные расходы.
Для высоконагруженных вычислений часто выгоднее хранить время в миллисекундах.
const timestamps = dates.map(date => date.getTime())
Сравнение:
if (aTimestamp > bTimestamp) {
// ...
}
const date = new Date(timestamp)
const result = records.filter(item => {
return isWithinInterval(item.date, {
start,
end
})
})
Функция isWithinInterval() удобна, но при экстремальных
объёмах создаёт дополнительную нагрузку.
const startTime = start.getTime()
const endTime = end.getTime()
const result = records.filter(item => {
const time = item.date.getTime()
return time >= startTime && time <= endTime
})
Форматирование — одна из самых дорогих операций.
format(date, 'dd.MM.yyyy HH:mm:ss')
При обработке десятков тысяч записей это становится узким местом.
const cache = new Map()
function formatCached(date) {
const key = date.getTime()
if (cache.has(key)) {
return cache.get(key)
}
const formatted = format(date, 'yyyy-MM-dd')
cache.se t(key, formatted)
return formatted
}
Обработка огромного массива за один проход может заблокировать поток выполнения.
function processChunked(items, size, callback) {
let index = 0
function next() {
const chunk = items.slice(index, index + size)
if (chunk.length === 0) {
return
}
callback(chunk)
index += size
setTimeout(next)
}
next()
}
Использование:
processChunked(records, 1000, chunk => {
chunk.forEach(processRecord)
})
import { eachDayOfInterval } from 'date-fns'
const days = eachDayOfInterval({
start,
end
})
При больших интервалах это создаёт огромный массив.
Диапазон в 20 лет:
eachDayOfInterval({
start: new Date(2000, 0, 1),
end: new Date(2020, 0, 1)
})
создаст более 7000 объектов Date.
Вместо создания массива выгоднее использовать генератор.
import { addDays } from 'date-fns'
function* generateDays(start, end) {
let current = start
while (current <= end) {
yield current
current = addDays(current, 1)
}
}
Использование:
for (const day of generateDays(start, end)) {
console.log(day)
}
Такой подход значительно снижает потребление памяти.
При частом поиске по диапазонам полезно заранее строить индексы.
const index = new Map()
records.forEach(record => {
const key = format(record.date, 'yyyy-MM-dd')
if (!index.has(key)) {
index.se t(key, [])
}
index.get(key).push(record)
})
Поиск:
const dayRecords = index.get('2026-05-22')
const unique = Array.from(
new Se t(
dates.map(date => date.getTime())
)
).map(timestamp => new Date(timestamp))
Операции с часовыми поясами существенно увеличивают нагрузку.
Особенно затратны:
formatInTimeZone;Для высоконагруженных систем:
Типичная задача — обработка миллионов временных меток.
[
{
timestamp: '2026-05-22T10:15:00Z'
}
]
const normalized = logs.map(log => ({
...log,
time: Date.parse(log.timestamp)
}))
Фильтрация:
const filtered = normalized.filter(log => {
return log.time >= startTime &&
log.time <= endTime
})
Сортировка:
filtered.sort((a, b) => a.time - b.time)
Форматирование только перед выводом:
filtered.map(log => ({
...log,
formatted: format(log.time, 'HH:mm:ss')
}))
Календарные интерфейсы часто генерируют тысячи дат.
months.forEach(month => {
eachDayOfInterval({
start: startOfMonth(month),
end: endOfMonth(month)
})
})
const memo = new Map()
function getMonthKey(date) {
const key = date.getTime()
if (memo.has(key)) {
return memo.get(key)
}
const result = format(date, 'yyyy-MM')
memo.set(key, result)
return result
}
В браузере тяжёлые операции над датами лучше переносить в Web Workers.
Date-fns хорошо подходит для worker-архитектуры благодаря чистым функциям и отсутствию глобального состояния.
const stats = records.reduce((acc, item) => {
const key = format(item.date, 'yyyy-MM-dd')
acc[key] = (acc[key] || 0) + 1
return acc
}, {})
Часто выгодно один раз подготовить структуру:
const prepared = records.map(record => ({
id: record.id,
timestamp: Date.parse(record.createdAt)
}))
После этого:
Плохой пример:
records.filter(record => {
return isSameDay(
parseISO(record.createdAt),
parseISO(target)
)
})
Здесь происходит:
Оптимизированный вариант:
const targetDate = parseISO(target)
const targetTime = targetDate.getTime()
records.filter(record => {
return isSameDay(
record.createdAtDate,
targetDate
)
})
import { differenceInMinutes } from 'date-fns'
const durations = sessions.map(session => (
differenceInMinutes(
session.end,
session.start
)
))
При экстремальных объёмах иногда выгоднее:
const durations = sessions.map(session => (
(session.end.getTime() - session.start.getTime()) / 60000
))
Большие массивы дат создают сильную нагрузку на GC.
Особенно опасны:
map/filter/reduce;Вместо:
records
.map(transform)
.filter(validate)
.map(formatRecord)
иногда эффективнее:
const result = []
for (const record of records) {
const transformed = transform(record)
if (!validate(transformed)) {
continue
}
result.push(formatRecord(transformed))
}
| Операция | Скорость |
|---|---|
timestamp comparison |
Очень высокая |
Date comparison |
Высокая |
parseISO |
Средняя |
format |
Низкая |
| timezone formatting | Очень низкая |
const normalized = rawData.map(item => ({
...item,
timestamp: Date.parse(item.createdAt)
}))
const byDay = new Map()
const filtered = normalized.filter(item => (
item.timestamp >= start &&
item.timestamp <= end
))
const grouped = filtered.reduce((acc, item) => {
const key = item.timestamp
acc[key] = (acc[key] || 0) + 1
return acc
}, {})
Object.entries(grouped).map(([key, value]) => ({
date: format(Number(key), 'yyyy-MM-dd'),
count: value
}))
format;parseISO;Date;