Search code examples
javascriptarraystypescriptjavascript-objects

Filter and map data according to the interval of the weeks of the month


I have an array of objects, each object has four properties (id, start, end and amount).

Then from this data I create a new array of objects with the following properties: date, duration and amount. The date is the date of the timestamp of the start property, duration is the time in minutes between start timestamp and end timestamp.

Then I created two variables, one is from the selected month and the other is from the selected year. Then filter the array dates according to the selected month and year. For example, if month 11 is selected, I would only want objects whose dates are from month 11. And the same applies to the year. The final result turns out to be a list of days according to the selected month and year.

const arrayOfDates = [
    {
        id: 1,
        start: '2021-12-01T12:10:56+0000',
        end: '2021-12-01T12:00:56+0000',
        amount: 10
    },
    {
        id: 2,
        start: '2021-12-03T11:10:56+0000',
        end: '2021-12-03T12:45:56+0000',
        amount: 4
    },
    {
        id: 3,
        start: '2021-12-07T09:10:56+0000',
        end: '2021-12-07T09:25:56+0000',
        amount: 8
    },
    {
        id: 4,
        start: '2021-11-24T11:10:56+0000',
        end: '2021-11-24T11:25:56+0000',
        amount: 8
    }
]

const selectedMonth = 12
const selectedYear = 2021

const mapped = arrayOfDates.map((el) => ({
    date: new Date(el.start).toLocaleDateString('en-CA'),
    duration: new Date(new Date(el.end).getTime() - new Date(el.start).getTime()).getMinutes(),
    amount: el.amount
}))

const filteredBySelectedDate = mapped.filter((el) => {
    var [year, month] = el.date.split('-')
    return selectedMonth === +month && selectedYear === +year;
})

const mappedByDays = filteredBySelectedDate

However, in addition to having the elements of the array listed by day (which is the result I currently have). I would like to use the filteredBySelectedDate array to filter by weeks. In other words, I wanted to group the elements of the array according to the range of weeks of the month. An array like this:

const mappedByWeeks = [
    {
        interval: '12/01 - 12/04',
        duration: 85,
        amount: 14
    },
    {
        interval: '12/05 - 12/11',
        duration: 15,
        amount: 8
    }
]

Solution

  • This can be accomplished in a few steps. First by building up the known start/end days for each week of the month (weekTransitionDays). Secondly a way of identifying which week an entry belongs to (getWeekIndex). And lastly a means of collapsing entries together for the same week (groupByWeek).

    Edit

    Added a helper function for creating the interval text.

    const arrayOfDates = [{
        id: 1,
        start: '2021-12-01T12:10:56+0000',
        end: '2021-12-01T12:00:56+0000',
        amount: 10
      },
      {
        id: 2,
        start: '2021-12-03T11:10:56+0000',
        end: '2021-12-03T12:45:56+0000',
        amount: 4
      },
      {
        id: 3,
        start: '2021-12-07T09:10:56+0000',
        end: '2021-12-07T09:25:56+0000',
        amount: 8
      },
      {
        id: 4,
        start: '2021-11-24T11:10:56+0000',
        end: '2021-11-24T11:25:56+0000',
        amount: 8
      }
    ];
    
    const selectedMonth = 12;
    const selectedYear = 2021;
    
    const weekTransitionDays = [];
    const firstDayOfMonth = (new Date(selectedYear, selectedMonth - 1, 1)).getDay();
    const daysInMonth = (new Date(selectedYear, selectedMonth, 0)).getDate();
    
    const getInterval = (start, end) => {
      if (start < 10) {
        start = "0" + start;
      }
      if (end < 10) {
        end = "0" + end;
      }
      start = selectedMonth + "/" + start;
      end = selectedMonth + "/" + end;
      return start + ' - ' + end;
    };
    
    let start = 1;
    if (firstDayOfMonth !== 0) {
      let end = start + 6 - firstDayOfMonth;
      const interval = getInterval(start, end);
      weekTransitionDays.push({
        start,
        end,
        interval
      });
      start = end;
    }
    
    while (start < daysInMonth) {
      let end;
      if (start + 1 < daysInMonth) {
        start++;
      } else {
        break;
      }
      if (start + 6 < daysInMonth) {
        end = start + 6;
      } else {
        break;
      }
      const interval = getInterval(start, end);
      weekTransitionDays.push({
        start,
        end,
        interval
      });
      start = end;
    }
    
    if (weekTransitionDays.at(-1).end !== daysInMonth) {
      const end = daysInMonth;
      const interval = getInterval(start, end);
      weekTransitionDays.push({
        start,
        end,
        interval
      });
    }
    
    const getWeekIndex = entry => {
      const entryStart = new Date(entry.start).getDate();
      for (let i = 0; i < weekTransitionDays.length; i++) {
        if (weekTransitionDays[i].start <= entryStart && entryStart <= weekTransitionDays[i].end) {
          return i;
        }
      }
      // Throw error if start date doesn't line up
      throw new Error("entry.start mismatch with transition days: \n" + JSON.stringify(entry) + "\n" + JSON.stringify(weekTransitionDays));
    };
    
    const mapped = arrayOfDates.map((el) => ({
      date: new Date(el.start).toLocaleDateString('en-CA'),
      duration: new Date(new Date(el.end).getTime() - new Date(el.start).getTime()).getMinutes(),
      weekIndex: getWeekIndex(el),
      amount: el.amount
    }));
    
    const filteredBySelectedDate = mapped.filter((el) => {
      var [year, month] = el.date.split('-');
      return selectedMonth === +month && selectedYear === +year;
    });
    
    const groupByWeek = () => {
      const obj = {};
      filteredBySelectedDate.forEach(entry => {
        if (!obj.hasOwnProperty(entry.weekIndex)) {
          obj[entry.weekIndex] = {
            interval: weekTransitionDays[entry.weekIndex].interval,
            duration: 0,
            amount: 0
          };
        }
        obj[entry.weekIndex].duration += entry.duration;
        obj[entry.weekIndex].amount += entry.amount;
      });
      return Object.values(obj);
    };
    
    const mappedByDays = filteredBySelectedDate;
    const groupedByWeek = groupByWeek(mapped);
    
    console.log(groupedByWeek);