Advanced JS

Group Data by Quarter

Group monthly financial data by calendar quarter in JavaScript for dashboards, reports, charting, and tax datasets.

Group List by Quarters

The input starts with monthly report rows and cumulative totals. The transformation groups them by calendar quarter so the result is easier to render in quarterly tables, charts, and report cards.

Useful for:

  • Visualizing quarterly reports
  • Aggregating financial or performance data
  • Preparing charting datasets or grouped UI views

Steps

  1. groupDataByQuarter(data) organizes report rows into keys like "2023-Q1" based on the date field.
  2. initializeDates(groupedQuarters) prepares a compact list of quarters and month strings.
  3. initializeRestData(groupedQuarters) turns grouped data into metric rows such as profit and profit_percent.

Keeping the steps separate makes the transformation easier to test and easier to adjust for a different report layout.

const getQuarter = (date) => Math.floor(date.getMonth() / 3) + 1;
const parseISO = (value) => new Date(`${value}T00:00:00`);

// data
export const TaxEntitiesKeys = {
  profit: "profit",
  profit_percent: "profit_percent",
};

const mockTaxes = {
  cumulative: [
    { date: "2023-01-31", profit: 1000, profit_percent: 100 },
    { date: "2023-02-28", profit: 2000, profit_percent: 200 },
    { date: "2023-04-30", profit: 3000, profit_percent: 250 },
  ],
  report: [
    { date: "2023-01-31", profit: 300, profit_percent: 30 },
    { date: "2023-02-28", profit: 500, profit_percent: 50 },
    { date: "2023-04-30", profit: 700, profit_percent: 70 },
  ],
};
// data

// group by quarters
const groupDataByQuarter = (data) => {
  const quarters = {};

  // Group data into quarters based on their date
  for (const monthData of data.report) {
    const date = parseISO(monthData.date);
    const year = date.getFullYear();
    const quarter = getQuarter(date);
    const key = `${year}-Q${quarter}`;

    if (!quarters[key]) {
      quarters[key] = { months: [], total: null };
    }
    quarters[key].months.push(monthData);
  }

  // Assign cumulative totals to their respective quarters
  for (const totalData of data.cumulative) {
    const date = parseISO(totalData.date);
    const year = date.getFullYear();
    const quarter = getQuarter(date);
    const key = `${year}-Q${quarter}`;

    if (quarters[key]) {
      quarters[key].total = totalData;
    }
  }

  return quarters;
};

const groupedQuarters = groupDataByQuarter(mockTaxes);
console.log(JSON.stringify({ groupedQuarters }, null, 2));
// group by quarters

// {
//   groupedQuarters: {
//     "2023-Q1": {
//       months: [
//         {
//           date: "2023-01-31",
//           profit: 300,
//           profit_percent: 30,
//         },
//         {
//           date: "2023-02-28",
//           profit: 500,
//           profit_percent: 50,
//         },
//       ],
//       total: {
//         date: "2023-02-28",
//         profit: 2000,
//         profit_percent: 200,
//       },
//     },
//     "2023-Q2": {
//       months: [
//         {
//           date: "2023-04-30",
//           profit: 700,
//           profit_percent: 70,
//         },
//       ],
//       total: {
//         date: "2023-04-30",
//         profit: 3000,
//         profit_percent: 250,
//       },
//     },
//   },
// };

// dates
const initializeDates = (quarters) => {
  const dates = [];

  Object.entries(quarters).forEach(([dateKey, data]) => {
    const [year, quarter] = dateKey.split("-Q").map(Number);

    let yearEntry = dates.find((entry) => entry.year === year);
    if (!yearEntry) {
      yearEntry = { year, periods: [] };
      dates.push(yearEntry);
    }

    yearEntry.periods.push({
      months: data.months.map((month) => month.date),
      quarter: `${quarter}`,
    });
  });

  return dates;
};

console.log(
  JSON.stringify({ dates: initializeDates(groupedQuarters) }, null, 2),
);
// dates

// {
//   dates: [
//     {
//       year: 2023,
//       periods: [
//         {
//           months: ["2023-01-31", "2023-02-28"],
//           quarter: "1",
//         },
//         {
//           months: ["2023-04-30"],
//           quarter: "2",
//         },
//       ],
//     },
//   ],
// };

// rest data
const initializeRestData = (quarters) => {
  return Object.keys(TaxEntitiesKeys).map((taxKey) => {
    const taxKeyTyped = taxKey;

    return {
      title: taxKeyTyped,
      periods: Object.entries(quarters).map(([_, data]) => ({
        months: data.months.map((month) => month[taxKeyTyped]),
        total: data.total ? data.total[taxKeyTyped] : null,
      })),
    };
  });
};

console.log(
  JSON.stringify({ restData: initializeRestData(groupedQuarters) }, null, 2),
);
// rest data

// {
//   restData: [
//     {
//       title: "profit",
//       periods: [
//         {
//           months: [300, 500],
//           total: 2000,
//         },
//         {
//           months: [700],
//           total: 3000,
//         },
//       ],
//     },
//     {
//       title: "profit_percent",
//       periods: [
//         {
//           months: [30, 50],
//           total: 200,
//         },
//         {
//           months: [70],
//           total: 250,
//         },
//       ],
//     },
//   ],
// };

On this page