Search code examples
javascripttypescriptdatedayjs

Dayjs: group data by each day while still keeping the format


I have the following data structure from the API, it comes in as an array of Data, each Data element is intervaled by 1 hour.

interface Data {
  time_bucket: string // ISO8601 string
  aggregated_value: number // 0 and up
}

My plan is to format this for it to work with d3 to plot as a barchart. The barchart has a selector for you to group data into week, day, month, and year. I decided to create a function called groupBy that works quite like lodash's groupBy. It groups data to specified groups you want. This is the function

export function groupBy<T, K extends keyof any> (list: T[], criteria: (item: T) => K): Record<K, T[]> {
  return list.reduce<Record<K, T[]>>((prev, curr) => {
    const group = criteria(curr)
    // eslint-disable-next-line
    if (!prev[group]) {
      prev[group] = []
    }
    prev[group].push(curr)
    return prev
    // eslint-disable-next-line
  }, {} as Record<K, T[]>)
}

The problem is that the x scale of the graph is constructed in YYYY-MM-DD format. I wanted to group the data into each day while keeping the date format to YYYY-MM-DD. What I get right now from running the function looks like this in the snippets.

const data = [
  {
    time_bucket: '2021-06-01T16:00:00.000Z',
    aggregated_value: 20
  },
  {
    time_bucket: '2021-06-01T18:00:00.000Z',
    aggregated_value: 20
  },
  {
    time_bucket: '2021-06-02T16:00:00.000Z',
    aggregated_value: 40
  },
  {
    time_bucket: '2021-06-02T20:00:00.000Z',
    aggregated_value: 40
  },
  {
    time_bucket: '2021-06-03T05:00:00.000Z',
    aggregated_value: 60
  }
]

function groupBy(list, criteria) {
  return list.reduce((prev, curr) => {
    const group = criteria(curr)
    if (!prev[group]) {
      prev[group] = []
    }
    prev[group].push(curr)
    return prev
  }, {})
}

console.log(groupBy(data, (item) => dayjs.utc(item.time_bucket).get('date')))
<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
<script src="https://unpkg.com/dayjs@1.8.21/plugin/utc.js"></script>
<script>dayjs.extend(window.dayjs_plugin_utc)</script>

You can see that the output looks like this:

{
  "1": [
    {
      "time_bucket": "2021-06-01T16:00:00.000Z",
      "aggregated_value": 20
    },
    {
      "time_bucket": "2021-06-01T18:00:00.000Z",
      "aggregated_value": 20
    }
  ],
  "2": [
    {
      "time_bucket": "2021-06-02T16:00:00.000Z",
      "aggregated_value": 40
    },
    {
      "time_bucket": "2021-06-02T20:00:00.000Z",
      "aggregated_value": 40
    }
  ],
  "3": [
    {
      "time_bucket": "2021-06-03T05:00:00.000Z",
      "aggregated_value": 60
    }
  ]
}

This is what I wanted

{
  "2021-06-01": [
    {
      "time_bucket": "2021-06-01T16:00:00.000Z",
      "aggregated_value": 20
    },
    {
      "time_bucket": "2021-06-01T18:00:00.000Z",
      "aggregated_value": 20
    }
  ],
  "2021-06-02": [
    {
      "time_bucket": "2021-06-02T16:00:00.000Z",
      "aggregated_value": 40
    },
    {
      "time_bucket": "2021-06-02T20:00:00.000Z",
      "aggregated_value": 40
    }
  ],
  "2021-06-03": [
    {
      "time_bucket": "2021-06-03T05:00:00.000Z",
      "aggregated_value": 60
    }
  ]
}

What I wanted from the function is to be able to group the data into specified range, while still keeping the format of the date in YYYY-MM-DD format for me to still map it to the d3 x scale that I have generated. Is there any function in dayjs that could do this or is there any workaround. Thank you very much for the response.


Solution

  • After quite a while of research. I decided to round any date to the start of each time range.

    I would be using it like this in the example.

    const data = [
      {
        time_bucket: '2021-06-01T16:00:00.000Z',
        aggregated_value: 20
      },
      {
        time_bucket: '2021-06-01T18:00:00.000Z',
        aggregated_value: 20
      },
      {
        time_bucket: '2021-06-02T16:00:00.000Z',
        aggregated_value: 40
      },
      {
        time_bucket: '2021-06-02T20:00:00.000Z',
        aggregated_value: 40
      },
      {
        time_bucket: '2021-06-03T05:00:00.000Z',
        aggregated_value: 60
      }
    ]
    
    function groupBy(list, criteria) {
      return list.reduce((prev, curr) => {
        const group = criteria(curr)
        if (!prev[group]) {
          prev[group] = []
        }
        prev[group].push(curr)
        return prev
      }, {})
    }
    
    console.log(groupBy(data, (item) => dayjs.utc(item.time_bucket).startOf('day').format('YYYY-MM-DD')))
    <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
    <script src="https://unpkg.com/dayjs@1.8.21/plugin/utc.js"></script>
    <script>dayjs.extend(window.dayjs_plugin_utc)</script>

    This way I can use the .format() that got exposed from startOf() to group the data into each time range.