Léo Coco
Léo Coco

Reputation: 4282

Javascript - Array of objects group by year, month and date

I'm trying to sort an array of object by date month and week

myArray : [
            {'name' : 'example1', 'date' : '2011-01-01'},
            {'name' : 'example1', 'date' : '2011-01-02'},
            {'name' : 'example1', 'date' : '2011-02-02'},
            {'name' : 'example1', 'date' : '2011-02-15'},
            {'name' : 'example1', 'date' : '2011-02-17'},
            {'name' : 'example1', 'date' : '2012-01-01'},
            {'name' : 'example1', 'date' : '2012-03-03'},
]

I would like to have such a result :

result : [{
                    '2011': { // Year 2011
                        '01': { // January
                            '01' : [ // First week of January
                                {'name' : 'example1', 'date' : '2011-01-01'},
                                {'name' : 'example1', 'date' : '2011-01-02'},
                            ]
                        },
                        '02' : { // February
                            '01' : [ // First week of February
                                {'name' : 'example1', 'date' : '2011-02-02'},
                            ],
                            '03' : [
                                {'name' : 'example1', 'date' : '2011-02-15'},
                                {'name' : 'example1', 'date' : '2011-02-17'},
                            ]
                        }
                    },
                    '2012' : { // Year 2012
                        '01' : { // January
                            '01': [ // First week of January
                                {'name': 'example1', 'date': '2012-01-01'}
                            ]
                        },
                        '03': { // March
                            '01' : [ // First week of March
                                {'name' : 'example1', 'date' : '2012-03-03'},
                            ]
                        }
                    }
                }]

I use lodash.groupBy with return getISOWeek(obj.date) or return getMonth(obj.date) or return getYear(obj.date).

I managed to sort the data by year and month, year and week but I can't figure out how to do it for the three at the same time.

Thanks for your help

Upvotes: 1

Views: 9067

Answers (4)

Saint Play
Saint Play

Reputation: 1083

Using lodash is the way to go.

var myArray = [
  {'name' : 'example1', 'date' : '2011-01-01'},
  {'name' : 'example1', 'date' : '2011-01-02'},
  {'name' : 'example1', 'date' : '2011-02-02'},
  {'name' : 'example1', 'date' : '2011-02-15'},
  {'name' : 'example1', 'date' : '2011-02-17'},
  {'name' : 'example1', 'date' : '2012-01-01'},
  {'name' : 'example1', 'date' : '2012-03-03'},
];

var orderedByMonths = _.groupBy(myArray, function(element) {
                          return element.date.substring(0,7);
                      });
            
var orderedByYears =  _.groupBy(orderedByMonths, function(month) {
                          return month[0].date.substring(0,4);
                      });

console.log(orderedByYears);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Upvotes: 3

guest271314
guest271314

Reputation: 1

You can use for..of loop, String.prototype.match() with RegExp /\d+/ to get YYYY, and MM portions of "date" property value; Array.prototype.filter() to match YYYY-MM of object at input array to current object at for..of loop by comparing YYYY with "-" and MM concatenated.

const myArray = [
  {'name' : 'example1', 'date' : '2011-01-01'},
  {'name' : 'example1', 'date' : '2011-01-02'},
  {'name' : 'example1', 'date' : '2011-02-02'},
  {'name' : 'example1', 'date' : '2011-02-15'},
  {'name' : 'example1', 'date' : '2011-02-17'},
  {'name' : 'example1', 'date' : '2012-01-01'},
  {'name' : 'example1', 'date' : '2012-03-03'},
];

let res = {};

let fn = (year, month, o = res, array = myArray) => {
  o[year][month] = {
    [month]: array.filter(({date: d}) => `${year}-${month}` === d.slice(0, 7))
  };
}

for (let {date} of myArray) {
  let [year, month] = date.match(/\d+/g);
  if (!res[year]) res[year] = {};
  fn(year, month)
}

console.log(res);

jsfiddle https://jsfiddle.net/sd7e9Lp7/3/

Upvotes: 1

RobG
RobG

Reputation: 147363

It's really not clear from your question what you are after. ISO week numbers are based on years, not months, e.g. 2011-01-01 falls in the last week of 2010, not the first week of 2011. You really can't combine grouping by month and ISO week number, as about 10 weeks per year will start in one month and end in another.

If your concept of "week of the month" that a day falls in is simply Math.ceil(day number / 7), then you can group by that, noting that dates like 29 February, 2016 will be the only day of week 5 in February that year.

var data = [
  {'name' : 'example1', 'date' : '2011-01-01'},
  {'name' : 'example1', 'date' : '2011-01-02'},
  {'name' : 'example1', 'date' : '2011-02-02'},
  {'name' : 'example1', 'date' : '2011-02-15'},
  {'name' : 'example1', 'date' : '2011-02-17'},
  {'name' : 'example1', 'date' : '2012-01-01'},
  {'name' : 'example1', 'date' : '2012-03-03'},
];

function groupByMonthWeek(data) {
  var year, month, week
  return data.reduce(function (acc, obj) {
    var b = obj.date.split(/\D/);
    
    // Get custom week number, zero padded
    var weekNum = '0' + Math.ceil(b[2]/7);

    // Add year if not already present
    if (!acc[b[0]]) acc[b[0]] = {};
    year = acc[b[0]];
    
    // Add month if not already present
    if (!year[b[1]]) year[b[1]] = {};
    month = year[b[1]];
    
    // Add week if not already present
    if (!month[weekNum]) month[weekNum] = [];

    // Add object to  week
    month[weekNum].push(obj);

    return acc;    
  }, Object.create(null));
}

console.log(groupByMonthWeek(data));

Which, if you're into obfuscation, can be compacted to the following (but I wouldn't suggest actually using it):

var data = [
  {'name' : 'example1', 'date' : '2011-01-01'},
  {'name' : 'example1', 'date' : '2011-01-02'},
  {'name' : 'example1', 'date' : '2011-02-02'},
  {'name' : 'example1', 'date' : '2011-02-15'},
  {'name' : 'example1', 'date' : '2011-02-17'},
  {'name' : 'example1', 'date' : '2012-01-01'},
  {'name' : 'example1', 'date' : '2012-03-03'},
];

function groupByMonthWeek(data) {
  return data.reduce((acc, obj) => {
    var [y, m, d] = obj.date.split(/\D/);
    [y, m, '0'+Math.ceil(d/7)].reduce((a,v,i) => a[v] || (a[v] = i < 2 ? {} : []), acc).push(obj);
    return acc;
  }, Object.create(null));
}

console.log(groupByMonthWeek(data));

Upvotes: 1

ibrahim mahrir
ibrahim mahrir

Reputation: 31682

function group(arr) {
  return arr.reduce((r, o) => {
    var p = o.date.split("-");                             // get the parts: year, month and day
    var week = Math.floor(p.pop() / 7) + 1;                // calculate the week number (Math.floor(day / 7) + 1) and remove day from the parts array (p.pop())
    var month = p.reduce((o, p) => o[p] = o[p] || {}, r);  // get the month object (first, get the year object (if not create one), then get the month object (if not create one)
    if(month[week]) month[week].push(o);                   // if there is an array for this week in the month object, then push this object o into that array
    else month[week] = [o];                                // otherwise create a new array for this week that initially contains the object o
    return r;
  }, {});
}


let array = [{"name":"example1","date":"2011-01-01"},{"name":"example1","date":"2011-01-02"},{"name":"example1","date":"2011-02-02"},{"name":"example1","date":"2011-02-15"},{"name":"example1","date":"2011-02-17"},{"name":"example1","date":"2012-01-01"},{"name":"example1","date":"2012-03-03"}];

console.log(group(array));

If you want the week number to be in this format "01", "02", ... and not "1"; "2", ..., then change this line:

var week = Math.floor(p.pop() / 7) + 1;

to this:

var week = "0" + Math.floor(p.pop() / 7) + 1;

Upvotes: 0

Related Questions