Давайте разберем эту задачу на отдельные части:
Преобразование даты строк в объекты момента
Поскольку строки в ваших входных данных представляют даты, давайте преобразуем их, прежде чем делать что-либо еще. Я определяю вспомогательную функцию для создания новых объектов, которые анализируют строки по моментам:
const DateEntry = ({ date, someData }) => ({
date: moment(date, "DD.MM.YYYY"),
data: someData
});
const dateEntries = dates.map(DateEntry); // Now, we can use momentjs for our date logic
Группировка записей даты, которые принадлежат друг другу
Теперь, когда у нас есть список дат, мы можем сгруппировать их по месяцам. Я реализовал быстрый groupBy
помощник, который вы можете скопировать, или вы можете использовать один из библиотек, таких как подчеркивание или ramda.
Чтобы сгруппировать по месяцам, мы передаем функцию, которая возвращает уникальную строку в месяц:
const entriesByMonth = groupBy(
({ date }) => date.format("MM.YYYY"),
entries
);
Объединение массивов записей в один элемент
Теперь мы застряли с группами записей, которые находятся в ту же дату. Вы хотите получить плоский список объектов и определить их диапазон в метке date
.
Опять же, мы можем определить вспомогательную функцию для преобразования этих групп в объекты, с которыми легче работать:
const EntryRange = ( dateEntries ) => {
const dates = dateEntries.map(d => d.date);
const data = dateEntries.map(d => d.data);
const from = moment.min(dates);
const to = moment.max(dates);
return { dates, data, from, to };
};
const ranges = Object.values(entriesByMonth).map(EntryRange);
Теперь у нас есть плоский список объектов; один объект в месяц записей. Объекты уже знают дату начала и окончания!
Преобразование обратно в желаемый результат
Осталось только написать функцию, которая преобразует объект EntryRange
обратно в желаемый формат:
ranges.map(({ from, to, data }) => ({
date: `${from.format("DD.MM.YYYY")} - ${to.format("DD.MM.YYYY")}`,
data: data[0]
}))
Обратите внимание, что для создания строки можно использовать метод формата momentjs!
Собираем все вместе
Вот код в исполняемом фрагменте.
// 1: Raw input data
const dates = [
{ date: "26.06.2019", someData: "foo" },
{ date: "27.06.2019", someData: "foo" },
{ date: "28.06.2019", someData: "foo" },
{ date: "29.06.2019", someData: "foo" },
{ date: "30.06.2019", someData: "foo" },
{ date: "01.07.2019", someData: "foo" },
{ date: "02.07.2019", someData: "foo" },
{ date: "03.07.2019", someData: "foo" },
{ date: "04.07.2019", someData: "foo" },
{ date: "05.07.2019", someData: "foo" },
{ date: "06.07.2019", someData: "foo" },
{ date: "08.08.2019", someData: "foo" },
{ date: "01.01.2020", someData: "foo" },
];
// 2: Define models
const DateEntry = ({ date, someData }) => ({
date: moment(date, "DD.MM.YYYY"),
data: someData
});
const EntryRange = ( dateEntries ) => {
const dates = dateEntries.map(d => d.date);
const data = dateEntries.map(d => d.data);
const from = moment.min(dates);
const to = moment.max(dates);
return {
dates,
data,
from,
to
}
};
EntryRange.sorter = (r1, r2) => r1.from.isBefore(r2.from) ? -1 : 1;
// 3. Convert data to easy-to-work-with formats
const entries = dates.map(DateEntry);
const entriesByMonth = groupBy(
({ date }) => date.format("MM.YYYY"),
entries
);
// Sorted list of EntryRanges
const entryGroups = Object
.values(entriesByMonth)
.map(EntryRange)
.sort(EntryRange.sorter);
// 4. Convert back to desired output
console.log(
entryGroups
.map(({ from, to, data }) => ({
date: `${from.format("DD.MM.YYYY")} - ${to.format("DD.MM.YYYY")}`,
data: data[0]
}))
)
// Utils
function groupBy(getKey, items) {
return items.reduce(
(groups, item) => {
const k = getKey(item);
if (!groups[k]) groups[k] = [ item ];
else groups[k].push(item);
return groups;
}, {});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
Преимущества разделения этих видов преобразований данных
Теперь, когда у нас есть фрагмент кода, который сокращает каждый шаг, мы можем многое сделать, написав небольшие изменения. Я собираюсь внести два изменения, просто чтобы показать, что мы можем:)
- Изменить логику группы на группу только по году
- Поддержка объединения различных записей данных с помощью
,
Попробуйте сами, чтобы увидеть, как вы можете получить разные результаты!
// 1: Raw input data
const dates = [
{ date: "26.06.2019", someData: "foo" },
{ date: "27.06.2019", someData: "foo" },
{ date: "28.06.2019", someData: "foo" },
{ date: "29.06.2019", someData: "foo" },
{ date: "30.06.2019", someData: "bar" },
{ date: "01.07.2019", someData: "foo" },
{ date: "02.07.2019", someData: "foo" },
{ date: "03.07.2019", someData: "foo" },
{ date: "04.07.2019", someData: "foo" },
{ date: "05.07.2019", someData: "foo" },
{ date: "06.07.2019", someData: "foo" },
{ date: "08.08.2019", someData: "foo" },
{ date: "01.01.2020", someData: "foo" },
];
// 2: Define models
const DateEntry = ({ date, someData }) => ({
date: moment(date, "DD.MM.YYYY"),
data: someData
});
const EntryRange = ( dateEntries ) => {
const dates = dateEntries.map(d => d.date);
const data = dateEntries.map(d => d.data);
const from = moment.min(dates);
const to = moment.max(dates);
return {
dates,
data,
from,
to
}
};
EntryRange.sorter = (r1, r2) => r1.from.isBefore(r2.from) ? -1 : 1;
// 3. Convert data to easy-to-work-with formats
const entries = dates.map(DateEntry);
const entriesByYear = groupBy(
({ date }) => date.format("YYYY"),
entries
);
// Sorted list of EntryRanges
const entryGroups = Object
.values(entriesByYear)
.map(EntryRange)
.sort(EntryRange.sorter);
// 4. Convert back to desired output
console.log(
entryGroups
.map(({ from, to, data }) => ({
date: `${from.format("DD.MM.YYYY")} - ${to.format("DD.MM.YYYY")}`,
data: [...new Set(data)].join(", ")
}))
)
// Utils
function groupBy(getKey, items) {
return items.reduce(
(groups, item) => {
const k = getKey(item);
if (!groups[k]) groups[k] = [ item ];
else groups[k].push(item);
return groups;
}, {});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>