Sort Array by ISO 8601 date
Asked Answered
D

8

79

How can I sort this array by date (ISO 8601)?

var myArray = new Array();

myArray[0] = { name:'oldest', date:'2007-01-17T08:00:00Z' }
myArray[1] = { name:'newest', date:'2011-01-28T08:00:00Z' }
myArray[2] = { name:'old',    date:'2009-11-25T08:00:00Z' }

Playground:
https://jsfiddle.net/4tUZt/

Deign answered 30/8, 2012 at 8:5 Comment(2)
Seems to me the date would sort alphabetically tooNuts
A lot of people are suggesting Date.parse but it doesn't give consistent results #5802961Corregidor
C
132

Sort Lexicographically:

As @kdbanman points out, ISO8601See General principles was designed for lexicographical sort. As such the ISO8601 string representation can be sorted like any other string, and this will give the expected order.

'2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === true

So you would implement:

var myArray = [
    { name:'oldest', date:'2007-01-17T08:00:00Z' },
    { name:'newest', date:'2011-01-28T08:00:00Z' },
    { name:'old',    date:'2009-11-25T08:00:00Z' }
];

myArray.sort(function(a, b) {
    return (a.date < b.date) ? -1 : ((a.date > b.date) ? 1 : 0);
});

Sort using JavaScript Date:

Older versions of WebKit and Internet Explorer do not support ISO 8601 dates, so you have to make a compatible date. It is supported by FireFox, and modern WebKit though See here for more information about Date.parse support JavaScript: Which browsers support parsing of ISO-8601 Date String with Date.parse

Here is a very good article for creating a Javascript ISO 8601 compatible date, which you can then sort like regular javascript dates.

http://webcloud.se/log/JavaScript-and-ISO-8601/

Date.prototype.setISO8601 = function (string) {
    var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
    "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
    "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
    var d = string.match(new RegExp(regexp));

    var offset = 0;
    var date = new Date(d[1], 0, 1);

    if (d[3]) { date.setMonth(d[3] - 1); }
    if (d[5]) { date.setDate(d[5]); }
    if (d[7]) { date.setHours(d[7]); }
    if (d[8]) { date.setMinutes(d[8]); }
    if (d[10]) { date.setSeconds(d[10]); }
    if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
    if (d[14]) {
        offset = (Number(d[16]) * 60) + Number(d[17]);
        offset *= ((d[15] == '-') ? 1 : -1);
    }

    offset -= date.getTimezoneOffset();
    time = (Number(date) + (offset * 60 * 1000));
    this.setTime(Number(time));
}

Usage:

console.log(myArray.sort(sortByDate));  

function sortByDate( obj1, obj2 ) {
    var date1 = (new Date()).setISO8601(obj1.date);
    var date2 = (new Date()).setISO8601(obj2.date);
    return date2 > date1 ? 1 : -1;
}

Updated usage to include sorting technique credit @nbrooks

Corregidor answered 30/8, 2012 at 8:9 Comment(5)
I found that returning this at the bottom of Date.prototype.setISO8601 = function (string) { ... return this; } was necessary to make this work...Stamina
This may be complete, but it's unnecessary. ISO8601 is designed for lexicographical sort, so just '2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === trueMiranda
@Miranda Thanks, good to know. I've updated the answer to include this.Corregidor
Careful, this answer is not fully correct. As pointed out by @Barnebas below lexicographical sort does not work in case your ISO8601 date strings contain a time zone offset or - less common - a negative year. So in case you are not 100% sure all your dates are given in universal time you should definitely create date objects from your iso strings first and then compare the date objects.Bendy
TIL! This is smartAhumada
L
39

You can avoid creating of dates and by using the built–in lexicographic compare function String.prototype.localeCompare, rather than the ?: compound operator or other expressions:

var myArray = [
  {name: 'oldest', date: '2007-01-17T08:00:00Z'},
  {name: 'newest', date: '2011-01-28T08:00:00Z'},
  {name: 'old', date: '2009-11-25T08:00:00Z'}
];

// Oldest first
console.log(
  myArray.sort((a, b) => a.date.localeCompare(b.date))
);

// Newest first
console.log(
  myArray.sort((a, b) => -a.date.localeCompare(b.date))
);
Lynch answered 1/10, 2018 at 2:26 Comment(0)
B
12

Be careful, the accepted answer now advises to sort our dates lexicographically.

However, this will only work if all your strings use the 'Z' or '+00' timezone (= UTC). Date strings ending with 'Z' do satisfy ISO8601 standard, but all ISO8601 do not end with 'Z'.

Thus, to be fully ISO8601 compliant, you need to parse your strings with some Date library (e.g. Javascript Date or Moment.js), and compare these objects. For this part, you can check Scott's answer that also covers browsers incompatible with ISO8601.

My simple example with Javascript Date (works on any not-too-old browser) :

var myArray = [
    { name:'oldest', date:'2007-01-17T08:00:00Z' },
    { name:'newest', date:'2011-01-28T08:00:00+0100' },
    { name:'old',    date:'2009-11-25T08:00:00-0100' }
];

myArray.sort(function(a, b) {
    return new Date(a.date) - new Date(b.date);
});

Downside : This is slower than just comparing strings lexicographically.

More info about ISO8601 standard : here.

Barnebas answered 25/6, 2019 at 9:26 Comment(3)
Hi, I have to disagree. For example, in a web app, you could compare some date you got from an API, and some date you just picked in the web app. The second one would surely use the computer's local time zone, which could be different from the API's. You could also compare dates you got from 2 different APIs that use different time zones.Barnebas
The sort function needs to return 1, -1, or 0. I don't see how that can work here by subtracting new Date(a.date) - new Date(b.date)Nita
Hello ! Actually, the function given to sort as an argument does not have to return precisely -1 or 1. Returning any negative value does the same as returning -1. Same for positve values and 1. You can check the doc page for JS sort function : developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Also, this all works because subtracting two JS Date objects returns the difference between them in milliseconds : https://mcmap.net/q/117351/-how-to-subtract-date-time-in-javascript-duplicate Have a nice day :)Barnebas
D
6

I'd go with this:

const myArray = new Array();

myArray[0] = { name:'oldest', date:'2007-01-17T08:00:00Z' }
myArray[1] = { name:'newest', date:'2011-01-28T08:00:00Z' }
myArray[2] = { name:'old',    date:'2009-11-25T08:00:00Z' }

function byDate (a, b) {
    if (a.date < b.date) return -1; 
    if (a.date > b.date) return 1; 
    return 0;  
}

const newArray = myArray.sort(byDate);


console.clear();
console.dir(myArray);
console.dir(newArray);
Dilan answered 30/8, 2012 at 8:15 Comment(4)
Actually in this case he's just sorting alphabetically, which really seems like it should work +1Superior
@Corregidor I'm just sorting strings not dates. This works and I've been using this for a while now.Dilan
Great minds think alike. As usual I should have posted an answer instead of a comment...Nuts
@Nuts Yes :-) I too prefer clean and simple solutions over difficult to read and maintain over-complexities.Dilan
P
3

ISO8601 is designed to sort correctly as plain text, so in general, a normal sort will do.

To sort by a specific key of objects in an array, you need to specify a comparison function to the sort() method. In many other languages, these are easy to write using the cmp function, but JS doesn't have a built in cmp function, so I find it easiest to write my own.

var myArray = new Array();

myArray[0] = { name:'oldest', date:'2007-01-17T08:00:00Z' }
myArray[1] = { name:'newest', date:'2011-01-28T08:00:00Z' }
myArray[2] = { name:'old',    date:'2009-11-25T08:00:00Z' }

// cmp helper function - built in to many other languages
var cmp = function (a, b) {
    return (a > b) ? 1 : ( (a > b) ? -1 : 0 );
}

myArray.sort(function (a,b) { return cmp(a.date, b.date) });

P.s. I would write my array using JSON-like syntax, like this:

var myArray = [
    { name:'oldest', date:'2007-01-17T08:00:00Z' },
    { name:'newest', date:'2011-01-28T08:00:00Z' },
    { name:'old',    date:'2009-11-25T08:00:00Z' }
];
Pyelonephritis answered 12/12, 2012 at 18:14 Comment(0)
S
2

http://jsfiddle.net/4tUZt/2/

$(document).ready(function()
{ 
    var myArray = [ { name:'oldest', date:'2007-01-17T08:00:00Z' },
        { name:'newest', date:'2011-01-28T08:00:00Z' },
        { name:'old',    date:'2009-11-25T08:00:00Z' }];

    console.log( myArray.sort(sortByDate) );        
});

// Stable, ascending sort (use < for descending)
function sortByDate( obj1, obj2 ) {
    return new Date(obj2.date) > new Date(obj1.date) ? 1 : -1;
}

Superior answered 30/8, 2012 at 8:10 Comment(6)
older versions of WebKit and Internet Explorer do not support ISO 8601 natively. So this may fail in older browsers.Corregidor
How about just sort on string ?Nuts
@scott Thanks good to know, I'm not positive which versions of IE actually support it, I'll double check.Superior
@Nuts My immediate reaction was "that won't work"...but in thinking about it I can't think of a counter-example. Perhaps it would work. I suppose it's always safer to stick with the date formatting anyway though...to handle the abbreviated versions, or API's that don't follow the standard strictlySuperior
If the string is consistent it surely beats having to run through hoops to make it work for the browsers that do not like the formatNuts
While this is simpler than Scott's answer, it's still unnecessarily complex. ISO8601 is designed for lexicographical sort, so '2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === true. See @Pyelonephritis answer below.Miranda
A
2

Demo: http://jsfiddle.net/4tUZt/4/

var myArray = new Array();

myArray[0] = { name:'oldest', date: '2007-01-17T08:00:00Z' };
myArray[1] = { name:'newest', date: '2011-01-28T08:00:00Z' };
myArray[2] = { name:'old',    date: '2009-11-25T08:00:00Z' };

var sortFunction = function (a, b) {
  return Date.parse(b.date) - Date.parse(a.date);
};

/* or

var sortFunction = function (a, b) {
  return new Date(b.date) - new Date(a.date);
};

*/

console.log(myArray.sort(sortFunction));

Abomasum answered 30/8, 2012 at 8:11 Comment(0)
A
0

In the instance that you're sorting objects that may be missing a date, and dates may be in different timezones, you'll end up needing something a little more complex:

const deletionDateSortASC = (itemA, itemB) => 
  (+new Date(itemA.deletedAt) || 0) -
  (+new Date(itemB.deletedAt) || 0);

const deletionDateSortDESC = (itemA, itemB) => 
  deletionDateSortASC(itemB, itemA);

If you know the dates are all defined and valid, and you know that all the dates are in the same timezone, then you should pick one of the other faster answers. However, if you want date sorting, have one or more of these edge cases, and don't want to have to preprocess the data to clean it up, then I suggest this approach.

I tried to demonstrate in the snippet below how the other answers fail in these edge cases.

const data = [
  {deletedAt: null},
  {deletedAt: '2022-08-24T12:00:00Z'},
  {deletedAt: undefined},
  {deletedAt: '2015-01-01T00:00:00Z'},
  {deletedAt: '2022-08-24T12:00:00-01:00'},
  {deletedAt: '2022-08-24T12:00:00+01:00'},
  {deletedAt: '2022-08-20T12:00:00+01:00'},
  {deletedAt: undefined}
];

const deletionDateSortASC = (itemA, itemB) =>
  (+new Date(itemA.deletedAt) || 0) -
  (+new Date(itemB.deletedAt) || 0);
const deletionDateSortDESC = (itemA, itemB) =>
  deletionDateSortASC(itemB, itemA);

function acceptedAnswerSortASC(a, b) {
  return (a.deletedAt < b.deletedAt) ? -1 : ((a.deletedAt > b.deletedAt) ? 1 : 0);
}
function acceptedAnswerSortDESC(a, b) {
  return acceptedAnswerSortASC(b, a);
}

// Had to modify this solution to avoid the TypeError: a.deletedAt is null
const localeCompareSortASC = (a, b) => (a.deletedAt || '').localeCompare(b.deletedAt);
const localeCompareSortDESC = (a, b) => -(a.deletedAt || '').localeCompare(b.deletedAt);

function simpleDateSubtractionSortASC(a, b) {
  return new Date(a.deletedAt) - new Date(b.deletedAt);
}
function simpleDateSubtractionSortDESC(a, b) {
  return simpleDateSubtractionSortASC(b, a);
}

console.log('Using modified Date subtraction', [...data].sort(deletionDateSortDESC));
console.log('Using accepted answer lexocographical sort', [...data].sort(acceptedAnswerSortDESC));
console.log('Using locale compare lexocographical sort', [...data].sort(localeCompareSortDESC));
console.log('Using simple Date subtraction sort', [...data].sort(simpleDateSubtractionSortDESC));
Alligator answered 24/8, 2022 at 21:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.