В задачах анализа временных рядов и календарных данных ключевым этапом становится приведение набора дат к логическим периодам. В JavaScript при использовании date-fns эта задача решается через комбинацию функций генерации интервалов и нормализации границ периодов.
Группировка по периодам обычно сводится к двум операциям:
date-fns предоставляет набор функций, которые формируют последовательности дат внутри заданного диапазона:
eachDayOfIntervaleachWeekOfIntervaleachMonthOfIntervaleachYearOfIntervalКаждая из них принимает объект интервала:
import { eachDayOfInterval } from "date-fns";
const result = eachDayOfInterval({
start: new Date(2024, 0, 1),
end: new Date(2024, 0, 7),
});
Результатом становится массив дат, каждая из которых представляет начало соответствующего периода (в случае дней — сами дни).
Почасовые или событийные данные часто приводятся к дневной агрегации.
Для этого используется нормализация через startOfDay и
последующее сопоставление.
import { startOfDay, format } from "date-fns";
const groupByDay = (items) => {
const groups = {};
items.forEach(({ date, value }) => {
const key = format(startOfDay(date), "yyyy-MM-dd");
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
});
return groups;
};
Ключевой принцип — все временные метки приводятся к началу дня, что устраняет влияние часов, минут и секунд.
Неделя в разных системах может начинаться с понедельника или
воскресенья. date-fns учитывает это через параметры локали и функцию
startOfWeek.
import { startOfWeek, format } from "date-fns";
const groupByWeek = (items) => {
const groups = {};
items.forEach(({ date, value }) => {
const weekStart = startOfWeek(date, { weekStartsOn: 1 });
const key = format(weekStart, "yyyy-'W'II");
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
});
return groups;
};
Использование ISO-нумерации недель позволяет избежать неоднозначности при переходе между годами.
Для более формализованного разбиения диапазона применяется:
import { eachWeekOfInterval } from "date-fns";
const weeks = eachWeekOfInterval({
start: new Date(2024, 0, 1),
end: new Date(2024, 2, 1),
});
Месячная агрегация часто используется в финансовой аналитике и отчетности. Основная идея — приведение даты к первому числу месяца.
import { startOfMonth, format } from "date-fns";
const groupByMonth = (items) => {
const groups = {};
items.forEach(({ date, value }) => {
const key = format(startOfMonth(date), "yyyy-MM");
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
});
return groups;
};
Для генерации списка месяцев внутри интервала используется:
import { eachMonthOfInterval } from "date-fns";
const months = eachMonthOfInterval({
start: new Date(2023, 0, 1),
end: new Date(2023, 11, 31),
});
Годовая агрегация применяется для долгосрочной статистики и тренд-анализа.
import { startOfYear, format } from "date-fns";
const groupByYear = (items) => {
const groups = {};
items.forEach(({ date, value }) => {
const key = format(startOfYear(date), "yyyy");
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
});
return groups;
};
Для построения диапазонов лет:
import { eachYearOfInterval } from "date-fns";
const years = eachYearOfInterval({
start: new Date(2015, 0, 1),
end: new Date(2025, 0, 1),
});
При работе с различными периодами удобнее использовать абстракцию, где тип периода определяет функцию нормализации.
import {
startOfDay,
startOfWeek,
startOfMonth,
startOfYear,
format,
} from "date-fns";
const strategies = {
day: (d) => startOfDay(d),
week: (d) => startOfWeek(d, { weekStartsOn: 1 }),
month: (d) => startOfMonth(d),
year: (d) => startOfYear(d),
};
const formatters = {
day: "yyyy-MM-dd",
week: "yyyy-'W'II",
month: "yyyy-MM",
year: "yyyy",
};
const groupByPeriod = (items, period) => {
const normalize = strategies[period];
const fmt = formatters[period];
const groups = {};
for (const { date, value } of items) {
const key = format(normalize(date), fmt);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
}
return groups;
};
Такой подход позволяет масштабировать систему агрегации без изменения логики обработки данных.
При группировке важно учитывать границы переходов между периодами:
date-fns обеспечивает корректную работу с календарными переходами за
счет использования локализованных правил и строгой работы с объектами
Date.
Комбинация each*OfInterval и группировочной логики
позволяет строить полные временные сетки.
import { eachDayOfInterval, format } from "date-fns";
const buildTimeline = (start, end, data) => {
const days = eachDayOfInterval({ start, end });
const map = new Map(
data.map((d) => [format(d.date, "yyyy-MM-dd"), d.value])
);
return days.map((day) => {
const key = format(day, "yyyy-MM-dd");
return {
date: day,
value: map.get(key) ?? 0,
};
});
};
Этот подход используется при построении графиков с непрерывной осью времени.
При работе с большими массивами данных важны следующие аспекты:
Map вместо объекта ускоряет доступ к
ключам при больших объемахВыбор формата ключа критически влияет на корректность агрегации:
yyyy-MM-dd — строгая дневная уникальностьyyyy-'W'II — ISO-недели без конфликтов с годамиyyyy-MM — стабильная месячная структураИспользование нестабильных форматов приводит к расхождению данных при визуализации и отчетности.
Сложные аналитические задачи требуют вложенной группировки: сначала по году, затем по месяцу, затем по дню.
const nestedGroup = (items) => {
const groups = {};
for (const { date, value } of items) {
const year = format(startOfYear(date), "yyyy");
const month = format(startOfMonth(date), "MM");
const day = format(startOfDay(date), "dd");
groups[year] ??= {};
groups[year][month] ??= {};
groups[year][month][day] ??= [];
groups[year][month][day].push(value);
}
return groups;
};
Такая структура используется в отчетных системах с иерархическим представлением данных.
Группировка по периодам в date-fns строится на трех базовых операциях:
startOf*)format)each*OfInterval)Комбинация этих механизмов позволяет формировать как простые агрегаты, так и сложные временные структуры без необходимости ручной работы с календарной арифметикой.