Как сжать массив дат до их месяцев? - PullRequest
0 голосов
/ 26 июня 2019

Мне нужно сжать массив дат до их месяцев.

Используя метод slice и библиотеку moment.js, я хочу сжать массив до их месяцев.

Здесь яполучить последнюю дату месяца для текущей даты массива.

const dateString = moment (lastIndexTemp, "DD.MM.YYYY")
        .endOf ("month")
        .format ("DD.MM.YYYY");

При наличии такого массива с датами и данными.Мне нужно сжать его до нескольких месяцев.

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" }
];

Ожидаемый результат:

const dates   = [
  { date: "26.06.2019 - 30.06.2019", someData: "foo" },
  { date: "01.07.2019 - 31.07.2019", someData: "foo" },
  { date: "01.08.2019 - 08.08.2019",someData: "foo" }
];

Спасибо за помощь.

Ответы [ 2 ]

1 голос
/ 26 июня 2019

Давайте разберем эту задачу на отдельные части:

Преобразование даты строк в объекты момента

Поскольку строки в ваших входных данных представляют даты, давайте преобразуем их, прежде чем делать что-либо еще. Я определяю вспомогательную функцию для создания новых объектов, которые анализируют строки по моментам:

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. Изменить логику группы на группу только по году
  2. Поддержка объединения различных записей данных с помощью ,

Попробуйте сами, чтобы увидеть, как вы можете получить разные результаты!

// 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>
0 голосов
/ 27 июня 2019

Вы можете создать новый массив новых объектов, пройдя текущий массив и скопировав первый объект в массив и обновив свойство date до текущего диапазона. Затем для каждого последующего объекта с датой, которая является следующим днем ​​в том же месяце, просто обновите дату и значение последнего объекта в массиве результатов.

В противном случае добавьте новый объект в массив с обновленным значением даты. Это создаст несколько диапазонов в текущем месяце, если в последовательности дат имеются разрывы. Непонятно, хочешь ты этого делать или нет.

например. ниже используется пара простых помощников для разбора строки и добавления дня. Вы можете заменить их вызовами из библиотеки, если хотите.

Непонятно, какие «someData» вы хотите сохранить, следующее сохраняет первое, но вы можете легко сохранить последнее.

let 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" }
];

// Parse DD.MM.YYYY to Date. Seperator can be
// any non–digit character
function parseDMY(s) {
  let b = s.split(/\D/);
  return new Date(b[2], b[1]-1, b[0]);
}
// Return a new date that is the passed date + 1 day
function addDay(date) {
  let d = new Date(+date);
  d.setDate(d.getDate() + 1);
  return d;
}

let result = dates.reduce((acc, obj) => {
  // Copy the passed in object
  let temp = Object.assign({}, obj);
  // Get the last entry in accumulator, use a default object if first iteration
  let last = acc.length? acc[acc.length - 1] :  {date:' - '};
  // Get the current date as string
  let currentS = temp.date;
  // Get the current date as Date
  let currentD = parseDMY(currentS);
  // Get the previous end date as Date,
  let lastEndD = parseDMY(last.date.split(' ')[2]);
  // Get the next day as Date
  let lastEndNextD = addDay(lastEndD);

  // If current date is the day after last end date
  // and in the same month, update date range
  if (+currentD == +lastEndNextD &&
      currentD.getMonth() == lastEndD.getMonth()) {
    last.date = last.date.replace(/\S+$/, currentS)

  // Otherwise, start a new entry with an updated range
  } else {
    temp.date = temp.date + ' - ' + temp.date;
    acc.push(temp);
  }
  
  return acc;
}, []);

console.log(result);
...