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.