Bernard
Bernard

Reputation: 11

How to groupby month using array of data

My array is like this:

const myArray = [{
    date: "2017-01-01",
    Name: "test1"
  },
  {
    date: "2017-01-02",
    Name: "test2"
  },
  {
    date: "2017-02-04",
    Name: "test3"
  },
  {
    date: "2017-02-05",
    Name: "test3"
  }
]

I want to convert this into:

const myArray = [{
    group: "Jan",
    data: [{
        date: "2017-01-01",
        Name: "test1"
      },
      {
        date: "2017-01-02",
        Name: "test2"
      }
    ]
  },
  {
    group: "Feb",
    data: [{
        date: "2017-02-04",
        Name: "test3"
      },
      {
        date: "2017-02-05",
        Name: "test3"
      }
    ]
  }
]

Upvotes: 1

Views: 114

Answers (3)

PeterKA
PeterKA

Reputation: 24648

You can use Object.entries(), Array#reduce, and Array#map methods as follows*:

const 
    myArray = [ { date: "2017-01-01", Name: "test1" }, { date: "2017-01-02", Name: "test2" }, { date: "2017-02-04", Name: "test3" }, { date: "2017-02-05", Name: "test3" } ],
    month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],

    outArr = Object.entries(
      myArray.reduce(
        (acc,{date,Name,group = month[+date.split('-')[1] - 1]}) =>
        ({ ...acc, [group]:[...(acc[group] || []),{date,Name}] }), {}
      )
    )
    .map(([group,data]) => ({group,data}));

console.log( outArr );

  • * Credit to @Layhout on how to compute short month name for the given dates. Each day I learn something new!

NOTE

I wonder why new Date(2017-01-01).toLocaleString('default',{month:'short'}) thinks the date is Sat Dec 31, 2016, hence giving Dec instead of Jan. There may be a bug in the Date object; if the date is in the format mm-dd-yyyy it works fine but with yyyy-mm-dd the object always subtracts one day.

Alternatively ....

Working with mm-dd-yyyy format would work as expected at least here where this is the default date format:

const 
    myArray = [ { date: "2017-01-01", Name: "test1" }, { date: "2017-01-02", Name: "test2" }, { date: "2017-02-04", Name: "test3" }, { date: "2017-02-05", Name: "test3" } ],

    outArr = Object.entries(
      myArray.reduce(
        (acc,{date,Name,d = date.split('-'),group = new Date([d[1],d[2],d[0]].join('-')).toLocaleString('default',{month:'short'})}) =>
        ({ ...acc, [group]:[...(acc[group] || []),{date,Name}] }), {}
      )
    )
    .map(([group,data]) => ({group,data}));

console.log( outArr );

Upvotes: 1

Layhout
Layhout

Reputation: 1590

assuming the date inside myArray is always yyyy-MM-dd then my solution should work just fine.

const myArray = [{
    date: "2017-01-01",
    Name: "test1"
  },
  {
    date: "2017-01-02",
    Name: "test2"
  },
  {
    date: "2017-02-04",
    Name: "test3"
  },
  {
    date: "2017-02-05",
    Name: "test3"
  }
]

const result = myArray.reduce((p,c)=>{
    const monthOfData = new Date(c.date).toLocaleString('default', { month: 'short' });
    const found = p.findIndex(p => p.group === monthOfData);
    if(found !==-1){
        p[found].data.push(c);
    } else {
        p.push({
            group: monthOfData,
            data:[c]
        })
    }

    return p;
}, []);

console.log(result);

Or, @danh's suggestion, using an object to build the index and taking a second pass to re-form into the desired output...

const myArray = [
  {date: "2017-01-01", Name: "test1"}, 
  {date: "2017-01-02", Name: "test2"},
  {date: "2017-02-04", Name: "test3"},
  {date: "2017-02-05", Name: "test3"} ]

// reduce into an object. keys are unique and O(1) lookup
const byDate = myArray.reduce((p, c) => {
  const monthOfData = new Date(c.date).toLocaleString('default', { month: 'short' });
  p[monthOfData] ??= []
  p[monthOfData].push(c)
  return p;
}, {});

// re-form to match the output
const result = Object.keys(byDate).map(d => {
  return { group: d, data: byDate[d] } 
})

console.log(result)

for the additional question, for grouping by year and month.

const myArray = [
    { date: "2021-11-01", Name: "example1" },
    { date: "2021-12-01", Name: "example2" },
    { date: "2022-02-01", Name: "example3" },
    { date: "2022-02-02", Name: "example4" },
    { date: "2023-05-22", Name: "example5" }
]

const result = myArray.reduce((p, c) => {
    const [monthOfData, yearOfData] = new Date(c.date).toLocaleString("default", { month: "short", year: "numeric" }).split(" ");
    p[yearOfData] ??= {};
    p[yearOfData][monthOfData] ??= [];
    p[yearOfData][monthOfData].push(c);

    return p
}, {});

console.log(result);

Upvotes: 1

Wijayanga Wijekoon
Wijayanga Wijekoon

Reputation: 99

Hope you will get help from the below sample code.

Here TestA should be a class whose content date and name attributes.

HashMap<String, List<TestA>> map = new HashMap<>();
        for (TestA testA: myArray){
            String month = monthName(testA.getDate());
            if(!map.containsKey(month)){
                map.put(month, new ArrayList<>());
            }
            map.get(month).add(testA);
        }

public String monthName(String date){
        try {
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
            calendar.setTime(sdf.parse(date));
            return new DateFormatSymbols().getMonths()[calendar.get(Calendar.MONTH)];

        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

Thanks

Upvotes: 0

Related Questions