74

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/

The Amateur Coder
  • 789
  • 3
  • 11
  • 33
Peter
  • 11,413
  • 31
  • 100
  • 152
  • 1
    Seems to me the date would sort alphabetically too – mplungjan Aug 30 '12 at 08:16
  • A lot of people are suggesting `Date.parse` but it doesn't give consistent results http://stackoverflow.com/questions/5802461/javascript-which-browsers-support-parsing-of-iso-8601-date-string-with-date-par – Scott Aug 30 '12 at 08:39

8 Answers8

122

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

Community
  • 1
  • 1
Scott
  • 21,211
  • 8
  • 65
  • 72
  • I found that returning this at the bottom of `Date.prototype.setISO8601 = function (string) { ... return this; }` was necessary to make this work... – Ian Jan 27 '15 at 12:45
  • 7
    This may be complete, but it's unnecessary. **ISO8601 is [designed](https://en.wikipedia.org/wiki/ISO_8601#General_principles) for lexicographical sort, so just `'2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === true`** – kdbanman Apr 07 '16 at 01:35
  • 1
    @kdbanman Thanks, good to know. I've updated the answer to include this. – Scott Apr 07 '16 at 13:20
  • 4
    Careful, this answer is not fully correct. As pointed out by @icoum 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. – brainfrozen Jul 16 '20 at 09:04
  • in the sort function, what is happening when you return 1, -1 and 0? – hello world Sep 14 '20 at 09:02
  • https://stackoverflow.com/questions/8282802/what-do-return-1-1-and-0-mean-in-this-javascript-code/8282836 – hello world Sep 14 '20 at 09:20
  • 1
    TIL! This is smart – dgilperez Nov 05 '22 at 16:19
34

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))
);
RobG
  • 142,382
  • 31
  • 172
  • 209
11

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.

icoum
  • 171
  • 1
  • 5
  • It's a good point, but it would be an unusual case (or a bug) where an API would return date strings in different formats – Drenai Feb 05 '21 at 08:37
  • 2
    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. – icoum Mar 13 '21 at 09:00
  • 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) – MarcFasel Mar 23 '22 at 05:40
  • 1
    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 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort Also, this all works because subtracting two JS Date objects returns the difference between them in milliseconds : https://stackoverflow.com/a/4944782/9903390 Have a nice day :) – icoum Mar 24 '22 at 11:29
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);
Dziad Borowy
  • 12,368
  • 4
  • 41
  • 53
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;
}

nbrooks
  • 18,126
  • 5
  • 54
  • 66
  • older versions of WebKit and Internet Explorer do not support ISO 8601 natively. So this may fail in older browsers. – Scott Aug 30 '12 at 08:13
  • 3
    How about just sort on string ? – mplungjan Aug 30 '12 at 08:17
  • @scott Thanks good to know, I'm not positive which versions of IE actually support it, I'll double check. – nbrooks Aug 30 '12 at 08:17
  • @mplungjan 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 strictly – nbrooks Aug 30 '12 at 08:24
  • 1
    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 format – mplungjan Aug 30 '12 at 10:42
  • While this is simpler than Scott's answer, it's still unnecessarily complex. ISO8601 is [designed](https://en.wikipedia.org/wiki/ISO_8601#General_principles) for lexicographical sort, so `'2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === true`. See @rjmunro answer below. – kdbanman Apr 07 '16 at 01:38
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));

Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
1

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' }
];
rjmunro
  • 27,203
  • 20
  • 110
  • 132
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));
Patrick
  • 2,672
  • 3
  • 31
  • 47