1

Hi all I need some help sorting an array of objects like this:

[
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    }
]

I'd like to sort it by jobDate and by jobEnd in a desc order:

[
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    },
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    }
]

I have tried to use various methods but I can only sort by one value, instead I have to sort by two values at once (jobsDate and jobsEnd). How would you deal with this problem?

  • 1
    Which is more prior than the other? Let's say if 2 objects have the same date? or have the same time? – KienHT Dec 11 '20 at 10:12
  • @KienHT 1st sort by jobsDate and 2nd each jobsDate must be sorted by jobsEnd. – Luca Donnaloia Dec 11 '20 at 10:17
  • You can provide custom function to `sort` method. Inside this function you need to create `Date` object from `jobDate` `jobEnd` time and use it for desc sorting as described here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort. – Oleksandr Sakun Dec 11 '20 at 10:35
  • Is `"12-10-2020"` supposed to be MM-DD-YYYY or DD-MM-YYYY? – VLAZ Dec 11 '20 at 10:39
  • @VLAZ yes it is "MM-DD-YYYY" – Luca Donnaloia Dec 11 '20 at 10:49
  • This question asks the same as this one: [How to sort an array of objects by multiple fields?](https://stackoverflow.com/questions/6913512/how-to-sort-an-array-of-objects-by-multiple-fields) – Heretic Monkey Dec 11 '20 at 15:52

6 Answers6

0

const jobDate = [
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    }
];

function sortDate() {
  jobDate.sort((a,b) => {
   const d = new Date(b.jobDate.split("-").reverse().join("-")) - new Date(a.jobDate.split("-").reverse().join("-"));
     if (d === 0) {
       return a.jobEnd.localeCompare(b.jobEnd);
     }
     return d;
   });
  console.log(jobDate);
}

//sortDate("jobDate");
sortDate();
sourav satyam
  • 980
  • 5
  • 11
  • This works too but I've edited it to fit my question (sorting desc): `function sortDate(arr) { arr.sort((a, b) => { const d = new Date(b.jobDate.split('-').reverse().join('-')) - new Date(a.jobDate.split('-').reverse().join('-')); if (d === 0) { return b.jobEnd.localeCompare(a.jobEnd); } return d; }); } sortDate(jobDate);` – Luca Donnaloia Dec 11 '20 at 11:07
  • ok, that's great. – sourav satyam Dec 11 '20 at 11:10
0

One just needs to write a (sort) function which, always in descending order, does compare at first the jobDates of two items, and in case both dates are equal, continues comparing both item's jobEnd values.

In case of jobDate one has to either validly convert each value into a Date type before passing it to compareDescending or reformat it for direct comparison from MM-DD-YYYY to YYYY-MM-DD (which is the current use case).

Both string values of each jobEnd can be compared directly via compareDescending because the hh:mm:ss format is provided on a 24hour base.

function sanitizeDateFormat(str) {
  const [month, day, year] = str.split('-');
  return [year, month, day].join('-');
}
function compareDescending(a, b) {
  return ((a > b) && -1) || ((a < b) && 1) || 0;
}

function compareJobsByDateAndEndTime(a, b) {
  return compareDescending(

    // new Date(a.jobDate),
    // new Date(b.jobDate)
    sanitizeDateFormat(a.jobDate),
    sanitizeDateFormat(b.jobDate)

  ) || compareDescending(

    a.jobEnd,
    b.jobEnd
  );
}

console.log([{
  "jobID": "2020121013290Yyjny8x",
  "jobDate": "12-10-2020",
  "jobStart": "13:29:58",
  "jobEnd": "13:30:36"
}, {
  "jobID": "202012101329yXHTXvqg",
  "jobDate": "12-10-2020",
  "jobStart": "13:29:26",
  "jobEnd": "13:31:58"
}, {
  "jobID": "202011120927afObyUv8",
  "jobDate": "12-11-2020",
  "jobStart": "09:27:42",
  "jobEnd": "09:27:58"
}, {
  "jobID": "202011120928w28NDLQVu",
  "jobDate": "12-11-2020",
  "jobStart": "09:28:09",
  "jobEnd": "09:28:25"
}].sort(compareJobsByDateAndEndTime));
.as-console-wrapper { min-height: 100%!important; top: 0; }

Edit

Please, can you explain what it does ? ((a > b) && -1) || ((a < b) && 1) || 0 – Luca Donnaloia

A comparator function, which by default does a comparison for an ascending order, is expected to return a value grater than Zero in case its first argument is found to be somehow "grater" than its second value; likewise with a return value lower than Zero which indicates that the first argument is minor in comparison to the second argument. A return value of Zero indicates the equality of both argument's order/sequence. A comparator which helps sorting in descending order then needs to flip the return values for grater than and lower than.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
0

const arr = [
    {
        jobID: '202012101329yXHTXvqg',
        jobDate: '12-10-2020',
        jobStart: '13:29:26',
        jobEnd: '13:31:58',
    },
    {
        jobID: '2020121013290Yyjny8x',
        jobDate: '12-10-2020',
        jobStart: '13:29:58',
        jobEnd: '13:30:36',
    },
    {
        jobID: '202011120928w28NDLQVu',
        jobDate: '12-11-2020',
        jobStart: '09:28:09',
        jobEnd: '09:28:25',
    },
    {
        jobID: '202011120927afObyUv8',
        jobDate: '12-11-2020',
        jobStart: '09:27:42',
        jobEnd: '09:27:58',
    },
];

arr.sort((a, b) => {
    const aDateAsArr = a.jobDate.split('-');
    const aTimeASArr = a.jobEnd.split(':');

    const bDateAsArr = b.jobDate.split('-');
    const bTimeASArr = b.jobEnd.split(':');

    const aDate = new Date(
        +aDateAsArr[2],
        +aDateAsArr[1],
        +aDateAsArr[0],
        +aTimeASArr[0],
        +aTimeASArr[1],
        +aTimeASArr[2],
    );
    const bDate = new Date(
        +bDateAsArr[2],
        +bDateAsArr[1],
        +bDateAsArr[0],
        +bTimeASArr[0],
        +bTimeASArr[1],
        +bTimeASArr[2],
    );

    return bDate.getTime() - aDate.getTime();
});

console.log(arr)
Oleksandr Sakun
  • 452
  • 4
  • 9
0

The biggest problem here is that your date is in MM-DD-YYYY format, which means it needs to be changed in order to be easily comparable to another date. The easiest way it to tranform to YYYY-MM-DD (ISO 8601 format) - in that case, lexicograpgical sorting is the same as chronological sorting, so you can directly compare the two dates as strings. Times can already compared that way.

With some abstraction and generalisation, you can get to an easily compostable solution:

/*   library code   */

const extractField = prop => x =>
  x[prop];

const pipe = (first, ...fns) => (...args) =>
  fns.reduce((result, fn) => fn(result), first(...args));

const compare = (normalise = x => x) => (a, b) =>
  (a = normalise(a)) === (b = normalise(b))
  ? 0 
  : a > b
    ? 1 
    : -1;

const sortingBy = (...criteria) => (a, b) =>
  criteria.reduce((acc, c) => acc || c(a, b), 0);

const flip = f => (a, b) => 
  f(b, a);

/*   /library code   */


/*   sorting code   */

//MM-DD-YYYY to YYYY-MM-DD
const formatDate = str => 
  str.replace(/(\d\d)-(\d\d)-(\d\d\d\d)/, "$3-$1-$2");

//get jobDate -> sort as formatted date
const comparableDate = pipe(extractField("jobDate"), formatDate);

const compareDatesAsc = compare(comparableDate);
const compareDatesDesc = flip(compareDatesAsc);
const compareTimesAsc = compare(extractField("jobEnd"));

const comparator = sortingBy(
  compareDatesDesc,
  compareTimesAsc
);

/*   /sorting code   */

const arr = [
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2019",
        "jobStart": "10:28:09",
        "jobEnd": "10:28:25"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2021",
        "jobStart": "09:58:09",
        "jobEnd": "09:58:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    }
]

arr.sort(comparator);
console.log(arr);

More detailed explanation

This is a generalised comparison criteria:

const compare = (normalise = x => x) => (a, b) =>
  (a = normalise(a)) === (b = normalise(b))
  ? 0 
  : a > b
    ? 1 
    : -1;

You can supply your normalisation function that will transform a and b to something that can be easily compared, so it can work for any data type that obeys the <, and > operators, like numbers, strings, dates, etc.

This is a generalised sorting algorithm:

const sortingBy = (...criteria) => (a, b) =>
  criteria.reduce((acc, c) => acc || c(a, b), 0);

It takes any amount of sorting criteria and will run a and b through each until one returns something different from 0.

These two enable us to generalise any sorting as a collection of criteria.

Some helpers are also defined as library code, as they aren't specific to the sorting operation:

  • extractField is fairly self-explanatory and will extract a field by name from an object.
  • pipe is a small helper method to compose functions together: g(f(x)) = pipe(f, g)(x).
  • flip reverses the arguments for a function. Useful here as it allows to create opposite sorting criteria from existing ones, since compare(x, y) must be symmetrically opposite to compare(y, x).

Library code aside, the actual sorting logic consists of defining what we sort by and in what order:

  • formatDate is the only custom function to transform to an ISO8601 date.
  • comparableDate is derived from existing functions: extracting jobDate and formatting it
  • compareDatesAsc and compareTimesAsc are also derived as sorting criteria.
  • compareDatesDesc is reverse sorting derived for free.
  • comparator is finally the final logic based on the two existing ones in order.
VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • Thank you very much, I appreciate the effort to explain your answer in detail, I have learned something new! I changed the backend code, so now it sends me the dates in ISO8601 format. I think I'll generalize the comparison part to accept "jobDate" rather than "jobEnd" or whatever arguments you want to pass to the extraField function, ascendingSort and descendingSort are a very welcome bonus :) – Luca Donnaloia Dec 11 '20 at 12:10
  • Although this was the most detailed answer I received, unfortunately it doesn't work as expected because the result is not right (unlike other answers with even simpler methods). Thanks anyway! – Luca Donnaloia Dec 11 '20 at 12:34
  • @LucaDonnaloia which input produces the wrong result? I seem to get the results you expected when doing a descending sort. – VLAZ Dec 11 '20 at 12:44
  • look at your output code: there is one element, the last of the output, which is not sorted, "12-11-2019" should be at the beginning of the output with the other days like 12-11-2019 – Luca Donnaloia Dec 14 '20 at 10:22
  • @LucaDonnaloia ah, I see what you mean now. It's ascending by date but descending by time, correct? – VLAZ Dec 14 '20 at 10:26
  • it's desc by date and by time per day, ie: `1. 2020-12-11 18:00 2. 2020-12-11 08:00 3. 2020-12-10 12:30 4. 2020-12-08 08:00 5. 2020-12-08 06:00 6. 2020-07-31 16:00 7. 2019-12-31 08:00` – Luca Donnaloia Dec 14 '20 at 11:11
  • @LucaDonnaloia Thanks. I have apparently misread the question the first time around, I thought you wanted ascending dates and times. I've updated to a descending dates and ascending times. Works out better, as I wanted to showcase how to reverse a sorting criteria with `flip`. Using it to have two sorting criteria one ascending one descending I think is an even better example. – VLAZ Dec 14 '20 at 12:08
-1
var arr = [
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    }
];
//first sort it by jobDate
arr.sort(function(item1,item2){
return (new Date(item2.jobDate) - new Date(item1.jobEnd));
});
//then by jobEnd
arr.sort(function(item1,item2){

return (new Date(item2.jobEnd) - new Date(item1.jobEnd));
});

I think this will help

Hemang vora
  • 71
  • 1
  • 10
  • 1. `"12-10-2020"` is an non-standard date format, so `new Date("12-10-2020")` will be implementation dependant. 2. same for `new Date("13:30:36")` but it's even more likely to produce an invalid date objects. 3. Two sorts just mean that the data will be sorted by the second criteria, not by both. – VLAZ Dec 11 '20 at 10:26
  • @VLAZ Thanks for correcting will keep this sceanarios in mind :-) – Hemang vora Dec 11 '20 at 10:29
  • unfortunately this solution is not good because it sorts the array by date first (and it's ok) but then sorts the whole array again by the time (and ruins the previous sort). – Luca Donnaloia Dec 11 '20 at 10:38
-1

It was confirmed that it works well in terms of functionality, and it was developed intuitively.

  • using sort()
    The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

  • with compareFunction
    If compareFunction is not supplied, all non-undefined array elements are sorted by converting them to strings and comparing strings in UTF-16 code units order. For example, "banana" comes before "cherry". In a numeric sort, 9 comes before 80, but because numbers are converted to strings, "80" comes before "9" in the Unicode order. All undefined elements are sorted to the end of the array.

reference : Array.prototype.sort()

let data=[
    {
        "jobID": "202012101329yXHTXvqg",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:26",
        "jobEnd": "13:31:58"
    },
    {
        "jobID": "2020121013290Yyjny8x",
        "jobDate": "12-10-2020",
        "jobStart": "13:29:58",
        "jobEnd": "13:30:36"
    },
    {
        "jobID": "202011120928w28NDLQVu",
        "jobDate": "12-11-2020",
        "jobStart": "09:28:09",
        "jobEnd": "09:28:25"
    },
    {
        "jobID": "202011120927afObyUv8",
        "jobDate": "12-11-2020",
        "jobStart": "09:27:42",
        "jobEnd": "09:27:58"
    }
]

const compareJobDateThenEndTime = ((a, b) => {
    return (new Date(b.jobDate) - new Date(a.jobDate)) || b.jobEnd.localeCompare(a.jobEnd);
})

let result = data.sort(compareJobDateThenEndTime);

console.log(result);
myeongkil kim
  • 2,465
  • 4
  • 16
  • 22