按照ISO 8601日期排序数组

78

如何按日期(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' }

代码示例:
https://jsfiddle.net/4tUZt/


1
在我看来,日期也会按字母顺序排序。 - mplungjan
很多人建议使用 Date.parse,但它不能给出一致的结果。https://dev59.com/lW025IYBdhLWcg3wyJCV - Scott
8个回答

125

按字典序排列:

正如 @kdbanman 指出的那样,ISO8601一般原则是为了字典排序而设计的。因此,ISO8601字符串表示形式可以像任何其他字符串一样进行排序,这将给出期望的顺序。

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

那么你需要实现:

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);
});

使用JavaScript日期进行排序:

旧版本的WebKit和Internet Explorer不支持ISO 8601日期格式,因此您需要创建一个兼容的日期。它被FireFox和现代WebKit支持有关Date.parse支持的更多信息,请参见JavaScript:哪些浏览器支持使用Date.parse解析ISO-8601日期字符串

这是一篇非常好的文章,用于创建JavaScript ISO 8601兼容日期,然后您可以像普通JavaScript日期一样进行排序。

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));
}

使用方法:

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;
}

更新用法以包括排序技术信用@nbrooks


我发现在底部返回这个是必要的,以使其正常工作:`Date.prototype.setISO8601 = function (string) { ... return this;}` - Ian
8
可能已经完整,但这是不必要的。ISO8601是为了字典排序而设计的,所以只需将“'2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z'”视为真即可。 - kdbanman
1
@kdbanman 谢谢,好的了解。我已经更新了答案以包含这个信息。 - Scott
5
注意,这个答案并不完全正确。正如下面@icoum所指出的那样,在ISO8601日期字符串包含时区偏移量或者是(较少见的)负年份的情况下,字典序排序就行不通了。因此,如果你不能百分之百确定所有的日期都是以世界协调时间给出的,你应该首先从你的ISO字符串创建日期对象,然后再比较这些日期对象。 - brainfrozen
1
TIL!这很聪明。 - dgilperez
显示剩余2条评论

37
您可以通过使用内置的词典比较函数String.prototype.localeCompare而不是?:复合运算符或其他表达式来避免创建日期:

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))
);


11

请注意,现在被接受的答案建议按字典顺序排序日期。

然而,这只有在所有字符串使用“Z”或“+00”时区(= UTC)时才有效。 以“Z”结尾的日期字符串确实符合ISO8601标准,但并非所有ISO8601都以“Z”结尾。

因此,为了完全符合ISO8601标准,您需要使用某个日期库(例如Javascript DateMoment.js)解析您的字符串,并比较这些对象。 关于此部分,您可以查看Scott的答案,它还涵盖了与ISO8601不兼容的浏览器。

我的简单示例使用Javascript Date(适用于任何不太旧的浏览器):

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);
});

缺点:这比仅按字典顺序比较字符串要慢。

关于ISO8601标准的更多信息:在此处


这是一个很好的观点,但API返回不同格式的日期字符串通常是一个不寻常的情况(或者是一个错误)。 - Drenai
2
嗨,我必须表示不同意。例如,在Web应用程序中,您可以比较从API获取的某些日期和在Web应用程序中选择的某些日期。第二个日期肯定会使用计算机的本地时区,这可能与API的时区不同。 您还可以比较从使用不同时区的2个不同API获取的日期。 - icoum
排序函数需要返回1、-1或0。我不知道通过减去新日期(a.date)- 新日期(b.date)如何在这里起作用。 - MarcFasel
1
你好! 实际上,作为参数给出的排序函数不必精确地返回-1或1。 返回任何负值与返回-1相同。正值和1也是如此。您可以查看JS sort函数的文档页面:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort 此外,这一切都有效,因为从两个JS日期对象中减去返回它们之间的毫秒差异:https://dev59.com/iW445IYBdhLWcg3wR4RN#4944782 祝您拥有愉快的一天 :) - icoum

6
我会选择这个:
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);

2
实际上,在这种情况下,他只是按字母顺序排序,这似乎确实应该起作用。+1 - nbrooks
@Scott 我只是在排序字符串而不是日期。这个方法可行,我已经使用了一段时间了。 - Dziad Borowy
伟大的思想总是相似的。像往常一样,我应该发表一个答案而不是评论... - mplungjan
@mplungjan 是的 :-) 我也更喜欢简洁明了的解决方案,而不是难以阅读和维护的过于复杂的东西。 - Dziad Borowy

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;
}


较旧版本的WebKit和Internet Explorer不支持ISO 8601。因此,在旧版浏览器中可能会失败。 - Scott
3
只按字符串排序怎么样? - mplungjan
@scott 谢谢,好的了解到了。我不确定哪些版本的IE实际上支持它,我会再确认一下。 - nbrooks
@mplungjan 我的第一反应是“那行不通”...但是在思考了一下之后,我想不出反例。也许它会起作用。不过,为了处理缩写版本或不严格遵循标准的API,始终坚持日期格式化可能更安全。 - nbrooks
1
如果字符串是一致的,那肯定比为了不支持该格式的浏览器而费尽周折要好。 - mplungjan
虽然这比Scott的答案简单,但仍然不必要地复杂。ISO8601旨在进行词典排序,因此'2007-01-17T08:00:00Z' < '2008-01-17T08:00:00Z' === true。请参见下面的@rjmunro答案。 - kdbanman

2
演示: 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));


1

ISO8601旨在以纯文本的形式正确排序,因此通常情况下,普通排序就可以了。

要按数组中对象的特定键进行排序,您需要为sort()方法指定比较函数。在许多其他语言中,这些很容易使用cmp函数编写,但JS没有内置的cmp函数,因此我发现编写自己的函数最容易。

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) });

顺便说一下,我会使用类似JSON的语法来编写我的数组,就像这样:

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' }
];

0

如果您正在对可能缺少日期且日期可能处于不同时区的对象进行排序,则最终需要使用更复杂的内容:

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

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

如果你知道日期都有定义且有效,并且你知道所有日期都在同一个时区,那么你应该选择其他更快的答案之一。然而,如果你想要进行日期排序,有一个或多个这些边缘情况,并且不想预处理数据来清理它,那么我建议使用这种方法。

我尝试在下面的代码片段中演示了其他答案在这些边缘情况下的失败。

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));


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接