Search code examples
typescriptreact-typescript

How to get data of last day of each month from a time-series object array Typescript


I receive timeseries data from an API of random days like so

[
  {
    "id": 1,
    "score": 23,
    "date": "2023-08-30"
  },
  {
    "id": 2,
    "score": 62,
    "date": "2023-08-22"
  },
  {
    "id": 3,
    "score": 82,
    "date": "2023-07-27"
  }
          .
          .
          .
]

How can I elegantly get the data from this series of last day of every month?

Expecting outcome like this

[
  {
    "id": 1,
    "score": 23,
    "date": "2023-08-30"
  },
  {
    "id": 3,
    "score": 82,
    "date": "2023-07-27"
  }
          .
          .
          .
]

Currently trying something like this

const getMonthlydata = (allData: Timeseries[]): Timeseries[] => {

//Loop over every item to get max date of every month and add it to return array

}

Edited for better format


Solution

  • Let's say the example array you have is inside a variable called scores.

    Essentially this works by creating an intermediary object that maps a specific month+year to the "latest" one, and then grabs the values of that object to go back to the array format you desire.

    Note technically speaking, this doesn't guarantee the order comes out the same as it went in. You might not care. The reason is the JS spec says object keys don't have an order. Practically speaking in every modern browser and js runtime (like node), it actually does maintain the order.

    If you really want to be sure you could just sort by the date field after as well. But it's pointless in practice.

    When encountering multiple entries with same dates where that date is the last one in the month overall this takes the last one due to the >= operator. It could be made to be the first one by changing this to >.

    Working playground.

    type Timeseries = {
      id: number
      score: number
      date: string
    }
    
    let scores: Timeseries[] = [
      {
        id: 1,
        score: 23,
        date: "2023-08-30",
      },
      {
        id: 2,
        score: 62,
        date: "2023-08-22",
      },
      {
        id: 3,
        score: 82,
        date: "2023-07-27",
      },
    ]
    
    const lastOfMonthScores = Object.values(
      scores.reduce<Record<string, Timeseries>>(
        (keyedTimeSeries, currentTimeSeries) => {
          const currentDate = new Date(currentTimeSeries.date)
          const currentKey = `${currentDate.getFullYear()}-${currentDate.getMonth()}`
          const existingEntry = keyedTimeSeries[currentKey]
          const existingDate = existingEntry
            ? new Date(existingEntry.date)
            : undefined
          return {
            ...keyedTimeSeries,
            [currentKey]: existingDate
              ? currentDate >= existingDate
                ? currentTimeSeries
                : existingEntry
              : currentTimeSeries,
          }
        },
        {},
      ),
    )
    
    console.log(lastOfMonthScores)
    

    enter image description here